// Contract: Token
// Target function: function airdropTokens() external onlyOwnerOrManager
Hidden() external view returns (string memory) { return( _baseUriForHidden ); }
    function baseUriForRevealed() external view returns (string memory) { return( _baseUriForRevealed ); }
    function tokenMax() external view returns (uint256) { return( _tokenMax ); }
    function totalSupply() external view returns (uint256) { return( _totalSupply ); }

    //--------------------------------------------------------
    // [external/onlyOwnerOrManager] set
    //--------------------------------------------------------
    function setBaseUriForHidden( string calldata uri ) external onlyOwnerOrManager { _baseUriForHidden = uri; }
    function setBaseUriForRevealed( string calldata uri ) external onlyOwnerOrManager { _baseUriForRevealed = uri; }
    function setTokenMax( uint256 max ) external onlyOwnerOrManager { _tokenMax = max; }

    //--------------------------------------------------------
    // [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 ) ) ) );
    }

    //--------------------------------------------------------
    // [external/onlyOwnerOrManager] write - AIRDROP
    //--------------------------------------------------------
    function AIRDROP_setList( address list ) external onlyOwnerOrManager {
        require( list != address(0), "AIRDROP: invalid list" );
        _AIRDROP_list = IAirdrop(list);
    }

    //--------------------------------------------------------
    // [external/onlyOwnerOrManager] write - MINTED
    //--------------------------------------------------------
    function MINTED_setList( address list ) external onlyOwnerOrManager {
        require( list != address(0), "MINTED: invalid list" );
        _MINTED_list = IWhiteList(list);
    }

    //========================================================
    // [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, true ) ){ 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;
    }

    // price
    function WONDERLIST_SALE_setPrice( uint256 price ) external onlyOwnerOrManager {
        _WONDERLIST_SALE_price = price;
    }

    // quantity
    function WONDERLIST_SALE_setQuantity( uint256 quantity ) external onlyOwnerOrManager {
        _WONDERLIST_SALE_quantity = quantity;
    }

    // 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+BLOCK_SEC_MARGIN), "WONDERLIST SALE: not opend" );
        require( arrInfo[INFO_SALE_END] == 0 || arrInfo[INFO_SALE_END] > (block.timestamp-BLOCK_SEC_MARGIN), "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, true ) ){ 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;
    }

    // price
    function WONDERKIDS_SALE_setPrice( uint256 price ) external onlyOwnerOrManager {
        _WONDERKIDS_SALE_price = price;
    }

    // quantity
    function WONDERKIDS_SALE_setQuantity( uint256 quantity ) external onlyOwnerOrManager {
        _WONDERKIDS_SALE_quantity = quantity;
    }

    // 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+BLOCK_SEC_MARGIN), "WONDERKIDS SALE: not opend" );
        require( arrInfo[INFO_SALE_END] == 0 || arrInfo[INFO_SALE_END] > (block.timestamp-BLOCK_SEC_MARGIN), "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, false ) ){ 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;
    }

    // price
    function PUBLIC_SALE_setPrice( uint256 price ) external onlyOwnerOrManager {
        _PUBLIC_SALE_price = price;
    }

    // quantity
    function PUBLIC_SALE_setQuantity( uint256 quantity ) external onlyOwnerOrManager {
        _PUBLIC_SALE_quantity = quantity;
    }

    // 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+BLOCK_SEC_MARGIN), "PUBLIC SALE: not opend" );
        require( arrInfo[INFO_SALE_END] == 0 || arrInfo[INFO_SALE_END] > (block.timestamp-BLOCK_SEC_MARGIN), "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;
    }

    // price
    function FINAL_SALE_setPrice( uint256 price ) external onlyOwnerOrManager {
        _FINAL_SALE_price = price;
    }

    // quantity
    function FINAL_SALE_setQuantity( uint256 quantity ) external onlyOwnerOrManager {
        _FINAL_SALE_quantity = quantity;
    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // [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+BLOCK_SEC_MARGIN), "FINAL SALE: not opend" );
        require( arrInfo[INFO_SALE_END] == 0 || arrInfo[INFO_SALE_END] > (block.timestamp-BLOCK_SEC_MARGIN), "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, bool checkMinted ) internal view returns (bool) {
        if( checkMinted ){
            if( _MINTED_list.check( target ) ){
                return( false );
            }
        }

        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( _totalSupply >= (RESERVED_TOKEN+AIRDROP_TOKEN), "airdrop not finished" );
        require( (_totalSupply+num) <= _tokenMax, "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;
    }