contract KoalafiedCommunity is ERC721, Ownable {
    using Strings for uint256;
    using Counters for Counters.Counter;

    Counters.Counter private supply;

    string public uriPrefix = "";
    string public uriSuffix = ".json";
    string public hiddenMetadataUri;

    uint256 public cost = 0.033 ether;
    uint256 public costWhitelist = 0 ether;
    uint256 public maxSupply = 3333;
    uint256 public maxMintAmountPerTx = 20;
    uint256 public maxMintAmountPerTxforWL = 1;
    uint256 public maxMintAmountPerWallet = 40;
    uint256 public maxMintAmountPerWalletforWL = 1;

    bool public paused = false;
    bool public isWhiteListActive = true;
    bool public revealed = false;

    mapping (address => uint) tokenAmountofWallet;

    bytes32 public merkleRoot = 0xc3ac6d8682d54ac01c0aff971efc933ff8d13e6451d51007c5b32ba1af9feb35;

    constructor() ERC721("Koalafied Community", "KOCO") {
        setHiddenMetadataUri("ipfs://QmPnAYFEBWCwWWgEemhGT9uwafvAq5a74C6ogxrsxYsgQG/hidden.json");
    }

    //mint compliances
    modifier mintCompliance(uint256 _mintAmount) {
        require(supply.current() + _mintAmount <= maxSupply, "Exceeded Max Supply!");
        _;
    }
    
    //public mint function
    function mint(uint256 _mintAmount) public payable mintCompliance(_mintAmount) {
        require(!paused, "The contract is paused!");
        require(!isWhiteListActive, "Whitelist minting is still active!");

        if(msg.sender != owner()) {
            require(_mintAmount > 0 && _mintAmount <= maxMintAmountPerTx, "Exceeded Allowed Amount per TX!");
            require(tokenAmountofWallet[msg.sender] + _mintAmount <= maxMintAmountPerWallet, "Exceeded Allowed Amount per Address!");
            require(msg.value >= cost * _mintAmount, "Insufficient funds!");
        }

        tokenAmountofWallet[msg.sender] += _mintAmount;
        _mintLoop(msg.sender, _mintAmount);
    }

    // whitelist mint function
    function whiteListMint(uint256 _mintAmount, bytes32[] calldata proof) public payable mintCompliance(_mintAmount) {
        require(!paused, "The contract is paused!");
        require(isWhiteListActive, "Whitelist minting isn't active anymore!");
        require(
            MerkleProof.verify(proof, merkleRoot, generateMerkleLeaf(msg.sender)),
            "User is not allowed to mint during this phase!"
        );

        if(msg.sender != owner()) {
            require(_mintAmount > 0 && _mintAmount <= maxMintAmountPerTxforWL, "Exceeded Allowed Amount per TX!");
            require(tokenAmountofWallet[msg.sender] + _mintAmount <= maxMintAmountPerWalletforWL, "Exceeded Allowed Amount per Address!");
            require(msg.value >= costWhitelist * _mintAmount, "Insufficient funds!");
        }

        tokenAmountofWallet[msg.sender] += _mintAmount;
        _mintLoop(msg.sender, _mintAmount);
    }

    //freemint function
    function mintForAddress(uint8 _mintAmount, address _receiver) public mintCompliance(_mintAmount) onlyOwner {
        _mintLoop(_receiver, _mintAmount);
    }

    //safemint function
    function _mintLoop(address _receiver, uint256 _mintAmount) internal {
        for (uint256 i = 0; i < _mintAmount; i++) {
            supply.increment();
            _safeMint(_receiver, supply.current());
        }
    }

    //get function for current token amount of caller
    function getCurrentMintCount(address _account) public view returns (uint256) {
        return tokenAmountofWallet[_account];
    }

    //supply check
    function totalSupply() public view returns (uint256) {
        return supply.current();
    }

    //merkleleaf generation
    function generateMerkleLeaf(address account) internal pure returns(bytes32){
        return keccak256(abi.encodePacked(account));
    }

    //set new merkle root function
    function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner {
        merkleRoot = _merkleRoot;
    }

    //call contract owner & id owner function
    function walletOfOwner(address _owner)
        public
        view
        returns (uint256[] memory)
    {
        uint256 ownerTokenCount = balanceOf(_owner);
        uint256[] memory ownedTokenIds = new uint256[](ownerTokenCount);
        uint256 currentTokenId = 1;
        uint256 ownedTokenIndex = 0;

        while (ownedTokenIndex < ownerTokenCount && currentTokenId <= maxSupply) {
            address currentTokenOwner = ownerOf(currentTokenId);
                if (currentTokenOwner == _owner) {
                    ownedTokenIds[ownedTokenIndex] = currentTokenId;
                    ownedTokenIndex++;
                }

            currentTokenId++;
        }

        return ownedTokenIds;
    }

    //call URL of token function
    function tokenURI(uint256 _tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        require(
            _exists(_tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );

        if (revealed == false) {
            return hiddenMetadataUri;
        }

        string memory currentBaseURI = _baseURI();
            return bytes(currentBaseURI).length > 0
                ? string(abi.encodePacked(currentBaseURI, _tokenId.toString(), uriSuffix))
                : "";
    }

    //contract enablement functions for white-list
    function setIsWhiteListActive(bool _isWhiteListActive) external onlyOwner {
        isWhiteListActive = _isWhiteListActive;
    }

    //contract enablement functions for all
    function setPaused(bool _state) public onlyOwner {
        paused = _state;
    }

    //call hidden metadata functions
    function setHiddenMetadataUri(string memory _hiddenMetadataUri) public onlyOwner {
        hiddenMetadataUri = _hiddenMetadataUri;
    }

    //call URL function
    function _baseURI() internal view virtual override returns (string memory) {
        return uriPrefix;
    }

    //reveal metadata function
    function setRevealed(bool _state) public onlyOwner {
        revealed = _state;
    }

    //set new cost function
    function setCost(uint256 _cost) public onlyOwner {
        cost = _cost;
    }

    //set new cost for whitelist function
    function setcostWhitelist(uint256 _costWhitelist) public onlyOwner {
        costWhitelist = _costWhitelist;
    }

    //set new amount per wallet function
    function setmaxMintAmountPerWallet(uint256 _maxMintAmountPerWallet) public onlyOwner {
        maxMintAmountPerWallet = _maxMintAmountPerWallet;
    }
    
    //set new amount per transaction function
    function setmaxMintAmountPerTx(uint256 _maxMintAmountPerTx) public onlyOwner {
        maxMintAmountPerTx = _maxMintAmountPerTx;
    }

    //set new amount per wallet for whitelist function
    function setmaxMintAmountPerWalletforWL(uint256 _maxMintAmountPerWalletforWL) public onlyOwner {
        maxMintAmountPerWalletforWL = _maxMintAmountPerWalletforWL;
    }
    
    //set new amount per transaction for whitelist function
    function setmaxMintAmountPerTxforWL(uint256 _maxMintAmountPerTxforWL) public onlyOwner {
        maxMintAmountPerTxforWL = _maxMintAmountPerTxforWL;
    }

    //set new URL prefix function
    function setUriPrefix(string memory _uriPrefix) public onlyOwner {
        uriPrefix = _uriPrefix;
    }

    //set new URL suffix function
    function setUriSuffix(string memory _uriSuffix) public onlyOwner {
        uriSuffix = _uriSuffix;
    }

    //withdraw function
    function withdraw() public onlyOwner {
        //50% goes to community wallet
        (bool hs, ) = payable(0xfc2f6C88Da2045846EbE84ede4dA3b3D9da1B87F).call{value: address(this).balance * 50 / 100}("");
        require(hs);
        //rest to team wallet
        (bool os, ) = payable(owner()).call{value: address(this).balance}("");
        require(os);
    }
}
function _baseURI() internal view virtual override returns (string memory) {
        return uriPrefix;
    }
//call URL function