contract GuardianKongz is ERC721Enumerable, ReentrancyGuard, Ownable {
    using Strings for uint256;
    
    string public baseURI;
    string public baseExtension = ".json";

    bool public presaleMintIsActive = false;
    bool public mintIsActive = false;

    uint256 public maxSupply = 5000;
    uint256 public maxMintAmount = 2;
    uint256 public mintPrice = 80000000000000000; // 0.08 ETH
    uint256 public presaleMintPrice = 60000000000000000; // 0.06 ETH

    address public signatureAddress;

    // constructor
    constructor(
    string memory _name,
    string memory _symbol,
    string memory _initBaseURI
    ) ERC721(_name, _symbol) {
        setBaseURI(_initBaseURI);
    }

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

    // public
    function walletOfOwner(address _owner)
        public
        view
        returns (uint256[] memory)
    {
        uint256 ownerTokenCount = balanceOf(_owner);
        uint256[] memory tokenIds = new uint256[](ownerTokenCount);
        for (uint256 i; i < ownerTokenCount; i++) {
        tokenIds[i] = tokenOfOwnerByIndex(_owner, i);
        }
        return tokenIds;
    }

    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        require( _exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        string memory currentBaseURI = _baseURI();
        return bytes(currentBaseURI).length > 0
            ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension))
            : "";
    }

    function setMintCost(uint256 _newPrice) public onlyOwner() {
        mintPrice = _newPrice;
    }

     function setPresaleMintCost(uint256 _newPrice) public onlyOwner() {
        presaleMintPrice = _newPrice;
    }

    function setmaxMintAmount(uint256 _newmaxMintAmount) public onlyOwner() {
        maxMintAmount = _newmaxMintAmount;
    }

    function setBaseURI(string memory _newBaseURI) public onlyOwner {
        baseURI = _newBaseURI;
    }

    function setBaseExtension(string memory _newBaseExtension) public onlyOwner {
        baseExtension = _newBaseExtension;
    }

    function setSignatureAddress(address _importantAddress) public onlyOwner {
        signatureAddress = _importantAddress;
    }

    function flipMintState() public onlyOwner {
        mintIsActive = !mintIsActive;
    }

    function flipPresaleMintState() public onlyOwner {
        presaleMintIsActive = !presaleMintIsActive;
    }

    function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        payable(_msgSender()).transfer(balance);
    }

    // mint
    function devMint(uint256 numberOfTokens) public onlyOwner {
        require(totalSupply() + numberOfTokens <= maxSupply, "Purchase would exceed max supply.");
        
        for (uint i = 0; i < numberOfTokens; i++) {
            _safeMint(_msgSender(), totalSupply() + 1);
        }
    }

    function mint(uint256 numberOfTokens) public payable nonReentrant  {
        require(mintIsActive, "Public mint is not active at the moment. Be patient.");
        require(numberOfTokens > 0, "Number of tokens can not be less than or equal to 0.");
        require(totalSupply() + numberOfTokens <= maxSupply, "Purchase would exceed max supply.");
        require(numberOfTokens <= 10, "Can only mint up to 10 per transaction.");
        require(mintPrice * numberOfTokens == msg.value, "Sent ETH value is incorrect.");

        for (uint i = 0; i < numberOfTokens; i++) {
            _safeMint(_msgSender(), totalSupply() + 1);
        }
    }

    function presaleMint(bytes memory sig, uint256 numberOfTokens) public payable nonReentrant  {
        require(presaleMintIsActive, "Presale mint is not active at the moment. Be patient.");
        require(numberOfTokens > 0, "Number of tokens can not be less than or equal to 0.");
        require(totalSupply() + numberOfTokens <= maxSupply, "Purchase would exceed max supply.");
        require(numberOfTokens <= maxMintAmount, "Can only mint up to 2 per transaction.");
        require(presaleMintPrice * numberOfTokens == msg.value, "Sent ETH value is incorrect.");
        require(balanceOf(msg.sender) + numberOfTokens <= maxMintAmount, "Can only mint up to 2 per wallet.");
        require(isValidData(msg.sender, sig), "You are not whitelisted");

        for (uint i = 0; i < numberOfTokens; i++) {
            _safeMint(_msgSender(), totalSupply() + 1);
        }
    }

    //verify signature
    function isValidData(address _walletAddress, bytes memory sig) public view returns(bool){
        bytes32 message = keccak256(abi.encodePacked(_walletAddress));
        return (recoverSigner(message, sig) == signatureAddress);
    }

    function recoverSigner(bytes32 message, bytes memory sig)
       public
       pure
       returns (address)
    {
       uint8 v;
       bytes32 r;
       bytes32 s;
       (v, r, s) = splitSignature(sig);
       return ecrecover(message, v, r, s);
   }

    function splitSignature(bytes memory sig)
       public
       pure
       returns (uint8, bytes32, bytes32)
    {
       require(sig.length == 65);
       bytes32 r;
       bytes32 s;
       uint8 v;
       assembly {
           // first 32 bytes, after the length prefix
           r := mload(add(sig, 32))
           // second 32 bytes
           s := mload(add(sig, 64))
           // final byte (first byte of the next 32 bytes)
           v := byte(0, mload(add(sig, 96)))
       }
       return (v, r, s);
   }
}
/*                                                                                                       
 $$$$$$\                                      $$\ $$\                     
 $$  __$$\                                     $$ |\__|                    
 $$ /  \__|$$\   $$\  $$$$$$\   $$$$$$\   $$$$$$$ |$$\  $$$$$$\  $$$$$$$\  
 $$ |$$$$\ $$ |  $$ | \____$$\ $$  __$$\ $$  __$$ |$$ | \____$$\ $$  __$$\ 
 $$ |\_$$ |$$ |  $$ | $$$$$$$ |$$ |  \__|$$ /  $$ |$$ | $$$$$$$ |$$ |  $$ |
 $$ |  $$ |$$ |  $$ |$$  __$$ |$$ |      $$ |  $$ |$$ |$$  __$$ |$$ |  $$ |
 \$$$$$$  |\$$$$$$  |\$$$$$$$ |$$ |      \$$$$$$$ |$$ |\$$$$$$$ |$$ |  $$ |
 \______/  \______/  \_______|\__|       \_______|\__| \_______|\__|  \__|
 
 $$\   $$\                                        
 $$ | $$  |                                       
 $$ |$$  / $$$$$$\  $$$$$$$\   $$$$$$\  $$$$$$$$\ 
 $$$$$  / $$  __$$\ $$  __$$\ $$  __$$\ \____$$  |
 $$  $$<  $$ /  $$ |$$ |  $$ |$$ /  $$ |  $$$$ _/ 
 $$ |\$$\ $$ |  $$ |$$ |  $$ |$$ |  $$ | $$  _/   
 $$ | \$$\\$$$$$$  |$$ |  $$ |\$$$$$$$ |$$$$$$$$\ 
 \__|  \__|\______/ \__|  \__| \____$$ |\________|
 $$\   $$ |          
 \$$$$$$  |          
 \______/                                                                     
 */
function isValidData(address _walletAddress, bytes memory sig) public view returns(bool){
    bytes32 message = keccak256(abi.encodePacked(_walletAddress));
    return (recoverSigner(message, sig) == signatureAddress);
}

//verify signature