// Contract: Token
// Target function: function tokenURI( uint256 tokenId ) public view override returns (string memory)
TokenTransfer(address(0), to, tokenId);

        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(address(0), to, tokenId);

        _afterTokenTransfer(address(0), to, tokenId);
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal virtual {
        address owner = ERC721.ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId);

        // Clear approvals
        _approve(address(0), tokenId);

        _balances[owner] -= 1;
        delete _owners[tokenId];

        emit Transfer(owner, address(0), tokenId);

        _afterTokenTransfer(owner, address(0), tokenId);
    }

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {Transfer} event.
     */
    function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {
        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId);

        // Clear approvals from the previous owner
        _approve(address(0), tokenId);

        _balances[from] -= 1;
        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);

        _afterTokenTransfer(from, to, tokenId);
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * Emits an {Approval} event.
     */
    function _approve(address to, uint256 tokenId) internal virtual {
        _tokenApprovals[tokenId] = to;
        emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits an {ApprovalForAll} event.
     */
    function _setApprovalForAll(
        address owner,
        address operator,
        bool approved
    ) internal virtual {
        require(owner != operator, "ERC721: approve to caller");
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Reverts if the `tokenId` has not been minted yet.
     */
    function _requireMinted(uint256 tokenId) internal view virtual {
        require(_exists(tokenId), "ERC721: invalid token ID");
    }

    /**
     * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
     * The call is not executed if the target address is not a contract.
     *
     * @param from address representing the previous owner of the given token ID
     * @param to target address that will receive the tokens
     * @param tokenId uint256 ID of the token to be transferred
     * @param data bytes optional data to send along with the call
     * @return bool whether the call correctly returned the expected magic value
     */
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    /// @solidity memory-safe-assembly
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
     * transferred to `to`.
     * - When `from` is zero, `tokenId` will be minted for `to`.
     * - When `to` is zero, ``from``'s `tokenId` will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}
}


//import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}


//import "./Common/IWhitelist.sol";
//--------------------------------------------
// WHITELIST intterface
//--------------------------------------------
interface IWhitelist {
    //--------------------
    // function
    //--------------------
    function check( address target ) external view returns (bool);
}


//------------------------------------------------------------
// Token(ERC721)
//------------------------------------------------------------
contract Token is Ownable, ERC721, ReentrancyGuard {
    //--------------------------------------------------------
    // 定数
    //--------------------------------------------------------
    string constant private TOKEN_NAME = "MetaIdol Extra";
    string constant private TOKEN_SYMBOL = "MIE";

    // mainnet
    address constant private OWNER_ADDRESS = 0xE2E577A889f2EB52C84c34E4539D33798987B6d2;

/*
    // testnet
    address constant private OWNER_ADDRESS = 0xf7831EA80Fc5179f86f82Af3aedDF2b7a2Ce13Df;
*/

    // constant
    uint256 constant private TOKEN_ID_OFS = 1;
    uint256 constant private BLOCK_SEC_MARGIN = 30;

    // enum
    uint256 constant private INFO_SUSPENDED = 0;
    uint256 constant private INFO_START = 1;
    uint256 constant private INFO_END = 2;
    uint256 constant private INFO_WHITELISTED = 3;
    uint256 constant private INFO_USER_MINTED = 4;
    uint256 constant private INFO_USER_MINTABLE = 5;
    uint256 constant private INFO_MAX = 6;

    uint256 constant private USER_INFO_TYPE = INFO_MAX;
    uint256 constant private USER_INFO_TOTAL_SUPPLY = INFO_MAX + 1;
    uint256 constant private USER_INFO_TOKEN_MAX = INFO_MAX + 2;
    uint256 constant private USER_INFO_MAX = INFO_MAX + 3;

    //--------------------------------------------------------
    // 管理
    //--------------------------------------------------------
    address private _manager;

    //--------------------------------------------------------
    // ストレージ
    //--------------------------------------------------------
    string private _base_uri_for_hidden;
    string private _base_uri_for_revealed;
    uint256 private _token_max;
    uint256 private _token_reserved;
    uint256 private _total_supply;

    // GOLD
    bool private _GOLD_is_suspended;
    uint256 private _GOLD_start;
    uint256 private _GOLD_end;
    uint256 private _GOLD_mintable;
    IWhitelist[] private _GOLD_arr_whitelist;
    mapping( address => uint256 ) private _GOLD_map_user_minted;

    // SILVER
    bool private _SILVER_is_suspended;
    uint256 private _SILVER_start;
    uint256 private _SILVER_end;
    uint256 private _SILVER_mintable;
    IWhitelist[] private _SILVER_arr_whitelist;
    mapping( address => uint256 ) private _SILVER_map_user_minted;

    //--------------------------------------------------------
    // [modifier] onlyOwnerOrManager
    //--------------------------------------------------------
    modifier onlyOwnerOrManager() {
        require( msg.sender == owner() || msg.sender == manager(), "caller is not the owner neither manager" );
        _;
    }

    //--------------------------------------------------------
    // コンストラクタ
    //--------------------------------------------------------
    constructor() Ownable() ERC721( TOKEN_NAME, TOKEN_SYMBOL ) {
        transferOwnership( OWNER_ADDRESS );
        _manager = msg.sender;

        //-----------------------
        // mainnet
        //-----------------------
        _base_uri_for_hidden = "ipfs://QmWSjFf17rHj6coHC2jGQs97gkLDBh3gADirmysmUkQ9Cm/";
        //_base_uri_for_revealed = "";
        _token_max = 777;
        _token_reserved = 20;

        //***********************
        // GOLD
        //***********************
        _GOLD_start = 1667116800;                  // 2022/10/30 17:00:00(JST)
        _GOLD_end   = 1667124000;                  // 2022/10/30 19:00:00(JST)
        _GOLD_mintable = 1;                        // 1 nft
        _GOLD_arr_whitelist.push( IWhitelist(0x3998f6917935bc77E77b52Bb067cf80eC7C837b4) );
        
        //~~~~~~~~~~~~~~~~~~~~~~~
        // SILVER
        //~~~~~~~~~~~~~~~~~~~~~~~
        _SILVER_start = 1667125800;                // 2022/10/30 19:30:00(JST)
        _SILVER_end   = 1667134800;                // 2022/10/30 22:00:00(JST)
        _SILVER_mintable = 5;                      // 5 nft
        _SILVER_arr_whitelist.push( IWhitelist(0x3998f6917935bc77E77b52Bb067cf80eC7C837b4) );   // GOLDはSILVERを兼ねる
        _SILVER_arr_whitelist.push( IWhitelist(0xf4C6DE0DAb70126FDD388b435ce4A81099768175) );

/*
        //-----------------------
        // testnet
        //-----------------------
        _base_uri_for_hidden = "ipfs://QmbuYkDiEuTxASyhRHtW9HRyvCKFPpk2BGEciSNAJ5sGDv/";
        //_base_uri_for_revealed = "";
        _token_max = 20;
        _token_reserved = 5;

        //***********************
        // GOLD
        //***********************
        _GOLD_start = 1667070000;                  // test 2022-10-30 04:00:00(JST)
        _GOLD_end   = 1667073000;                  // test 2022-10-30 04:50:00(JST)
        _GOLD_mintable = 1;                        // 1 nft
        _GOLD_arr_whitelist.push( IWhitelist(0x370324659963b56f34C86fC120d13176a80e0923) );
        
        //~~~~~~~~~~~~~~~~~~~~~~~
        // SILVER
        //~~~~~~~~~~~~~~~~~~~~~~~
        _SILVER_start = 1667073600;                // test 2022-10-30 05:00:00(JST)
        _SILVER_end   = 1667076600;                // test 2022-10-30 05:50:00(JST)
        _SILVER_mintable = 5;                      // 5 nft
        _SILVER_arr_whitelist.push( IWhitelist(0x370324659963b56f34C86fC120d13176a80e0923) );   // GOLDはSILVERを兼ねる
        _SILVER_arr_whitelist.push( IWhitelist(0xF7131FAF9504ffB195730622c7c664cB138f2a67) );
*/
    }

    //--------------------------------------------------------
    // [public] manager
    //--------------------------------------------------------
    function manager() public view returns (address) {
        return( _manager );
    }

    //--------------------------------------------------------
    // [external/onlyOwner] setManager
    //--------------------------------------------------------
    function setManager( address target ) external onlyOwner {
        _manager = target;
    }

    //--------------------------------------------------------
    // [external] get
    //--------------------------------------------------------
    function baseUriForHidden() external view returns (string memory) { return( _base_uri_for_hidden ); }
    function baseUriForRevealed() external view returns (string memory) { return( _base_uri_for_revealed ); }
    function tokenMax() external view returns (uint256) { return( _token_max ); }
    function tokenReserved() external view returns (uint256) { return( _token_reserved ); }
    function totalSupply() external view returns (uint256) { return( _total_supply ); }

    //--------------------------------------------------------
    // [external/onlyOwnerOrManager] set
    //--------------------------------------------------------
    function setBaseUriForHidden( string calldata uri ) external onlyOwnerOrManager { _base_uri_for_hidden = uri; }
    function setBaseUriForRevealed( string calldata uri ) external onlyOwnerOrManager { _base_uri_for_revealed = uri; }
    function setTokenMax( uint256 max ) external onlyOwnerOrManager { _token_max = max; }
    function setTokenReserved( uint256 reserved ) external onlyOwnerOrManager { _token_reserved = reserved; }