contract BearnTokenGateway is
    CanReclaimTokens,
    GatewayState {
    using SafeMath for uint;

    event LogMintAuthorityUpdated(address indexed _newMintAuthority);
    event LogMint(
        address indexed _to,
        uint _amount,
        uint indexed _n,
        bytes32 indexed _signedMessageHash
    );
    event LogBurn(
        bytes _to,
        uint _amount,
        uint indexed _n,
        bytes indexed _indexedTo
    );

    /// @notice Only allow the Darknode Payment contract.
    modifier onlyOwnerOrMintAuthority() {
        require(
            msg.sender == mintAuthority || msg.sender == owner(),
            "Gateway: caller is not the owner or mint authority"
        );
        _;
    }

    /// @param _token The BearnTokenERC20 this Gateway is responsible for.
    /// @param _feeRecipient The recipient of burning and minting fees.
    /// @param _mintAuthority The address of the key that can sign mint
    ///        requests.
    /// @param _mintFee The amount subtracted each mint request and
    ///        forwarded to the feeRecipient. In BIPS.
    /// @param _burnFee The amount subtracted each burn request and
    ///        forwarded to the feeRecipient. In BIPS.
    function initialize(
        BearnTokenERC20 _token,
        address _feeRecipient,
        address _mintAuthority,
        uint16 _mintFee,
        uint16 _burnFee,
        uint _minimumBurnAmount
    ) public initializer {
        CanReclaimTokens.initialize(msg.sender);
        minimumBurnAmount = _minimumBurnAmount;
        token = _token;
        mintFee = _mintFee;
        burnFee = _burnFee;
        updateMintAuthority(_mintAuthority);
        updateFeeRecipient(_feeRecipient);
    }

    // Public functions ////////////////////////////////////////////////////////

    /// @notice Claims ownership of the token passed in to the constructor.
    /// `transferStoreOwnership` must have previously been called.
    /// Anyone can call this function.
    function claimTokenOwnership() public {
        token.claimOwnership();
    }

    /// @notice Allow the owner to update the owner of the BearnTokenERC20 token.
    function transferTokenOwnership(BearnTokenGateway _nextTokenOwner)
    public
    onlyOwner
    {
        token.transferOwnership(address(_nextTokenOwner));
        _nextTokenOwner.claimTokenOwnership();
    }

    /// @notice Allow the owner to update the fee recipient.
    ///
    /// @param _nextMintAuthority The address to start paying fees to.
    function updateMintAuthority(address _nextMintAuthority)
    public
    onlyOwnerOrMintAuthority
    {
        // The mint authority should not be set to 0, which is the result
        // returned by ecrecover for an invalid signature.
        require(
            _nextMintAuthority != address(0),
            "Gateway: mintAuthority cannot be set to address zero"
        );
        mintAuthority = _nextMintAuthority;
        emit LogMintAuthorityUpdated(mintAuthority);
    }

    /// @notice Allow the owner to update the minimum burn amount.
    ///
    /// @param _minimumBurnAmount The new min burn amount.
    function updateMinimumBurnAmount(uint _minimumBurnAmount)
    public
    onlyOwner
    {
        minimumBurnAmount = _minimumBurnAmount;
    }

    /// @notice Allow the owner to update the fee recipient.
    ///
    /// @param _nextFeeRecipient The address to start paying fees to.
    function updateFeeRecipient(address _nextFeeRecipient) public onlyOwner {
        // 'mint' and 'burn' will fail if the feeRecipient is 0x0
        require(
            _nextFeeRecipient != address(0x0),
            "Gateway: fee recipient cannot be 0x0"
        );

        feeRecipient = _nextFeeRecipient;
    }

    /// @notice Allow the owner to update the 'mint' fee.
    ///
    /// @param _nextMintFee The new fee for minting and burning.
    function updateMintFee(uint16 _nextMintFee) public onlyOwner {
        mintFee = _nextMintFee;
    }

    /// @notice Allow the owner to update the burn fee.
    ///
    /// @param _nextBurnFee The new fee for minting and burning.
    function updateBurnFee(uint16 _nextBurnFee) public onlyOwner {
        burnFee = _nextBurnFee;
    }

    function mint(
        string calldata _symbol,
        address _recipient,
        uint _amount,
        bytes32 _nHash,
        bytes calldata _sig
    ) external {
        bytes32 payloadHash = keccak256(abi.encode(_symbol, _recipient));
        // Verify signature
        bytes32 signedMessageHash = hashForSignature(
            _symbol,
            _recipient,
            _amount,
            msg.sender,
            _nHash
        );
        require(
            status[signedMessageHash] == false,
            "Gateway: nonce hash already spent"
        );
        if (!verifySignature(signedMessageHash, _sig)) {
            // Return a detailed string containing the hash and recovered
            // signer. This is somewhat costly but is only run in the revert
            // branch.
            revert(
                String.add8(
                    "Gateway: invalid signature. pHash: ",
                    String.fromBytes32(payloadHash),
                    ", amount: ",
                    String.fromUint(_amount),
                    ", msg.sender: ",
                    String.fromAddress(msg.sender),
                    ", _nHash: ",
                    String.fromBytes32(_nHash)
                )
            );
        }
        status[signedMessageHash] = true;

        // Mint `amount - fee` for the recipient and mint `fee` for the minter
        uint absoluteFee = _amount.mul(mintFee).div(
            BIPS_DENOMINATOR
        );
        uint receivedAmount = _amount.sub(
            absoluteFee,
            "Gateway: fee exceeds amount"
        );

        // Mint amount minus the fee
        token.mint(_recipient, receivedAmount);
        // Mint the fee
        token.mint(feeRecipient, absoluteFee);

        emit LogMint(
            _recipient,
            receivedAmount,
            nextN,
            signedMessageHash
        );
        nextN += 1;
    }

    /// @notice burn destroys tokens after taking a fee for the `_feeRecipient`,
    ///         allowing the associated assets to be released on their native
    ///         chain.
    ///
    /// @param _to The address to receive the un-bridged asset. The format of
    ///        this address should be of the destination chain.
    ///        For example, when burning to Bitcoin, _to should be a
    ///        Bitcoin address.
    /// @param _amount The amount of the token being burnt, in its
    ///        smallest value. (e.g. satoshis for BTC)
    function burn(bytes calldata _to, uint _amount)
    external {
        //    function burn(bytes memory _to, uint _amount) public returns (uint) {
        require(
            token.transferFrom(msg.sender, address(this), _amount),
            "token transfer failed"
        );
        // The recipient must not be empty. Better validation is possible,
        // but would need to be customized for each destination ledger.
        require(_to.length != 0, "Gateway: to address is empty");

        // Calculate fee, subtract it from amount being burnt.
        uint fee = _amount.mul(burnFee).div(BIPS_DENOMINATOR);
        uint amountAfterFee = _amount.sub(
            fee,
            "Gateway: fee exceeds amount"
        );

        // Burn the whole amount, and then re-mint the fee.
        token.burn(_amount);
        token.mint(feeRecipient, fee);

        require(
        // Must be strictly greater, to that the release transaction is of
        // at least one unit.
            amountAfterFee > minimumBurnAmount,
            "Gateway: amount is less than the minimum burn amount"
        );

        emit LogBurn(_to, amountAfterFee, nextN, _to);
        nextN += 1;
    }

    /// @notice verifySignature checks the the provided signature matches the provided
    /// parameters.
    function verifySignature(bytes32 _signedMessageHash, bytes memory _sig)
    public
    view
    returns (bool)
    {
        bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(_signedMessageHash);
        address signer = ECDSA.recover(ethSignedMessageHash, _sig);
        return mintAuthority == signer;
    }

    /// @notice hashForSignature hashes the parameters so that they can be signed.
    function hashForSignature(
        string memory _symbol,
        address _recipient,
        uint _amount,
        address _caller,
        bytes32 _nHash
    ) public view returns (bytes32) {
        bytes32 payloadHash = keccak256(abi.encode(_symbol, _recipient));
        return
        keccak256(abi.encode(payloadHash, _amount, address(token), _caller, _nHash));
    }
}
/// @notice Gateway handles verifying mint and burn requests. A mintAuthority
/// approves new assets to be minted by providing a digital signature. An ownernpm
/// of an asset can request for it to be burnt.
function transferTokenOwnership(BearnTokenGateway _nextTokenOwner)
public
onlyOwner
{
    token.transferOwnership(address(_nextTokenOwner));
    _nextTokenOwner.claimTokenOwnership();
}

/// @notice Allow the owner to update the owner of the BearnTokenERC20 token.