contract EternalCollections is ERC721Enumerable, Ownable {
    using Counters for Counters.Counter;
    using Strings for uint256;

    address payable private _PaymentAddress = payable(0xe9E7aEb2dC214C8Fe354feb1ECd61871Ea201539);

    uint256 public MAX_SUPPLY = 3333;
    uint256 private PRESALE_PRICE = 0.08 ether;
    uint256 private PUBLIC_PRICE = 0.128 ether;
    
    uint256 private PRESALE1_MINT_LIMIT = 35;
    uint256 private PRESALE2_MINT_LIMIT = 5;
    uint256 private PUBLIC_MINT_LIMIT = MAX_SUPPLY;
    uint256 private PRESALE1_PURCHASE_LIMIT = 35;
    uint256 private PRESALE2_PURCHASE_LIMIT = 5;
    uint256 private PUBLIC_PURCHASE_LIMIT = 10;
    uint256 private POOL_PERCENT = 5;
    
    bytes32 private _whitelist1Root;
    bytes32 private _whitelist2Root;

    mapping(address => uint256) private _mappingPresale1MintCount;
    mapping(address => uint256) private _mappingPresale2MintCount;
    mapping(address => uint256) private _mappingPublicMintCount;

    uint256 private _presale1DateTime = 1645459200; // (GMT): Monday, February 21, 2022 4:00:00 PM
    
    uint256 private _presale2DateTime = 1647786120; // (GMT): Sunday, March 20, 2022 2:22:00 PM
    
    uint256 private _publicDateTime = 1647958920; // (GMT): Tuesday, March 22, 2022 2:22:00 PM
    uint256 private _revealDateTime = 1711117320;
    
    string private _tokenBaseURI = "";
    string private _revealURI = "";
    
    Counters.Counter internal _publicCounter;

    constructor() ERC721("Eternal Collections", "EC-Spring") {}

    function setPaymentAddress(address paymentAddress, uint256 pool) external onlyOwner {
        _PaymentAddress = payable(paymentAddress);
        POOL_PERCENT = pool;
    }

    function setPresale1DateTime(uint256 presale1DateTime) external onlyOwner {
        _presale1DateTime = presale1DateTime;
    }

    function setPresale2DateTime(uint256 presale2DateTime) external onlyOwner {
        _presale2DateTime = presale2DateTime;
    }

    function setPublicDateTime(uint256 publicDateTime, uint256 revealDateTime) external onlyOwner {
        _publicDateTime = publicDateTime;
        _revealDateTime = revealDateTime;
    }

    function setWhiteList1Root(bytes32 root1) external onlyOwner {
        _whitelist1Root = root1;
    }

    function setWhiteList2Root(bytes32 root2) external onlyOwner {
        _whitelist2Root = root2;
    }

    // Verify that a given leaf is in the tree.
    function isWhiteListed1(bytes32 _leafNode, bytes32[] memory proof) public view returns (bool) {
        return MerkleProof.verify(proof, _whitelist1Root, _leafNode);
    }

    function isWhiteListed2(bytes32 _leafNode, bytes32[] memory proof) public view returns (bool) {
        return MerkleProof.verify(proof, _whitelist2Root, _leafNode);
    }

    // Generate the leaf node (just the hash of tokenID concatenated with the account address)
    function toLeaf(address account, uint256 index, uint256 amount) public pure returns (bytes32) {
        return keccak256(abi.encodePacked(index, account, amount));
    }

    function setMintPrice(uint256 presaleMintPrice, uint256 publicMintPrice) external onlyOwner {
        PRESALE_PRICE = presaleMintPrice;
        PUBLIC_PRICE = publicMintPrice;
    }

    function setMaxLimit(uint256 maxLimit) external onlyOwner {
        MAX_SUPPLY = maxLimit;
    }

    function setMintLimit(uint256 presale1MintLimit, uint256 presale2MintLimit, uint256 publicMintLimit) external onlyOwner {
        PRESALE1_MINT_LIMIT = presale1MintLimit;
        PRESALE2_MINT_LIMIT = presale2MintLimit;
        PUBLIC_MINT_LIMIT = publicMintLimit;
    }

    function setPurchaseLimit(uint256 presale1PurchaseLimit, uint256 presale2PurchaseLimit, uint256 publicPurchaseLimit) external onlyOwner {
        PRESALE1_PURCHASE_LIMIT = presale1PurchaseLimit;
        PRESALE2_PURCHASE_LIMIT = presale2PurchaseLimit;
        PUBLIC_PURCHASE_LIMIT = publicPurchaseLimit;
    }

    function setRevealURI(string memory revealURI) external onlyOwner {
        _revealURI = revealURI;
    }

    function setBaseURI(string memory baseURI) external onlyOwner {
        _tokenBaseURI = baseURI;
    }

    function airdrop(address[] memory airdropAddress, uint256 numberOfTokens) external onlyOwner {
        for (uint256 k = 0; k < airdropAddress.length; k++) {
            for (uint256 i = 0; i < numberOfTokens; i++) {
                uint256 tokenId = _publicCounter.current();

                if (_publicCounter.current() < MAX_SUPPLY) {
                    _publicCounter.increment();
                    if (!_exists(tokenId)) _safeMint(airdropAddress[k], tokenId);
                }
            }
        }
    }

    function airdropSpecial(address to, uint256 tokenId) external onlyOwner {
        require(!_exists(tokenId), "Token already exist");
        if (!_exists(tokenId)) _safeMint(to, tokenId);
    }

    function purchase(uint256 numberOfTokens) external payable {
        require(_publicCounter.current() + numberOfTokens <= MAX_SUPPLY,"Purchase would exceed MAX_SUPPLY");

        if (msg.sender != owner()) {

            require(_publicDateTime <= block.timestamp,"Public Mint is not activated.");

            require(numberOfTokens <= PUBLIC_PURCHASE_LIMIT,"Can only mint up to purchase limit");

            require(PUBLIC_PRICE * numberOfTokens <= msg.value,"ETH amount is not sufficient");

            _mappingPublicMintCount[msg.sender] = _mappingPublicMintCount[msg.sender] + numberOfTokens;
            
            require(_mappingPublicMintCount[msg.sender] <= PUBLIC_MINT_LIMIT,"Overflow for PUBLIC_MINT_LIMIT");

            if (POOL_PERCENT > 0) {
                uint256 poolAmount = (msg.value * POOL_PERCENT) / 100;
                _PaymentAddress.transfer(msg.value - poolAmount);
            } else {
                _PaymentAddress.transfer(msg.value);
            }
        }

        for (uint256 i = 0; i < numberOfTokens; i++) {
            uint256 tokenId = _publicCounter.current();

            if (_publicCounter.current() < MAX_SUPPLY) {
                _publicCounter.increment();
                if (!_exists(tokenId)) _safeMint(msg.sender, tokenId);
            }
        }
    }

    function presaleRound1(uint256 numberOfTokens, uint256 index, uint256 amount, bytes32[] calldata proof) external payable {
        require(_publicCounter.current() + numberOfTokens <= MAX_SUPPLY,"Purchase would exceed MAX_SUPPLY");

        if (msg.sender != owner()) {
            
            require(isWhiteListed1(toLeaf(msg.sender, index, amount), proof), "Invalid proof");

            require(_presale1DateTime <= block.timestamp,"Presale Round1 is not activated.");

            require(numberOfTokens <= PRESALE1_PURCHASE_LIMIT,"Can only mint up to purchase limit");

            require(PRESALE_PRICE * numberOfTokens <= msg.value,"ETH amount is not sufficient");

            _mappingPresale1MintCount[msg.sender] = _mappingPresale1MintCount[msg.sender] + numberOfTokens;
            
            require(_mappingPresale1MintCount[msg.sender] <= PRESALE1_MINT_LIMIT,"Overflow for PRESALE1_MINT_LIMIT");

            if (POOL_PERCENT > 0) {
                uint256 poolAmount = (msg.value * POOL_PERCENT) / 100;
                _PaymentAddress.transfer(msg.value - poolAmount);
            } else {
                _PaymentAddress.transfer(msg.value);
            }
        }
        for (uint256 i = 0; i < numberOfTokens; i++) {
            uint256 tokenId = _publicCounter.current();

            if (_publicCounter.current() < MAX_SUPPLY) {
                _publicCounter.increment();
                if (!_exists(tokenId)) _safeMint(msg.sender, tokenId);
            }
        }
    }

    function presaleRound2(uint256 numberOfTokens, uint256 index, uint256 amount, bytes32[] calldata proof) external payable {
        require(_publicCounter.current() + numberOfTokens <= MAX_SUPPLY,"Purchase would exceed MAX_SUPPLY");

        if (msg.sender != owner()) {
                
            require(isWhiteListed2(toLeaf(msg.sender, index, amount), proof), "Invalid proof");

            require(_presale2DateTime <= block.timestamp,"Presale Round2 is not activated.");

            require(numberOfTokens <= PRESALE2_PURCHASE_LIMIT,"Can only mint up to purchase limit");

            require(PRESALE_PRICE * numberOfTokens <= msg.value,"ETH amount is not sufficient");

            _mappingPresale2MintCount[msg.sender] = _mappingPresale2MintCount[msg.sender] + numberOfTokens;
            
            require(_mappingPresale2MintCount[msg.sender] <= PRESALE2_MINT_LIMIT,"Overflow for PRESALE2_MINT_LIMIT");

            if (POOL_PERCENT > 0) {
                uint256 poolAmount = (msg.value * POOL_PERCENT) / 100;
                _PaymentAddress.transfer(msg.value - poolAmount);
            } else {
                _PaymentAddress.transfer(msg.value);
            }
        }

        for (uint256 i = 0; i < numberOfTokens; i++) {
            uint256 tokenId = _publicCounter.current();

            if (_publicCounter.current() < MAX_SUPPLY) {
                _publicCounter.increment();
                if (!_exists(tokenId)) _safeMint(msg.sender, tokenId);
            }
        }
    }

    function tokenURI(uint256 tokenId) public view override(ERC721) returns (string memory) {
        require(_exists(tokenId), "Token does not exist");

        if (_revealDateTime < block.timestamp) {
            return string(abi.encodePacked(_tokenBaseURI, tokenId.toString()));
        }

        return _revealURI;
    }

    function processRound() external onlyOwner {
        uint256 balance = address(this).balance;

        payable(msg.sender).transfer(balance);
    }
}
function toLeaf(address account, uint256 index, uint256 amount) public pure returns (bytes32) {
        return keccak256(abi.encodePacked(index, account, amount));
    }
// Generate the leaf node (just the hash of tokenID concatenated with the account address)