contract ProjectKoala is ERC721, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;

    bytes32 public merkleRoot;

    address proxyRegistryAddress = 0x58807baD0B376efc12F5AD86aAc70E78ed67deaE;

    string BASE_URI = "https://projectkoalanft.com/assets/";

    bool public IS_SALE_ACTIVE = false;
    bool public IS_PRESALE_ACTIVE = true;

    uint256 constant TOTAL_SUPPLY = 3334;

    uint256 MINT_PRICE = 0.069 ether;

    uint256 constant MAX_ALLOWED_MINT = 10;

    mapping(address => uint256) addressToMintCount;

    constructor(
        string memory name,
        string memory symbol,
        bytes32 _merkleRoot
    ) ERC721(name, symbol) {
        merkleRoot = _merkleRoot;
    }

    function setProxyOpensea(address _address) external onlyOwner {
        proxyRegistryAddress = _address;
    }

    function setMintPrice(uint256 _mintPrice) external onlyOwner {
        MINT_PRICE = _mintPrice;
    }

    function setMerkleRoot(bytes32 _merkleroot) public onlyOwner {
        merkleRoot = _merkleroot;
    }

    function setBaseURI(string memory newUri) public onlyOwner {
        BASE_URI = newUri;
    }

    function togglePublicSale() public onlyOwner {
        IS_SALE_ACTIVE = !IS_SALE_ACTIVE;
    }

    function togglePreSale() public onlyOwner {
        IS_PRESALE_ACTIVE = !IS_PRESALE_ACTIVE;
    }

    function ownerMint(uint256 numberOfTokens) public onlyOwner {
        uint256 current = _tokenIdCounter.current();
        require(
            current + numberOfTokens < TOTAL_SUPPLY,
            "Total supply exeeded"
        );

        for (uint256 i = 0; i < numberOfTokens; i++) {
            mintInternal();
        }
    }

    function preSaleMint(
        uint256 numberOfTokens,
        uint256 amount,
        bytes32[] calldata proof
    ) public payable {
        require(IS_PRESALE_ACTIVE, "Pre-sale is not active");
        require(
            msg.value >= numberOfTokens * MINT_PRICE,
            "Not enough ethers sent"
        );
        string memory allowedCount = Strings.toString(amount);
        require(
            _verify(proof, msg.sender, allowedCount),
            "Invalid merkle proof"
        );

        uint256 current = _tokenIdCounter.current();

        require(
            current + numberOfTokens < TOTAL_SUPPLY,
            "Exceeds total supply"
        );
        require(
            addressToMintCount[msg.sender] + numberOfTokens <= amount,
            "Exceeds total allowed mint for account"
        );

        addressToMintCount[msg.sender] += numberOfTokens;

        for (uint256 i = 0; i < numberOfTokens; i++) {
            mintInternal();
        }
    }

    function publicSaleMint(uint256 numberOfTokens) public payable {
        require(IS_SALE_ACTIVE, "Public sale is not active ");
        require(numberOfTokens <= MAX_ALLOWED_MINT, "Too many requested");
        require(
            msg.value >= numberOfTokens * MINT_PRICE,
            "Not enough ethers sent"
        );

        uint256 current = _tokenIdCounter.current();

        require(
            current + numberOfTokens < TOTAL_SUPPLY,
            "Exceeds total supply"
        );

        for (uint256 i = 0; i < numberOfTokens; i++) {
            mintInternal();
        }
    }

    function mintInternal() internal {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _mint(msg.sender, tokenId);
    }

    function withdrawAll() public onlyOwner {
        uint256 balance = address(this).balance;
        require(balance > 0);

        payable(msg.sender).transfer(balance);
    }

    function totalSupply() public view returns (uint256) {
        return _tokenIdCounter.current();
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721)
        returns (string memory)
    {
        require(_exists(tokenId), "Cannot query non-existent token");
        return
            string(
                abi.encodePacked(BASE_URI, Strings.toString(tokenId), ".json")
            );
    }

    function tokensOfOwner(
        address _owner,
        uint256 startId,
        uint256 endId
    ) external view returns (uint256[] memory) {
        uint256 tokenCount = balanceOf(_owner);
        if (tokenCount == 0) {
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](tokenCount);
            uint256 index = 0;

            for (uint256 tokenId = startId; tokenId < endId; tokenId++) {
                if (index == tokenCount) break;

                if (ownerOf(tokenId) == _owner) {
                    result[index] = tokenId;
                    index++;
                }
            }

            return result;
        }
    }

    /**
     * Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-less listings.
     */
    function isApprovedForAll(address owner, address operator)
        public
        view
        override
        returns (bool)
    {
        // Whitelist OpenSea proxy contract for easy trading.
        ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
        if (address(proxyRegistry.proxies(owner)) == operator) {
            return true;
        }

        return super.isApprovedForAll(owner, operator);
    }

    function _verify(
        bytes32[] memory proof,
        address user,
        string memory amount
    ) internal view returns (bool) {
        bytes32 trueLeaf = keccak256(
            abi.encodePacked(user, abi.encodePacked(":", amount))
        );
        return MerkleProof.verify(proof, merkleRoot, trueLeaf);
    }
}
function isApprovedForAll(address owner, address operator)
    public
    view
    override
    returns (bool)
{
    // Whitelist OpenSea proxy contract for easy trading.
    ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
    if (address(proxyRegistry.proxies(owner)) == operator) {
        return true;
    }

    return super.isApprovedForAll(owner, operator);
}

/**
 * Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-less listings.
 */