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

    address payable private _PaymentAddress1 = payable(0x7d0F904ccCc7fA01383d5cEd1944774BAc3508b3);
    address payable private _PaymentAddress2 = payable(0x39968b14C7A25ce05072dafe2caa4D6a3Bb6b839);

    uint256 public MAX_SUPPLY = 9999;
    uint256 public PRESALE_PRICE = 0.25 ether; // 0.25 ETH
    uint256 public PUBLIC_PRICE = 0.25 ether; // 0.25 ETH
    uint256 private REVEAL_DELAY = 144 hours;
    uint256 private PRESALE_HOURS = 24 hours;
    uint256 public PRESALE_MINT_LIMIT = 2;
    uint256 public PUBLIC_MINT_LIMIT = 1;

    mapping(address => uint256) _mappingPresaleMintCount;
    mapping(address => uint256) _mappingPublicMintCount;

    bytes32 private _whitelistRoot;

    uint256 private _activeDateTime = 1646092800; // Date and time (GMT): Tuesday, March 1, 2022 12:00:00 AM

    string private _tokenBaseURI = "";
    string private _revealURI = "";

    Counters.Counter private _publicCounter;

    constructor() ERC721("TheStarslab", "TSL") {}

    function setPaymentAddress(address paymentAddress1, address paymentAddress2) external onlyOwner {
        _PaymentAddress1 = payable(paymentAddress1);
        _PaymentAddress2 = payable(paymentAddress2);
    }

    function setActiveDateTime(uint256 activeDateTime) external onlyOwner {
        _activeDateTime = activeDateTime;
    }

    function setRevealDelay(uint256 revealDelay) external onlyOwner {
        REVEAL_DELAY = revealDelay;
    }

    function setPresaleHours(uint256 presaleHours) external onlyOwner {
        PRESALE_HOURS = presaleHours;
    }

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

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

    function setPurchaseLimit( uint256 presaleMintLimit, uint256 publicMintLimit) external onlyOwner {
        PRESALE_MINT_LIMIT = presaleMintLimit;
        PUBLIC_MINT_LIMIT = publicMintLimit;
    }

    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 setWhiteListRoot(bytes32 root) external onlyOwner {
        _whitelistRoot = root;
    }

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

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

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

        if (msg.sender != owner()) {
            require(isWhiteListed(toLeaf(msg.sender, index, amount), proof), "Invalid proof");
            
            require(block.timestamp > _activeDateTime - PRESALE_HOURS,"Contract is not active for presale");

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

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

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

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

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

        if (msg.sender != owner()) {
            require( block.timestamp > _activeDateTime, "Contract is not active");

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

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

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

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

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

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

        return _revealURI;
    }

    function withdraw() external onlyOwner {
        uint256 balance = address(this).balance;
        uint256 amount1 = (balance * 9500) / 10000; // 95%
        uint256 amount2 = (balance * 500) / 10000; // 5%
        _PaymentAddress1.transfer(amount1);
        _PaymentAddress2.transfer(amount2);
    }
}
function toLeaf(address account, uint256 index, uint256 amount) internal pure returns (bytes32) {
    return keccak256(abi.encodePacked(index, account, amount));
}

// Generate the leaf node (just the hash of tokenID concatenated with the account address)