contract SignerV2 is ISignatureValidator {
    bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH =
        0x7a9f5b2bf4dbb53eb85e012c6094a3d71d76e5bfe821f44ab63ed59311264e35;
    bytes32 private constant MSG_TYPEHASH =
        0xa1a7ad659422d5fc08fdc481fd7d8af8daf7993bc4e833452b0268ceaab66e5d; // mapping for msg typehash

    mapping(bytes32 => bytes32) public approvedSignatures;

    /* solium-disable */
    address public safe;
    mapping(address => mapping(bytes4 => bool)) public allowedTransactions;
    /* solium-enable */

    event SignatureCreated(bytes signature, bytes32 indexed hash);

    modifier onlySafe() {
        require(msg.sender == safe, "Signer: only safe functionality");
        _;
    }

    /**
     * @dev                       Signer Constructor
     * @param _safe               Gnosis Safe address.
     * @param _contracts          array of contract addresses
     * @param _functionSignatures array of function signatures
     */
    constructor(
        address _safe,
        address[] memory _contracts,
        bytes4[] memory _functionSignatures
    ) {
        require(_safe != address(0), "Signer: Safe address zero");
        safe = _safe;
        for (uint256 i; i < _contracts.length; i++) {
            require(
                _contracts[i] != address(0),
                "Signer: contract address zero"
            );
            require(
                _functionSignatures[i] != bytes4(0),
                "Signer: function signature zero"
            );
            allowedTransactions[_contracts[i]][_functionSignatures[i]] = true;
        }
    }

    /**
     * @dev                   Signature generator
     * @param _to             receiver address.
     * @param _value          value in wei.
     * @param _data           encoded transaction data.
     * @param _operation      type of operation call.
     * @param _safeTxGas      safe transaction gas for gnosis safe.
     * @param _baseGas        base gas for gnosis safe.
     * @param _gasPrice       gas price for gnosis safe transaction.
     * @param _gasToken       token which gas needs to paid for gnosis safe transaction.
     * @param _refundReceiver address account to receive refund for remaining gas.
     * @param _nonce          gnosis safe contract nonce.
     */
    function generateSignature(
        address _to,
        uint256 _value,
        bytes calldata _data,
        Enum.Operation _operation,
        uint256 _safeTxGas,
        uint256 _baseGas,
        uint256 _gasPrice,
        address _gasToken,
        address _refundReceiver,
        uint256 _nonce
    ) external returns (bytes memory signature, bytes32 hash) {
        // check if transaction parameters are correct
        require(
            allowedTransactions[_to][_getFunctionHashFromData(_data)],
            "Signer: invalid function"
        );
        require(
            _value == 0 &&
                _refundReceiver == address(0) &&
                _operation == Enum.Operation.Call,
            "Signer: invalid arguments"
        );

        // get contractTransactionHash from gnosis safe
        hash = Safe(safe).getTransactionHash(
            _to,
            0,
            _data,
            _operation,
            _safeTxGas,
            _baseGas,
            _gasPrice,
            _gasToken,
            _refundReceiver,
            _nonce
        );

        bytes memory paddedAddress = bytes.concat(
            bytes12(0),
            bytes20(address(this))
        );
        bytes memory messageHash = _encodeMessageHash(hash);
        // check if transaction is not signed before
        // solhint-disable-next-line reason-string
        require(
            approvedSignatures[hash] != keccak256(messageHash),
            "Signer: transaction already signed"
        );

        // generate signature and add it to approvedSignatures mapping
        signature = bytes.concat(
            paddedAddress,
            bytes32(uint256(65)),
            bytes1(0),
            bytes32(uint256(messageHash.length)),
            messageHash
        );
        approvedSignatures[hash] = keccak256(messageHash);
        emit SignatureCreated(signature, hash);
    }

    /**
     * @dev                Validate signature using EIP1271
     * @param _data        Encoded transaction hash supplied to verify signature.
     * @param _signature   Signature that needs to be verified.
     */
    function isValidSignature(bytes memory _data, bytes memory _signature)
        public
        view
        override
        returns (bytes4)
    {
        if (_data.length == 32) {
            bytes32 hash;
            // solhint-disable-next-line no-inline-assembly
            assembly {
                hash := mload(add(_data, 32))
            }
            if (approvedSignatures[hash] == keccak256(_signature)) {
                return EIP1271_MAGIC_VALUE;
            }
        } else {
            if (approvedSignatures[keccak256(_data)] == keccak256(_signature)) {
                return EIP1271_MAGIC_VALUE;
            }
        }
        return "0x";
    }

    /**
     * @dev               Get the byte hash of function call i.e. first four bytes of data
     * @param data        encoded transaction data.
     */
    function _getFunctionHashFromData(bytes memory data)
        private
        pure
        returns (bytes4 functionHash)
    {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            functionHash := mload(add(data, 32))
        }
    }

    /**
     * @dev                encode message with contants
     * @param message      the message that needs to be encoded
     */
    function _encodeMessageHash(bytes32 message)
        private
        pure
        returns (bytes memory)
    {
        bytes32 safeMessageHash = keccak256(abi.encode(MSG_TYPEHASH, message));
        return
            abi.encodePacked(
                bytes1(0x19),
                bytes1(0x23),
                keccak256(
                    abi.encode(DOMAIN_SEPARATOR_TYPEHASH, safeMessageHash)
                )
            );
    }

    /**
     * @dev                set new safe
     * @param _safe        safe address
     */
    function setSafe(address _safe) public onlySafe {
        require(_safe != address(0), "Signer: Safe zero address");
        safe = _safe;
    }

    /**
     * @dev                      add new contracts and functions
     * @param _contract          contract address
     * @param _functionSignature function signature for the contract
     */
    function approveNewTransaction(address _contract, bytes4 _functionSignature)
        external
        onlySafe
    {
        require(_contract != address(0), "Signer: contract address zero");
        require(
            _functionSignature != bytes4(0),
            "Signer: function signature zero"
        );
        allowedTransactions[_contract][_functionSignature] = true;
    }

    /**
     * @dev                      add new contracts and functions
     * @param _contract           contract address
     * @param _functionSignature function signature for the contract
     */
    function removeAllowedTransaction(
        address _contract,
        bytes4 _functionSignature
    ) external onlySafe {
        // solhint-disable-next-line reason-string
        require(
            allowedTransactions[_contract][_functionSignature] == true,
            "Signer: only approved transactions can be removed"
        );
        allowedTransactions[_contract][_functionSignature] = false;
    }
}
/**
 * @title PrimeDAO Signer Contract
 * @dev   Enables signing approved function signature transaction before sending it to Gnosis Safe.
 */
function isValidSignature(bytes memory _data, bytes memory _signature)
    public
    view
    override
    returns (bytes4)
{
    if (_data.length == 32) {
        bytes32 hash;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            hash := mload(add(_data, 32))
        }
        if (approvedSignatures[hash] == keccak256(_signature)) {
            return EIP1271_MAGIC_VALUE;
        }
    } else {
        if (approvedSignatures[keccak256(_data)] == keccak256(_signature)) {
            return EIP1271_MAGIC_VALUE;
        }
    }
    return "0x";
}

/**
 * @dev                Validate signature using EIP1271
 * @param _data        Encoded transaction hash supplied to verify signature.
 * @param _signature   Signature that needs to be verified.
 */