contract DateFactory is ERC721, ERC721Enumerable, Ownable {
    // Metadata base URI
    string internal baseURI;
    // Treasury wallet
    address payable treasury;
    // Minter: Ascension portal contract address which will mint resurrected humans
    address minter;

    // Switch to enable burn
    bool public isBurnEnabled;
    // Sale switches
    bool public isPublicSaleActive;
    bool public isPrivateSaleActive;

    // Each user can't mint more than 10 NFTs.
    uint256 public constant MAX_PER_WALLET = 10;
    // Price per NFT: 0.05 ETH
    uint256 private constant MINT_PRICE = 50000000000000000;
    // Maximum deth counts
    uint256 private constant TOTAL_FIRST_DETHS = 3334;
    uint256 private constant TOTAL_RESURRECTED_DETHS = 530;
    // Mint counter
    uint256 public firstDethCounter = 0;
    // Token counter
    using Counters for Counters.Counter;
    Counters.Counter private tokenIdCounter;
    Counters.Counter private resurrectedDethCounter;

    // Ledger of users added to whitelist and their mint count
    mapping(address => uint256) public whitelist;
    // Ledger of NFTs minted by each unique wallet address.
    mapping(address => uint256) public userMintCount;

    // Mint event
    event Mint(address indexed account, uint256 indexed tokenId);
    // Ressurrected event
    event ResurrectMint(
        address indexed account,
        uint256 indexed prevTokenId,
        uint256 indexed currentTokenId
    );

    constructor(string memory _uri, address payable _treasury)
        ERC721("IndefiniteGames", "IGI")
    {
        baseURI = _uri;
        treasury = _treasury;
        _mintDates(treasury, 1);
    }

    /**
     * @dev Allows withdrawing the Ether in the contract to the treasury account.
     */
    function withdraw() external {
        require(
            msg.sender == owner() || msg.sender == treasury,
            "Caller is not owner or treasury"
        );
        payable(treasury).transfer(address(this).balance);
    }

    /**
     * @dev Sets the base URI for the API that provides the NFT data.
     */
    function setBaseURI(string memory _uri) external onlyOwner {
        baseURI = _uri;
    }

    /**
     * @dev Public sale switch
     */
    function setPublicSaleState(bool _state) external onlyOwner {
        isPublicSaleActive = _state;
    }

    /**
     * @dev Private sale switch
     */
    function setPrivateSaleState(bool _state) external onlyOwner {
        isPrivateSaleActive = _state;
    }

    /**
     * @dev Enable/Disable burning.
     */
    function setIsBurnEnabled(bool _isBurnEnabled) external onlyOwner {
        isBurnEnabled = _isBurnEnabled;
    }

    /**
     * @dev Add to Whitelist
     */
    function addWhitelist(address[] memory _adds, uint256 _count)
        external
        onlyOwner
    {
        require(_count > 0, "Count can not be zero");
        for (uint256 i = 0; i < _adds.length; i++) {
            whitelist[_adds[i]] = _count;
        }
    }

    /**
     * @dev Remove from Whitelist
     */
    function removeWhitelist(address[] memory _adds) external onlyOwner {
        for (uint256 i = 0; i < _adds.length; i++) {
            whitelist[_adds[i]] = 0;
        }
    }

    /**
     * @dev Sets minter: only minter will be able to mint resurrected deths
     */
    function setMinter(address _minter) external onlyOwner {
        minter = _minter;
    }

    /**
     * @dev Claim dates as per the whitelisted user limit
     */
    function privateMintDates(uint256 _tokensCount) external payable {
        require(isPrivateSaleActive, "Sale is not active at the moment");
        require(whitelist[msg.sender] != 0, "Address not found in whitelist");
        _checkDateSale(_tokensCount, whitelist[msg.sender]);
        _mintDates(msg.sender, _tokensCount);
    }

    /**
     * @dev Claim up to 10 dates at once
     */
    function publicMintDates(uint256 _tokensCount) external payable {
        require(isPublicSaleActive, "Sale is not active at the moment");
        _checkDateSale(_tokensCount, MAX_PER_WALLET);
        _mintDates(msg.sender, _tokensCount);
    }

    /**
     * @dev Allows ascension portal (minter) to mint resurrected human
     */
    function mintResurrected(address _to, uint256 _prevTokenId) external {
        require(msg.sender == minter, "Caller is not a minter");
        require(
            resurrectedDethCounter.current() < TOTAL_RESURRECTED_DETHS,
            "No dates left to be claimed"
        );
        tokenIdCounter.increment();
        _safeMint(_to, tokenIdCounter.current());
        emit ResurrectMint(_to, _prevTokenId, tokenIdCounter.current());
        resurrectedDethCounter.increment();
    }

    /**
     * @dev Allows burning tokens when burning is enabled.
     */
    function burn(uint256 tokenId) external {
        require(isBurnEnabled, "Burning disabled");
        require(
            _isApprovedOrOwner(msg.sender, tokenId),
            "not owner nor approved"
        );
        _burn(tokenId);
    }

    /**
     * @dev Gets the count of Date Mints left for a given account
     */
    function getDatesLeft(address _account) external view returns (uint256) {
        uint256 datesLeft;
        if (isPrivateSaleActive) {
            datesLeft = whitelist[_account] - userMintCount[_account];
        } else if (isPublicSaleActive) {
            datesLeft = MAX_PER_WALLET - userMintCount[_account];
        }
        return datesLeft;
    }

    /**
     * @dev Gets the list of token IDs of the requested owner.
     */
    function tokensOfOwner(address _owner)
        external
        view
        returns (uint256[] memory)
    {
        uint256 tokenCount = balanceOf(_owner);
        uint256[] memory result = new uint256[](tokenCount);
        for (uint256 index = 0; index < tokenCount; index++) {
            result[index] = tokenOfOwnerByIndex(_owner, index);
        }
        return result;
    }

    /**
     * @dev See {IERC721Enumerable}.
     */
    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721Enumerable)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }

    /**
     * @dev Checking all the requirements needed for minting
     */
    function _checkDateSale(uint256 _tokensCount, uint256 maxMint)
        internal
        view
    {
        require(
            tx.origin == msg.sender,
            "The caller can only be a user and not a contract"
        );
        require(_tokensCount > 0, "Can't mint 0 dates");
        require(
            firstDethCounter < TOTAL_FIRST_DETHS,
            "No dates left to be claimed"
        );
        require(
            (firstDethCounter + _tokensCount) <= TOTAL_FIRST_DETHS,
            "Not enough dates to be claimed"
        );
        require(
            userMintCount[msg.sender] + _tokensCount <= maxMint,
            "Can not mint more than alloted dates"
        );
        require(
            msg.value >= MINT_PRICE * _tokensCount,
            "Not enough Ether to claim the dates"
        );
    }

    /**
     * @dev Internal function to mint dates
     */
    function _mintDates(address _to, uint256 _tokensCount) internal {
        for (uint256 i = 0; i < _tokensCount; i++) {
            tokenIdCounter.increment();
            _safeMint(_to, tokenIdCounter.current());
            emit Mint(_to, tokenIdCounter.current());
        }
        userMintCount[_to] += _tokensCount;
        firstDethCounter += _tokensCount;
    }

    /**
     * @dev See {ERC721}.
     */
    function _baseURI() internal view virtual override returns (string memory) {
        return baseURI;
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal override(ERC721, ERC721Enumerable) {
        super._beforeTokenTransfer(from, to, tokenId);
    }
}
function withdraw() external {
    require(
        msg.sender == owner() || msg.sender == treasury,
        "Caller is not owner or treasury"
    );
    payable(treasury).transfer(address(this).balance);
}

/**
 * @dev Allows withdrawing the Ether in the contract to the treasury account.
 */