// Contract: Token
// Target function: function getUserInfo( address target ) external view returns (uint256[USER_INFO_MAX] memory)
:00:00(UTC)
        _PUBLIC_SALE_end = 1655154000;          // 2022-06-13 21:00:00(UTC)
        _PUBLIC_SALE_arrWhiteList.push( IWhiteList(0x2821Dbf2685fCda10C64907637aF56c7f92C4dd6) );
        _PUBLIC_SALE_arrWhiteList.push( IWhiteList(0x55E25eb0DbB1AA8a9b47e44fD27438B827D4A986) );
        _PUBLIC_SALE_arrWhiteList.push( IWhiteList(0x62a2CC3889d65666A669533fE8f92a323FE2c9A8) );
        _PUBLIC_SALE_arrWhiteList.push( IWhiteList(0x904f99D3646660096752e0Bc2fd5458962788F7B) );
        _PUBLIC_SALE_arrWhiteList.push( IWhiteList(0x8336f23513c25c52775DB44624BA5B731C8BAE18) );

        //~~~~~~~~~~~~~~~~~~~~~~~
        // FINAL SALE
        //~~~~~~~~~~~~~~~~~~~~~~~
        _FINAL_SALE_start = 1655154000;         // 2022-06-13 21:00:00
        _FINAL_SALE_end = 0;                    // no period
    }

    //--------------------------------------------------------
    // [public] manager
    //--------------------------------------------------------
    function manager() public view returns (address) {
        return( _manager );
    }

    //--------------------------------------------------------
    // [external/onlyOwner] setManager
    //--------------------------------------------------------
    function setManager( address target ) external onlyOwner {
        _manager = target;
    }

    //--------------------------------------------------------
    // [external] baseUriForHidden
    //--------------------------------------------------------
    function baseUriForHidden() external view returns (string memory) {
        return( _baseUriForHidden );
    }

    //--------------------------------------------------------
    // [external/onlyOwnerOrManager] setBaseUriForHidden
    //--------------------------------------------------------
    function setBaseUriForHidden( string calldata uri ) external onlyOwnerOrManager {
        _baseUriForHidden = uri;
    }

    //--------------------------------------------------------
    // [external] baseUriForRevealed
    //--------------------------------------------------------
    function baseUriForRevealed() external view returns (string memory) {
        return( _baseUriForRevealed );
    }

    //--------------------------------------------------------
    // [external/onlyOwnerOrManager] setBaseUriForRevealed
    //--------------------------------------------------------
    function setBaseUriForRevealed( string calldata uri ) external onlyOwnerOrManager {
        _baseUriForRevealed = uri;
    }

    //--------------------------------------------------------
    // [external] totalSupply
    //--------------------------------------------------------
    function totalSupply() external view returns (uint256) {
        return( _totalSupply );
    }

    //--------------------------------------------------------
    // [public/override] tokenURI
    //--------------------------------------------------------
    function tokenURI( uint256 tokenId ) public view override returns (string memory) {
        require( _exists( tokenId ), "nonexistent token" );

        if( bytes(_baseUriForRevealed).length > 0 ){
            return( string( abi.encodePacked( _baseUriForRevealed, Strings.toString( tokenId ) ) ) );            
        }

        return( string( abi.encodePacked( _baseUriForHidden, Strings.toString( tokenId ) ) ) );
    }

    //========================================================
    // [public] getInfo - WONDERLIST SALE
    //========================================================
    function WONDERLIST_SALE_getInfo( address target ) public view returns (uint256[INFO_MAX] memory) {
        uint256[INFO_MAX] memory arrRet;

        if( _WONDERLIST_SALE_isSuspended ){ arrRet[INFO_SALE_SUSPENDED] = 1; }
        arrRet[INFO_SALE_START] = _WONDERLIST_SALE_start;
        arrRet[INFO_SALE_END] = _WONDERLIST_SALE_end;
        arrRet[INFO_SALE_PRICE] = WONDERLIST_SALE_PRICE;
        if( _checkWhitelist( _WONDERLIST_SALE_arrWhiteList, target) ){ arrRet[INFO_SALE_WHITELISTED] = 1; }
        arrRet[INFO_SALE_MAX_MINT] = WONDERLIST_SALE_QUANTITY;
        arrRet[INFO_SALE_USER_MINTED] = _WONDERLIST_SALE_mapMinted[target];

        return( arrRet );
    }

    //========================================================
    // [external/onlyOwnerOrManager] write - WONDERLIST SALE
    //========================================================
    // isSuspended
    function WONDERLIST_SALE_suspend( bool flag ) external onlyOwnerOrManager {
        _WONDERLIST_SALE_isSuspended = flag;
    }

    // start-end
    function WONDERLIST_SALE_setStartEnd( uint256 start, uint256 end ) external onlyOwnerOrManager{
        _WONDERLIST_SALE_start = start;
        _WONDERLIST_SALE_end = end;
    }

    // whitelist
    function WONDERLIST_SALE_setWhiteList( address[] calldata lists ) external onlyOwnerOrManager {
        delete _WONDERLIST_SALE_arrWhiteList;

        for( uint256 i=0; i<lists.length; i++ ){
            require( lists[i] != address(0), "WONDERLIST SALE: invalid list"  );
            _WONDERLIST_SALE_arrWhiteList.push( IWhiteList(lists[i]) );
        }
    }

    //========================================================
    // [external/payable] mint - WONDERLIST SALE
    //========================================================
    function WONDERLIST_SALE_mint( uint256 num ) external payable nonReentrant {
        uint256[INFO_MAX] memory arrInfo = WONDERLIST_SALE_getInfo( msg.sender );

        require( arrInfo[INFO_SALE_SUSPENDED] == 0, "WONDERLIST SALE: suspended" );
        require( arrInfo[INFO_SALE_START] == 0 || arrInfo[INFO_SALE_START] <= block.timestamp, "WONDERLIST SALE: not opend" );
        require( arrInfo[INFO_SALE_END] == 0 || arrInfo[INFO_SALE_END] > block.timestamp, "WONDERLIST SALE: finished" );
        require( arrInfo[INFO_SALE_WHITELISTED] == 1, "WONDERLIST SALE: not whitelisted" );
        require( arrInfo[INFO_SALE_MAX_MINT] >= (arrInfo[INFO_SALE_USER_MINTED]+num), "WONDERLIST SALE: reached the limit" );

        _checkPayment( msg.sender, arrInfo[INFO_SALE_PRICE]*num, msg.value );

        _mintTokens( msg.sender, num );
        _WONDERLIST_SALE_mapMinted[msg.sender] += num;
    }

    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    // [public] getInfo - WONDERKIDS SALE
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    function WONDERKIDS_SALE_getInfo( address target ) public view returns (uint256[INFO_MAX] memory) {
        uint256[INFO_MAX] memory arrRet;

        if( _WONDERKIDS_SALE_isSuspended ){ arrRet[INFO_SALE_SUSPENDED] = 1; }
        arrRet[INFO_SALE_START] = _WONDERKIDS_SALE_start;
        arrRet[INFO_SALE_END] = _WONDERKIDS_SALE_end;
        arrRet[INFO_SALE_PRICE] = WONDERKIDS_SALE_PRICE;
        if( _checkWhitelist( _WONDERKIDS_SALE_arrWhiteList, target) ){ arrRet[INFO_SALE_WHITELISTED] = 1; }
        arrRet[INFO_SALE_MAX_MINT] = WONDERKIDS_SALE_QUANTITY;
        arrRet[INFO_SALE_USER_MINTED] = _WONDERKIDS_SALE_mapMinted[target];

        return( arrRet );
    }

    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    // [external/onlyOwnerOrManager] write - WONDERKIDS SALE
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    // isSuspended
    function WONDERKIDS_SALE_suspend( bool flag ) external onlyOwnerOrManager {
        _WONDERKIDS_SALE_isSuspended = flag;
    }

    // start-end
    function WONDERKIDS_SALE_setStartEnd( uint256 start, uint256 end ) external onlyOwnerOrManager{
        _WONDERKIDS_SALE_start = start;
        _WONDERKIDS_SALE_end = end;
    }

    // whitelist
    function WONDERKIDS_SALE_setWhiteList( address[] calldata lists ) external onlyOwnerOrManager {
        delete _WONDERKIDS_SALE_arrWhiteList;

        for( uint256 i=0; i<lists.length; i++ ){
            require( lists[i] != address(0), "WONDERKIDS SALE: invalid list"  );
            _WONDERKIDS_SALE_arrWhiteList.push( IWhiteList(lists[i]) );
        }
    }

    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    // [external/payable] mint - WONDERKIDS SALE
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    function WONDERKIDS_SALE_mint( uint256 num ) external payable nonReentrant {
        uint256[INFO_MAX] memory arrInfo = WONDERKIDS_SALE_getInfo( msg.sender );

        require( arrInfo[INFO_SALE_SUSPENDED] == 0, "WONDERKIDS SALE: suspended" );
        require( arrInfo[INFO_SALE_START] == 0 || arrInfo[INFO_SALE_START] <= block.timestamp, "WONDERKIDS SALE: not opend" );
        require( arrInfo[INFO_SALE_END] == 0 || arrInfo[INFO_SALE_END] > block.timestamp, "WONDERKIDS SALE: finished" );
        require( arrInfo[INFO_SALE_WHITELISTED] == 1, "WONDERKIDS SALE: not whitelisted" );
        require( arrInfo[INFO_SALE_MAX_MINT] >= (arrInfo[INFO_SALE_USER_MINTED]+num), "WONDERKIDS SALE: reached the limit" );

        _checkPayment( msg.sender, arrInfo[INFO_SALE_PRICE]*num, msg.value );

        _mintTokens( msg.sender, num );
        _WONDERKIDS_SALE_mapMinted[msg.sender] += num;
    }

    //********************************************************
    // [public] getInfo - PUBLIC SALE
    //********************************************************
    function PUBLIC_SALE_getInfo( address target ) public view returns (uint256[INFO_MAX] memory) {
        uint256[INFO_MAX] memory arrRet;

        if( _PUBLIC_SALE_isSuspended ){ arrRet[INFO_SALE_SUSPENDED] = 1; }
        arrRet[INFO_SALE_START] = _PUBLIC_SALE_start;
        arrRet[INFO_SALE_END] = _PUBLIC_SALE_end;
        arrRet[INFO_SALE_PRICE] = PUBLIC_SALE_PRICE;
        if( _checkWhitelist( _PUBLIC_SALE_arrWhiteList, target) ){ arrRet[INFO_SALE_WHITELISTED] = 1; }
        arrRet[INFO_SALE_MAX_MINT] = PUBLIC_SALE_QUANTITY;
        arrRet[INFO_SALE_USER_MINTED] = _PUBLIC_SALE_mapMinted[target];

        return( arrRet );
    }

    //********************************************************
    // [external/onlyOwnerOrManager] write - PUBLIC SALE
    //********************************************************
    // isSuspended
    function PUBLIC_SALE_suspend( bool flag ) external onlyOwnerOrManager {
        _PUBLIC_SALE_isSuspended = flag;
    }

    // start-end
    function PUBLIC_SALE_setStartEnd( uint256 start, uint256 end ) external onlyOwnerOrManager{
        _PUBLIC_SALE_start = start;
        _PUBLIC_SALE_end = end;
    }

    // whitelist
    function PUBLIC_SALE_setWhiteList( address[] calldata lists ) external onlyOwnerOrManager {
        delete _PUBLIC_SALE_arrWhiteList;

        for( uint256 i=0; i<lists.length; i++ ){
            require( lists[i] != address(0), "PUBLIC SALE: invalid list"  );
            _PUBLIC_SALE_arrWhiteList.push( IWhiteList(lists[i]) );
        }
    }

    //********************************************************
    // [external/payable] mint - PUBLIC SALE
    //********************************************************
    function PUBLIC_SALE_mint( uint256 num ) external payable nonReentrant {
        uint256[INFO_MAX] memory arrInfo = PUBLIC_SALE_getInfo( msg.sender );

        require( arrInfo[INFO_SALE_SUSPENDED] == 0, "PUBLIC SALE: suspended" );
        require( arrInfo[INFO_SALE_START] == 0 || arrInfo[INFO_SALE_START] <= block.timestamp, "PUBLIC SALE: not opend" );
        require( arrInfo[INFO_SALE_END] == 0 || arrInfo[INFO_SALE_END] > block.timestamp, "PUBLIC SALE: finished" );
        require( arrInfo[INFO_SALE_WHITELISTED] == 1, "PUBLIC SALE: not whitelisted" );
        require( arrInfo[INFO_SALE_MAX_MINT] >= (arrInfo[INFO_SALE_USER_MINTED]+num), "PUBLIC SALE: reached the limit" );

        _checkPayment( msg.sender, arrInfo[INFO_SALE_PRICE]*num, msg.value );

        _mintTokens( msg.sender, num );
        _PUBLIC_SALE_mapMinted[msg.sender] += num;
    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // [public] getInfo - FINAL SALE
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    function FINAL_SALE_getInfo( address target ) public view returns (uint256[INFO_MAX] memory) {
        uint256[INFO_MAX] memory arrRet;

        if( _FINAL_SALE_isSuspended ){ arrRet[INFO_SALE_SUSPENDED] = 1; }
        arrRet[INFO_SALE_START] = _FINAL_SALE_start;
        arrRet[INFO_SALE_END] = _FINAL_SALE_end;
        arrRet[INFO_SALE_PRICE] = FINAL_SALE_PRICE;
        arrRet[INFO_SALE_WHITELISTED] = 1;
        arrRet[INFO_SALE_MAX_MINT] = FINAL_SALE_QUANTITY;
        arrRet[INFO_SALE_USER_MINTED] = _FINAL_SALE_mapMinted[target];

        return( arrRet );
    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // [external/onlyOwnerOrManager] write - FINAL SALE
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // isSuspended
    function FINAL_SALE_suspend( bool flag ) external onlyOwnerOrManager {
        _FINAL_SALE_isSuspended = flag;
    }

    // start-end
    function FINAL_SALE_setStartEnd( uint256 start, uint256 end ) external onlyOwnerOrManager{
        _FINAL_SALE_start = start;
        _FINAL_SALE_end = end;
    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // [external/payable] mint - FINAL SALE
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    function FINAL_SALE_mint( uint256 num ) external payable nonReentrant {
        uint256[INFO_MAX] memory arrInfo = FINAL_SALE_getInfo( msg.sender );

        require( arrInfo[INFO_SALE_SUSPENDED] == 0, "FINAL SALE: suspended" );
        require( arrInfo[INFO_SALE_START] == 0 || arrInfo[INFO_SALE_START] <= block.timestamp, "FINAL SALE: not opend" );
        require( arrInfo[INFO_SALE_END] == 0 || arrInfo[INFO_SALE_END] > block.timestamp, "FINAL SALE: finished" );
        require( arrInfo[INFO_SALE_MAX_MINT] >= (arrInfo[INFO_SALE_USER_MINTED]+num), "FINAL SALE: reached the limit" );

        _checkPayment( msg.sender, arrInfo[INFO_SALE_PRICE]*num, msg.value );

        _mintTokens( msg.sender, num );
        _FINAL_SALE_mapMinted[msg.sender] += num;
    }

    //--------------------------------------------------------
    // [internal] _checkWhitelist
    //--------------------------------------------------------
    function _checkWhitelist( IWhiteList[] storage lists, address target ) internal view returns (bool) {
        for( uint256 i=0; i<lists.length; i++ ){
            if( lists[i].check(target) ){
                return( true);
            }
        }

        return( false );        
    }

    //--------------------------------------------------------
    // [internal] _checkPayment
    //--------------------------------------------------------
    function _checkPayment( address msgSender, uint256 price, uint256 payment ) internal {
        require( price <= payment, "insufficient value" );

        // refund if overpaymented
        if( price < payment ){
            uint256 change = payment - price;
            address payable target = payable( msgSender );
            target.transfer( change );
        }
    }

    //--------------------------------------------------------
    // [internal] _mintTokens
    //--------------------------------------------------------
    function _mintTokens( address to, uint256 num ) internal {
        require( to != address(0), "invalid to" );
        require( _totalSupply >= RESERVED_TOKEN, "reservation not finished" );
        require( (_totalSupply+num) <= MAX_TOKEN, "exceeded the supply range" );

        for( uint256 i=0; i<num; i++ ){
            _mint( to, _totalSupply+(i+1) );
        }
        _totalSupply += num;
    }

    //--------------------------------------------------------
    // [external/onlyOwnerOrManager] reserveTokens
    //--------------------------------------------------------
    function reserveTokens( uint256 num ) external onlyOwnerOrManager {
        require( (_totalSupply+num) <= RESERVED_TOKEN, "exceeded the reservation range" );

        for( uint256 i=0; i<num; i++ ){
            _mint( owner(), _totalSupply+(i+1) );
        }
        _totalSupply += num;
    }