// Contract: Token
// Target function: function PRIVATE_SALE_mint( uint256 num, uint256 amount, bytes32[] calldata merkleProof ) external payable nonReentrant
[] memory);

    /**
     * @notice Returns the filtered operator at the given index of the set of filtered operators for a given address or
     *         its subscription.
     *         Note that order is not guaranteed as updates are made.
     */
    function filteredOperatorAt(address registrant, uint256 index) external returns (address);

    /**
     * @notice Returns the filtered codeHash at the given index of the list of filtered codeHashes for a given address or
     *         its subscription.
     *         Note that order is not guaranteed as updates are made.
     */
    function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32);

    /**
     * @notice Returns true if an address has registered
     */
    function isRegistered(address addr) external returns (bool);

    /**
     * @dev Convenience method to compute the code hash of an arbitrary contract
     */
    function codeHashOf(address addr) external returns (bytes32);
}


//import {CANONICAL_CORI_SUBSCRIPTION} from "./lib/Constants.sol";
address constant CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS = 0x000000000000AAeB6D7670E522A718067333cd4E;
address constant CANONICAL_CORI_SUBSCRIPTION = 0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6;


//import {OperatorFilterer} from "./OperatorFilterer.sol";
/**
 * @title  OperatorFilterer
 * @notice Abstract contract whose constructor automatically registers and optionally subscribes to or copies another
 *         registrant's entries in the OperatorFilterRegistry.
 * @dev    This smart contract is meant to be inherited by token contracts so they can use the following:
 *         - `onlyAllowedOperator` modifier for `transferFrom` and `safeTransferFrom` methods.
 *         - `onlyAllowedOperatorApproval` modifier for `approve` and `setApprovalForAll` methods.
 *         Please note that if your token contract does not provide an owner with EIP-173, it must provide
 *         administration methods on the contract itself to interact with the registry otherwise the subscription
 *         will be locked to the options set during construction.
 */

abstract contract OperatorFilterer {
    /// @dev Emitted when an operator is not allowed.
    error OperatorNotAllowed(address operator);

    IOperatorFilterRegistry public constant OPERATOR_FILTER_REGISTRY =
        IOperatorFilterRegistry(CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS);

    /// @dev The constructor that is called when the contract is being deployed.
    constructor(address subscriptionOrRegistrantToCopy, bool subscribe) {
        // If an inheriting token contract is deployed to a network without the registry deployed, the modifier
        // will not revert, but the contract will need to be registered with the registry once it is deployed in
        // order for the modifier to filter addresses.
        if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
            if (subscribe) {
                OPERATOR_FILTER_REGISTRY.registerAndSubscribe(address(this), subscriptionOrRegistrantToCopy);
            } else {
                if (subscriptionOrRegistrantToCopy != address(0)) {
                    OPERATOR_FILTER_REGISTRY.registerAndCopyEntries(address(this), subscriptionOrRegistrantToCopy);
                } else {
                    OPERATOR_FILTER_REGISTRY.register(address(this));
                }
            }
        }
    }

    /**
     * @dev A helper function to check if an operator is allowed.
     */
    modifier onlyAllowedOperator(address from) virtual {
        // Allow spending tokens from addresses with balance
        // Note that this still allows listings and marketplaces with escrow to transfer tokens if transferred
        // from an EOA.
        if (from != msg.sender) {
            _checkFilterOperator(msg.sender);
        }
        _;
    }

    /**
     * @dev A helper function to check if an operator approval is allowed.
     */
    modifier onlyAllowedOperatorApproval(address operator) virtual {
        _checkFilterOperator(operator);
        _;
    }

    /**
     * @dev A helper function to check if an operator is allowed.
     */
    function _checkFilterOperator(address operator) internal view virtual {
        // Check registry code length to facilitate testing in environments without a deployed registry.
        if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
            // under normal circumstances, this function will revert rather than return false, but inheriting contracts
            // may specify their own OperatorFilterRegistry implementations, which may behave differently
            if (!OPERATOR_FILTER_REGISTRY.isOperatorAllowed(address(this), operator)) {
                revert OperatorNotAllowed(operator);
            }
        }
    }
}


