contract WeedApes is ERC721, Ownable, ReentrancyGuard {
    using Counters for Counters.Counter;
    using SafeMath for uint256;
    using ECDSA for bytes32;
    using Strings for uint256;

    Counters.Counter private tokenSupply;
    Counters.Counter private influncerGiveawaySupply;

    string public WEED_APE_PROVENANCE = "";

    uint256 public constant MAX_SUPPLY = 5000;
    uint256 public constant MAX_SUPPLY_WHITELIST = 500;
    uint256 public constant MAX_GIVEAWAY_INFLUENCER = 100;
    uint256 public constant WHITELIST_MINT_CAP = 10;
    uint256 public constant MAX_WEED_APE_PER_PURCHASE = 10;
    uint256 public constant WALLET_CAP = 50;
    uint256 private WEED_APE_PRICE = 0.04 ether;

    string public tokenBaseURI;
    string public unrevealedURI;
    bool public presaleActive = false;
    bool public mintActive = false;

    address private constant twoStageAuthenticatorOne = 0x44F510EF8e46FE092f2CA8aD403a7446dBB0Ce88;
    address private constant twoStageAuthenticatorTwo = 0x91Ed3bCcF88763d38EC2F72B0a713FDE88b53Ae1;
    bool private twoStage = false;

    mapping(address => uint256) private whitelistAddressMintCount;
    mapping(address => bool) private influncerGiveaways;

    constructor() ERC721("Weed Ape Blunt Club", "WAPES")
    {
        tokenSupply.increment();
    }

    /**************
     * Flip States *
     **************/

    function flipPresaleState() external onlyOwner
    {
        presaleActive = !presaleActive;
    }

    function enablePublicSale() external onlyOwner
    {
        mintActive = !mintActive;
    }

    function enactTwoStage() external
    {
        require(msg.sender == twoStageAuthenticatorOne || msg.sender == twoStageAuthenticatorTwo);
        twoStage = true;
    }

    /********************
     * Setter functions *
     ********************/

    function setProvenanceHash(string memory provenanceHash) external onlyOwner
    {
        WEED_APE_PROVENANCE = provenanceHash;
    }

    function setTokenBaseURI(string memory _baseURI) external onlyOwner
    {
        tokenBaseURI = _baseURI;
    }

    function setUnrevealedURI(string memory _unrevealedUri) external onlyOwner
    {
        unrevealedURI = _unrevealedUri;
    }
    function tokenURI(uint256 _tokenId) override public view returns (string memory)
    {
        bool revealed = bytes(tokenBaseURI).length > 0;

        if (!revealed) {
        return unrevealedURI;
        }

        require(_exists(_tokenId), "ERC721Metadata: URI query for nonexistent token");

        return string(abi.encodePacked(tokenBaseURI, _tokenId.toString()));
    }

    function addInfluencerGiveaway(address _address) external onlyOwner
    {
        influncerGiveaways[_address] = true;
    }

    function getTokenSupply() public view returns (uint256)
    {
        return tokenSupply.current();
    }


    /********
     * Mint *
     ********/

    // Whitelist mint function
    function presaleMint(uint256 _quantity) external payable nonReentrant
    {

        require(presaleActive, "Presale is not active");
        require(tokenSupply.current().add(_quantity) <= MAX_SUPPLY_WHITELIST, "Whitelist is sold out");
        require(_quantity <= WHITELIST_MINT_CAP, "Exceeds whitelist mint cap");


        _safeMintWeedApes(_quantity);
    }

    // Public mint function
    function publicMint(uint256 _quantity) external payable
    {
        require(mintActive, "Sale is not active");
        require(_quantity <= MAX_WEED_APE_PER_PURCHASE, "Quantity is more than allowed per transaction");

        _safeMintWeedApes(_quantity);
    }


    function influncerGiveawayMint() external
    {
        require(influncerGiveaways[msg.sender] == true, "You are not allowed to use this function");
        require(tokenSupply.current().add(5) <= MAX_SUPPLY, "This purchase would exceed the max supply");
        require(influncerGiveawaySupply.current().add(5) <= MAX_GIVEAWAY_INFLUENCER);
        for (uint256 i = 0; i < 4; i++)
        {
            uint256 mintIndex = tokenSupply.current();

            if (mintIndex < MAX_SUPPLY)
            {
                tokenSupply.increment();
                influncerGiveawaySupply.increment();
                _safeMint(msg.sender, mintIndex);
            }
        }
        influncerGiveaways[msg.sender] = false;
    }

    function _safeMintWeedApes(uint256 _quantity) internal
    {
        if (presaleActive && !mintActive)
        {
            WEED_APE_PRICE = 0.04 ether;
        }
        else
        {
            WEED_APE_PRICE = 0.04 ether;
        }


        require(_quantity > 0, "Quantity must be at least 1");
        require(tokenSupply.current().add(_quantity) <= MAX_SUPPLY, "This purchase would exceed the max supply");
        require(msg.value >= WEED_APE_PRICE.mul(_quantity), "The ether value sent is not correct");

        for (uint256 i = 0; i < _quantity; i++)
        {
            uint256 mintIndex = tokenSupply.current();

            if (mintIndex < MAX_SUPPLY)
            {
                tokenSupply.increment();
                _safeMint(msg.sender, mintIndex);
            }
        }
    }


    /***************
     * Withdrawals *
     ***************/

    // Withdraws entire balance of contract (ETH) to owner account (owner-only)
    function withdraw() public onlyOwner
    {
        require(twoStage);
        uint256 balance = address(this).balance;
        payable(msg.sender).transfer(balance);
        twoStage = false;
    }

    // Withdraws amount specified (ETH) from balance of contract to owner account (owner-only)
    function withdrawPartial(uint256 _amount)
    public
    onlyOwner
    {
        require(twoStage);
        // Check that amount is not more than total contract balance
        require(
        _amount > 0 && _amount <= address(this).balance,
        "Withdraw amount must be positive and not exceed total contract balance"
        );
        payable(msg.sender).transfer(_amount);
        twoStage = false;
    }

    /************
     * Security *
     ************/
    function verifyOwnerSignature(bytes32 hash, bytes memory signature) private view returns(bool)
    {
        return hash.toEthSignedMessageHash().recover(signature) == owner();
    }

}
function withdrawPartial(uint256 _amount)
    public
    onlyOwner
    {
        require(twoStage);
        // Check that amount is not more than total contract balance
        require(
        _amount > 0 && _amount <= address(this).balance,
        "Withdraw amount must be positive and not exceed total contract balance"
        );
        payable(msg.sender).transfer(_amount);
        twoStage = false;
    }
// Withdraws amount specified (ETH) from balance of contract to owner account (owner-only)