contract DoodlePenguins 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 DOODLE_PENGUIN_PROVENANCE = "";

    uint256 public constant MAX_SUPPLY = 10000;
    uint256 public constant MAX_SUPPLY_WHITELIST = 1000;
    uint256 public constant MAX_GIVEAWAY_INFLUENCER = 100;
    uint256 public constant WHITELIST_MINT_CAP = 2;
    uint256 public constant MAX_DOODLE_PENGUIN_PER_PURCHASE = 20;
    uint256 public constant WALLET_CAP = 100;
    uint256 private DOODLE_PENGIUN_PRICE = 0 ether;


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

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

    constructor() ERC721("Doodles Penguin", "DOODLEPENGS") 
    {
        tokenSupply.increment();
    }

    /**************
     * Flip State *
     **************/

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

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

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

    function setProvenanceHash(string memory provenanceHash) external onlyOwner 
    {
        DOODLE_PENGUIN_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;
    }

    /*********************
     * Get current price *
     *********************/
    function getCurrentPrice(uint256 _quantity) public view returns (uint256)
    {
        uint256 returnPrice;

        if (tokenSupply.current().add(_quantity) > 1000)
        {
            returnPrice = 0.01 ether;
        }

        if (tokenSupply.current().add(_quantity) > 3000)
        {
            returnPrice = 0.02 ether;
        }

        if (tokenSupply.current().add(_quantity) > 5000)
        {
            returnPrice = 0.03 ether;
        }

        if (tokenSupply.current().add(_quantity) > 7000)
        {
            returnPrice = 0.04 ether;
        }

        return returnPrice;
    }

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

    /**********************
     * 0-1000      free   *
     * 1001-3000  .01 eth *
     * 3001-5000  .02 eth *
     * 5001-7000  .03 eth *
     * 7001-10000 .04 eth *
     **********************/

    // Whitelist mint function
    function presaleMint(uint256 _quantity, bytes calldata _whitelistSignature) external payable nonReentrant 
    {
        require(verifyOwnerSignature(keccak256(abi.encode(msg.sender)), _whitelistSignature), "Invalid whitelist signature");
        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");
        require(whitelistAddressMintCount[msg.sender].add(_quantity) <= WHITELIST_MINT_CAP, "This purchase would exceed the maximum you are allowed to mint for presale");

        whitelistAddressMintCount[msg.sender] += _quantity;
        _safeMintDoodlePenguins(_quantity);
    }

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

        _safeMintDoodlePenguins(_quantity);
    }


    // Used for giveaway winners or influencer payment for marketing (mints 5 Doodle Penguins)
    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 _safeMintDoodlePenguins(uint256 _quantity) internal 
    {
        if (tokenSupply.current().add(_quantity) > 1000)
        {
            DOODLE_PENGIUN_PRICE = 0.01 ether;
        }

        if (tokenSupply.current().add(_quantity) > 3000)
        {
            DOODLE_PENGIUN_PRICE = 0.02 ether;
        }

        if (tokenSupply.current().add(_quantity) > 5000)
        {
            DOODLE_PENGIUN_PRICE = 0.03 ether;
        }

        if (tokenSupply.current().add(_quantity) > 7000)
        {
            DOODLE_PENGIUN_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 >= DOODLE_PENGIUN_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 
    {
        uint256 balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }

    // Withdraws amount specified (ETH) from balance of contract to owner account (owner-only)
    function withdrawPartial(address payable _recipient, uint256 _amount)
    public
    onlyOwner
    {
        // 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"
        );
        _recipient.transfer(_amount);
    }

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

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

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