//import "operator-filter-registry/src/DefaultOperatorFilterer.sol";
/**
 * @title  DefaultOperatorFilterer
 * @notice Inherits from OperatorFilterer and automatically subscribes to the default OpenSea subscription.
 * @dev    Please note that if your token contract does not provide an owner with EIP-173, it must provide
 *         administration methods on the contract itself to interact with the registry otherwise the subscription
 *         will be locked to the options set during construction.
 */

abstract contract DefaultOperatorFilterer is OperatorFilterer {
    /// @dev The constructor that is called when the contract is being deployed.
    constructor() OperatorFilterer(CANONICAL_CORI_SUBSCRIPTION, true) {}
}


//import "./common/IAirdrop.sol";
interface IAirdrop {
    //--------------------
    // function
    //--------------------
    function getTotal() external view returns (uint256);
    function getAt( uint256 at ) external view returns (address);
}


//import "./common/IERC721Monitor.sol";
interface IERC721Monitor{
    function onTransfered( address from, address to, uint256 tokenId, uint256 batchSize ) external;
}


//-----------------------------------
// Token(ERC721)
//-----------------------------------
contract Token is Ownable, ERC721, ReentrancyGuard, ERC2981, DefaultOperatorFilterer {
    //--------------------------------------
    // constant
    //--------------------------------------
    // mainnet
    string constant private TOKEN_NAME = "SUKIGAKU";
    string constant private TOKEN_SYMBOL = "SUKIGAKU";
    address constant private OWNER_ADDRESS = 0x9383B5C421C30Cd79a6C5233539779F387e40826;

    uint96 constant private ROYALTY_FEE_NUMERATOR = 1000;    // 1000/10000 = 10%
    uint256 constant private BLOCK_SEC_MARGIN = 30;
    uint256 constant private TOKEN_ID_OFS = 1;

    // enum
    uint256 constant private INFO_SALE_SUSPENDED = 0;
    uint256 constant private INFO_SALE_START = 1;
    uint256 constant private INFO_SALE_END = 2;
    uint256 constant private INFO_SALE_PRICE = 3;
    uint256 constant private INFO_SALE_WHITELISTED = 4;
    uint256 constant private INFO_SALE_USER_MINTED = 5;
    uint256 constant private INFO_SALE_USER_MINTABLE = 6;
    uint256 constant private INFO_MAX = 7;
    uint256 constant private USER_INFO_SALE_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_MINT_LIMIT = INFO_MAX + 3;
    uint256 constant private USER_INFO_MAX = INFO_MAX + 4;

    //--------------------------------------
    // storage
    //--------------------------------------
    address private _manager;

    IAirdrop private _airdrop;
    IERC721Monitor private _monitor;

    string private _base_uri_for_hidden;
    string private _base_uri_for_revealed;
    uint256 private _token_max;
    uint256 private _token_reserved;
    uint256 private _token_airdrop;
    uint256 private _token_mint_limit;
    uint256 private _total_supply;

    // PRIVATE(whitelist)
    bool private _PRIVATE_SALE_is_suspended;
    uint256 private _PRIVATE_SALE_start;
    uint256 private _PRIVATE_SALE_end;
    uint256 private _PRIVATE_SALE_price;
    bytes32 private _PRIVATE_SALE_merkle_root;
    mapping( address => uint256 ) private _PRIVATE_SALE_map_user_minted;

    // PUBLIC
    bool private _PUBLIC_SALE_is_suspended;
    uint256 private _PUBLIC_SALE_start;
    uint256 private _PUBLIC_SALE_end;
    uint256 private _PUBLIC_SALE_price;
    uint256 private _PUBLIC_SALE_mintable;
    mapping( address => uint256 ) private _PUBLIC_SALE_map_user_minted;

    //--------------------------------------------------------
    // [modifier] onlyOwnerOrManager
    //--------------------------------------------------------
    modifier onlyOwnerOrManager() {
        require( msg.sender == owner() || msg.sender == manager(), "caller is not the owner neither manager" );
        _;
    }

    //--------------------------------------------------------
    // constructor
    //--------------------------------------------------------
    constructor() ERC721( TOKEN_NAME, TOKEN_SYMBOL ) {
        if( owner() != OWNER_ADDRESS ){
            transferOwnership( OWNER_ADDRESS );
        }
        _manager = msg.sender;

        _setDefaultRoyalty( OWNER_ADDRESS, ROYALTY_FEE_NUMERATOR );

        //-----------------------
        // mainnet
        //-----------------------
        _airdrop = IAirdrop( 0xa01f756E1cD24a001c8c6ECC62d5E5f3F26390b8 );
        _base_uri_for_hidden = "ipfs://QmU2PZd2K5HCAa2aFKsuByf4WZKigbshfQrJzATDhE3iJm/";
        _token_max = 7000;
        _token_reserved = 50;
        _token_airdrop = 133;
        _token_mint_limit = 15;

        //***********************
        // PRIVATE(whitelist)
        //***********************
        _PRIVATE_SALE_start = 1678359600;               // 2023-03-09 20:00:00(JST)
        _PRIVATE_SALE_end   = 1678446000;               // 2023-03-10 20:00:00(JST)
        _PRIVATE_SALE_price = 14000000000000000;        // 0.014 ETH
        _PRIVATE_SALE_merkle_root = 0xe62c6a4890da808ccc76575d03ad8b2383780515a5095dcf496660e284a2cf3b;
        
        //~~~~~~~~~~~~~~~~~~~~~~~
        // PUBLIC
        //~~~~~~~~~~~~~~~~~~~~~~~
        _PUBLIC_SALE_start = 1678446000;                // 2023-03-10 20:00:00(JST)
        _PUBLIC_SALE_end   = 1679144400;                // 2023-03-18 22:00:00(JST)
        _PUBLIC_SALE_price = 14000000000000000;         // 0.014 ETH
        _PUBLIC_SALE_mintable = 15;                     // 15
    }

    //======================================================================================
    // [public/override] 
    //======================================================================================
    function supportsInterface(bytes4 interfaceId) public view override( ERC721, ERC2981 ) returns (bool) {
        return super.supportsInterface(interfaceId);
    }

    //======================================================================================
    // [external/onlyOwnerOrManager] for ERC2981
    //======================================================================================
    function setDefaultRoyalty( address receiver, uint96 feeNumerator ) external onlyOwnerOrManager { _setDefaultRoyalty( receiver, feeNumerator ); }
    function deleteDefaultRoyalty() external onlyOwnerOrManager { _deleteDefaultRoyalty(); }
    function setTokenRoyalty( uint256 tokenId, address receiver, uint96 feeNumerator ) external onlyOwnerOrManager { _setTokenRoyalty( tokenId, receiver, feeNumerator ); }
    function resetTokenRoyalty( uint256 tokenId ) external onlyOwnerOrManager { _resetTokenRoyalty( tokenId ); }

    //======================================================================================
    // [public/override/onlyAllowedOperatorApproval/onlyAllowedOperator] for OperatorFilter
    //======================================================================================
    function setApprovalForAll(address operator, bool approved) public override onlyAllowedOperatorApproval(operator) { super.setApprovalForAll(operator, approved); }
    function approve(address operator, uint256 tokenId) public override onlyAllowedOperatorApproval(operator) { super.approve(operator, tokenId); }
    function transferFrom(address from, address to, uint256 tokenId) public override onlyAllowedOperator(from) { super.transferFrom(from, to, tokenId); }
    function safeTransferFrom(address from, address to, uint256 tokenId) public override onlyAllowedOperator(from) { super.safeTransferFrom(from, to, tokenId); }
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public override onlyAllowedOperator(from) { super.safeTransferFrom(from, to, tokenId, data); }

    //--------------------------------------------------------
    // [public] manager
    //--------------------------------------------------------
    function manager() public view returns (address) {
        return( _manager );
    }

    //--------------------------------------------------------
    // [external/onlyOwner] setManager
    //--------------------------------------------------------
    function setManager( address target ) external onlyOwner {
        _manager = target;
    }

    //--------------------------------------------------------
    // [external] get
    //--------------------------------------------------------
    function airdrop() external view returns (address) { return( address(_airdrop) ); }
    function monitor() external view returns (address) { return( address(_monitor) ); }

    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 tokenAirdrop() external view returns (uint256) { return( _token_airdrop ); }
    function tokenMintLimit() external view returns (uint256) { return( _token_mint_limit ); }    
    function totalSupply() external view returns (uint256) { return( _total_supply ); }

    //--------------------------------------------------------
    // [external/onlyOwnerOrManager] set
    //--------------------------------------------------------
    function setAirdrop( address target ) external onlyOwnerOrManager { _airdrop = IAirdrop(target); }
    function setMonitor( address target ) external onlyOwnerOrManager { _monitor = IERC721Monitor(target); }

    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 num ) external onlyOwnerOrManager { _token_max = num; }
    function setTokenReserved( uint256 num ) external onlyOwnerOrManager { _token_reserved = num; }
    function setTokenAirdrop( uint256 num ) external onlyOwnerOrManager { _token_airdrop = num; }
    function setTokenMintLimit( uint256 num ) external onlyOwnerOrManager { _token_mint_limit = num; }

    //--------------------------------------------------------
    // [public/override] tokenURI
    //--------------------------------------------------------
    function tokenURI( uint256 tokenId ) public view override returns (string memory) {
        require( _exists( tokenId ), "nonexistent token" );

        if( bytes(_base_uri_for_revealed).length > 0 ){
            return( string( abi.encodePacked( _base_uri_for_revealed, Strings.toString( tokenId ) ) ) );            
        }

        return( string( abi.encodePacked( _base_uri_for_hidden, Strings.toString( tokenId ) ) ) );
    }

    //********************************************************
    // [public] getInfo - PRIVATE(whitelist)
    //********************************************************
    function PRIVATE_SALE_getInfo( address target, uint256 amount, bytes32[] calldata merkleProof ) public view returns (uint256[INFO_MAX] memory) {
        uint256[INFO_MAX] memory arrRet;

        if( _PRIVATE_SALE_is_suspended ){ arrRet[INFO_SALE_SUSPENDED] = 1; }
        arrRet[INFO_SALE_START] = _PRIVATE_SALE_start;
        arrRet[INFO_SALE_END] = _PRIVATE_SALE_end;
        arrRet[INFO_SALE_PRICE] = _PRIVATE_SALE_price;
        if( _checkWhitelisted( target, amount, merkleProof ) ){
            arrRet[INFO_SALE_WHITELISTED] = 1;
            arrRet[INFO_SALE_USER_MINTABLE] = amount;
        }
        arrRet[INFO_SALE_USER_MINTED] = _PRIVATE_SALE_map_user_minted[target];

        return( arrRet );
    }

    //********************************************************
    // [external/onlyOwnerOrManager] write - PRIVATE(whitelist)
    //********************************************************
    function PRIVATE_SALE_suspend( bool flag ) external onlyOwnerOrManager { _PRIVATE_SALE_is_suspended = flag; }
    function PRIVATE_SALE_setStartEnd( uint256 start, uint256 end ) external onlyOwnerOrManager { _PRIVATE_SALE_start = start; _PRIVATE_SALE_end = end; }
    function PRIVATE_SALE_setPrice( uint256 price ) external onlyOwnerOrManager { _PRIVATE_SALE_price = price; }
    function PRIVATE_SALE_setMerkleRoot( bytes32 root ) external onlyOwnerOrManager { _PRIVATE_SALE_merkle_root = root; }