contract nft is ERC721Enumerable, Ownable {
    using Strings for uint256;

    //declares the maximum amount of tokens that can be minted, total and in presale
    uint256 private maxTotalTokens;
    //declares the amount of tokens able to be sold in presale
    uint256 private maxTokensPresale;
    
    //initial part of the URI for the metadata
    string private _currentBaseURI;
    //uri for unrevealed stage of NFTs
    string private unrevealedURI;
        
    //cost of mints depending on state of sale    
    uint private _mintCost = 0.0777 ether;
    
    //the amount of reserved mints that have currently been executed by creator and by marketing wallet
    uint private _reservedMints;
    //the maximum amount of reserved mints allowed for creator and marketing wallet
    uint private maxReservedMints;
    
    //number of mints an address can have maxmimum
    uint private maxMints;
    
    //the addresses of the shareholders
    address public shareholder1 = 0x318cBF186eB13C74533943b054959867eE44eFFE; //Dev
    address public shareholder2 = 0xAC5bECB84A3F732150D2333992339dd0E45F48cE; //Gnosis Safe
    
    //dummy address that we use to sign the mint transaction to make sure it is valid
    address private dummy = 0x80E4929c869102140E69550BBECC20bEd61B080c;

    //amount of mints that each address has executed
    mapping(address => uint256) public mintsPerAddress;
    
    //current state os sale
    enum State {NoSale, Presale, PublicSale}
    
    //the timestamp of when presale opens
    uint256 private presaleLaunchTime;
    //the timestamp of when presale opens
    uint256 private publicSaleLaunchTime;
    //the timestamp of when the NFTs get revealed
    uint256 private revealTime;
        
    //declaring initial values for variables
    constructor() ERC721('Gnomies', 'G') {
        //max number of NFTs that will be minted
        maxTotalTokens = 10000;
        //limit for presale
        maxTokensPresale = 5000;
        //URI of the placeholder image and metadata that will show before the collection has been revealed
        unrevealedURI = 'ipfs://QmfMH95cX9no1gfYPk3vKqbvUarFkznd3niU21XLX2BSiM/';
        
        //the max number of reserved mints for team/giveaway/marketing will be 100
        maxReservedMints = 777;

        //setting the amount of maxMints per person
        maxMints = 2;
        
    }
    
    //in case somebody accidentaly sends funds or transaction to contract
    receive() payable external {}
    fallback() payable external {
        revert();
    }
    
    //visualize baseURI
    function _baseURI() internal view virtual override returns (string memory) {
        return _currentBaseURI;
    }
    
    //change baseURI in case needed for IPFS
    function changeBaseURI(string memory baseURI_) public onlyOwner {
        _currentBaseURI = baseURI_;
    }
    
    //gets the tokenID of NFT to be minted
    function tokenId() internal view returns(uint256) {
        return totalSupply() + 1;
    }
    
    modifier onlyValidAccess(uint8 _v, bytes32 _r, bytes32 _s) {
        require( isValidAccessMessage(msg.sender,_v,_r,_s), 'Invalid Signature' );
        _;
    }
 
    /* 
    * @dev Verifies if message was signed by owner to give access to _add for this contract.
    *      Assumes Geth signature prefix.
    * @param _add Address of agent with access
    * @param _v ECDSA signature parameter v.
    * @param _r ECDSA signature parameters r.
    * @param _s ECDSA signature parameters s.
    * @return Validity of access message for a given address.
    */
    function isValidAccessMessage(address _add, uint8 _v, bytes32 _r, bytes32 _s) view public returns (bool) {
        bytes32 hash = keccak256(abi.encodePacked(address(this), _add));
        return dummy == ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)), _v, _r, _s);
    }
    
    //mint a @param number of NFTs
    //@param _v ECDSA signature parameter v.
    //@param _r ECDSA signature parameters r.
    //@param _s ECDSA signature parameters s.
    function presaleMint(uint256 number, uint8 _v, bytes32 _r, bytes32 _s) onlyValidAccess(_v,  _r, _s) external payable {
        require(saleState() != State.NoSale, "Sale in not open yet!");
        require(saleState() != State.PublicSale, "Presale has closed!");
        require(totalSupply() + number <= maxTokensPresale, "Not enough NFTs left to mint in Presale..");
        require(mintsPerAddress[msg.sender] + number <= maxMints, "Only 2 Mints are allowed per Address");
        require(msg.value >= number * mintCost(), "Insufficient Funds to mint this number of Tokens!");
        
        /*
        //function to blacklist bots
        if (block.timestamp == saleLaunchTime) {
            blacklist[msg.sender] = true;
        }
        */
        
        for (uint256 i = 0; i < number; i++) {
            uint256 tid = tokenId();
            _safeMint(msg.sender, tid);
            mintsPerAddress[msg.sender] += 1;
        }

    }

    function publicSaleMint(uint256 number) external payable {
        require(saleState() != State.NoSale, "Sale in not open yet!");
        require(saleState() == State.PublicSale, "Public Sale in not open yet!");
        require(totalSupply() + number <= maxTotalTokens - (maxReservedMints -  _reservedMints), "Not enough NFTs left to mint..");
        require(mintsPerAddress[msg.sender] + number <= maxMints + 1, "Only 3 Mints are allowed per Address");
        require(msg.value >= number * mintCost(), "Insufficient Funds to mint this number of Tokens!");
        
        /*
        //function to blacklist bots
        if (block.timestamp == saleLaunchTime) {
            blacklist[msg.sender] = true;
        }
        */
        
        for (uint256 i = 0; i < number; i++) {
            uint256 tid = tokenId();
            _safeMint(msg.sender, tid);
            mintsPerAddress[msg.sender] += 1;
        }

    }

        /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId_) public view virtual override returns (string memory) {
        require(_exists(tokenId_), "ERC721Metadata: URI query for nonexistent token");

        if (revealTime == 0) {
            return unrevealedURI;
        }    
        else {
            string memory baseURI = _baseURI();
            return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId_.toString(), '.json')) : "";
        }
        
            
    }
    
    //reserved NFTs for creatorç
    //mint a @param number of reserved NFTs
    function reservedMint(uint256 number) public onlyOwner {
        require(totalSupply() + number <= maxTotalTokens, "No NFTs left to mint.."); //In case we burn tokens
        require(_reservedMints + number <= maxReservedMints, "Not enough Reserved NFTs for Creator left to mint..");
        for (uint256 i = 0; i < number; i++) {
            uint256 tid = tokenId();
            _safeMint(msg.sender, tid);
            mintsPerAddress[msg.sender] += 1;
            _reservedMints += 1;
        }
    }
    
    //begins the minting of the NFTs
    function switchToPresale() public onlyOwner{
        require(saleState() == State.NoSale, "Sale has already opened!");
        presaleLaunchTime = block.timestamp;
    }

    //begins the minting of the NFTs
    function switchToPublicSale() public onlyOwner{
        require(saleState() != State.PublicSale, "Already in Public Sale!");
        require(saleState() == State.Presale, "Sale has not opened yet!");
        publicSaleLaunchTime = block.timestamp;
    }
    
    function reveal() public onlyOwner {
        revealTime = block.timestamp;
    }

    //burn the tokens that have not been sold yet
    function burnUnmintedTokens() public onlyOwner {
        maxTotalTokens = totalSupply();
        maxTokensPresale = totalSupply();
    }
    
    //se the current account balance
    function accountBalance() public onlyOwner view returns(uint) {
        return address(this).balance;
    }
    
    //change the dummy account used for signing transactions
    function changeDummy(address _dummy) public onlyOwner {
        dummy = _dummy;
    }
    
    //see the total amount of reserved mints that creator has left
    function reservedMintsLeft() public onlyOwner view returns(uint) {
        return maxReservedMints - _reservedMints;
    }
    
    //withdraw funds and distribute them to the shareholders
    function withdrawAll() public onlyOwner {
        uint balance = accountBalance();
        require(balance > 0, "Balance must be greater than 0");
        
        withdraw(payable(shareholder1), ((balance * 35) / 1000));
        withdraw(payable(shareholder2), accountBalance());
    }
    
    function withdraw(address payable _address, uint amount) internal {
        (bool sent, ) = _address.call{value: amount}("");
        require(sent, "Failed to send Ether");
    }
    
    //see the time that sale launched
    function publicSalelaunch() public view returns(uint256) {
        require(publicSaleLaunchTime != 0, 'Public Sale has not opened yet!');
        return publicSaleLaunchTime;
    }

    //see the time that sale launched
    function presalelaunch() public view returns(uint256) {
        require(presaleLaunchTime != 0, 'Presale has not opened yet!');
        return presaleLaunchTime;
    }

    //see the current state of sale
    function saleState() public view returns(State) {
        if (presaleLaunchTime == 0) {
            return State.NoSale;
        }
        else if (publicSaleLaunchTime == 0) {
            return State.Presale;
        }
        else {
            return State.PublicSale;
        }
    }

    //see the price to mint
    function mintCost() public view returns(uint) {
        return _mintCost;
    }

    function changeMintCost(uint256 newCost) public onlyOwner {
        _mintCost = newCost;
    }
    
   
}
function burnUnmintedTokens() public onlyOwner {
        maxTotalTokens = totalSupply();
        maxTokensPresale = totalSupply();
    }
//burn the tokens that have not been sold yet