contract MoonieNFT is
    IERC721Mintable,
    ERC721Burnable,
    ERC721Enumerable,
    ERC721URIStorage,
    AccessControlMixin,
    NativeMetaTransaction,
    ContextMixin,
    FxBaseRootTunnel
{

    // STATE CONSTANTS

    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

    // EVENTS

    event TransferToPolygon(address indexed owner, uint256 indexed tokenId);
    event ReceivedFromPolygon(address indexed owner, uint256 indexed tokenId);
    event Burned(uint256 indexed tokenId);

    // CONSTRUCTOR

    constructor(
        string memory name_,
        string memory symbol_,
        address checkpointManager,
        address fxRoot
    ) ERC721(name_, symbol_) FxBaseRootTunnel(checkpointManager, fxRoot) {
        _setupContractId("MoonieNFT");
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
        _initializeEIP712(name_);
    }

    // VIEWS

    function tokenURI(uint256 tokenId) public view virtual override(ERC721, ERC721URIStorage) returns (string memory) {
        return super.tokenURI(tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        virtual
        override(IERC165, AccessControl, ERC721, ERC721Enumerable)
        returns (bool) {
            return super.supportsInterface(interfaceId);
    }

    // EXTERNAL FUNCTIONS

    /**
     * @dev See {IMintableERC721-mint}.
     * 
     * If you're attempting to bring metadata associated with token
     * from L2 to L1, you must implement this method
     */
    function mint(address user, uint256 tokenId, string memory tokenUri) external override only(MINTER_ROLE) {
        _mint(user, tokenId);
        _setTokenURI(tokenId, tokenUri);
    }

    function exists(uint256 tokenId) external view override returns (bool) {
        return _exists(tokenId);
    }

    function transferToPolygon(uint256 tokenId) external {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "MoonieNFT: caller is not owner nor approved");
        _sendMessageToChild(abi.encode(_msgSender(), tokenId, _stripBaseURI(tokenId)));
        _burn(tokenId);
        emit TransferToPolygon(_msgSender(), tokenId);
    }

    // INTERNAL FUNCTIONS

    function _burn(uint256 tokenId) internal virtual override(ERC721, ERC721URIStorage) {
        emit Burned(tokenId);
        super._burn(tokenId);
    }

    // This is to support Native meta transactions
    // never use msg.sender directly, use _msgSender() instead
    function _msgSender()
        internal
        override
        view
        returns (address)
    {
        return ContextMixin.msgSender();
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual override(ERC721, ERC721Enumerable) {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return "ipfs://";
    }

    function _stripBaseURI(uint256 tokenId) internal view returns (string memory) {
        bytes memory uri = bytes(tokenURI(tokenId));
        uint256 uriLength = uri.length;
        bytes memory strippedUri = new bytes(uriLength - 7);
        for (uint256 i = 7; i < uriLength; i++) {
            strippedUri[i - 7] = uri[i];
        }
        return string(strippedUri);
    }


    function _processMessageFromChild(bytes memory message) virtual internal override {
        (address owner, uint256 tokenId, string memory tokenUri) = abi.decode(message, (address, uint256, string));
        _mint(owner, tokenId);
        _setTokenURI(tokenId, tokenUri);
        emit ReceivedFromPolygon(owner, tokenId);
    }

}
function tokenURI(uint256 tokenId) public view virtual override(ERC721, ERC721URIStorage) returns (string memory) {
    return super.tokenURI(tokenId);
}

// VIEWS