contract GoldenHeroes is ERC721("GOLDEN HEROES", "GH"), ERC721Enumerable, Ownable {
    using SafeMath for uint256;
    using Strings for uint256;

    string private baseURI;
    uint256 public nftsNumber = 999;
    uint256 public nftsPresale = 100;
    uint256 public maxTokensPresale = 2;
    uint256 public maxTokensPerTransaction = 10;
    uint public presalePrice = 0.1 ether;
    uint public initialPrice = 0.3 ether;
    uint public finalPrice = 0.2 ether;
    uint public priceDrop= 0.05 ether;
    uint public priceDropTime = 360 minutes;
    uint public startTime = 1638154800;
    uint public whiteListId = 0;
    uint private devSup = 4.5 ether;
    address private devAddress = 0x0AF3f0461f2bEE2F18f405d0a4463A0cC131723D;
    bool public isActive;
    bool public isPresaleActive;
    bytes32 public root;

    mapping(uint => mapping(address => uint)) private presaleClaimed;

    /*
     * Function setIsActive to activate/desactivate the smart contract
    */
    function setIsActive(
        bool _isActive
    )
        external
        onlyOwner
    {
        isActive = _isActive;
    }

    /*
     * Function setPresaleActive to activate/desactivate the presale
    */
    function setPresaleActive(
        bool _isActive
    )
        external
        onlyOwner
    {
        isPresaleActive = _isActive;
    }

    /*
     * Function to set Base and URI
    */
    function setURI(
        string memory _URI
    )
        external
        onlyOwner
    {
        baseURI = _URI;
    }

    /*
	 * @dev create a new drop.
	 */
    function setNewDrop(
        uint256 _supply,
        uint256 _presaleSupply,
        uint256 _maxTokensPresale,
        uint256 _maxTokensPerTransaction,
        uint256 _presalePrice,
        uint256 _initialPrice,
        uint256 _finalPrice,
        uint256 _priceDrop,
        uint256 _priceDropTimeInSeconds,
        uint256 _startTime,
        uint _whiteListId
    )
        public
        onlyOwner
    {
        nftsNumber = _supply;
        nftsPresale = _presaleSupply;
        maxTokensPresale = _maxTokensPresale;
        maxTokensPerTransaction = _maxTokensPerTransaction;
        presalePrice = _presalePrice;
        initialPrice = _initialPrice;
        finalPrice = _finalPrice;
        priceDrop= _priceDrop;
        priceDropTime = _priceDropTimeInSeconds;
        startTime = _startTime;
        whiteListId = _whiteListId;
    }

    /*
	 * @dev calculate NFT price on current time.
	 */
	function getCurrentPrice() public view returns(uint) {
		return getPriceOnTime(block.timestamp);
	}

	/*
	 * @dev calculate NFT price on exact time.
	 * @param _time time for calculation
	 */
	function getPriceOnTime(uint _time) public view returns(uint) {
		if(_time < startTime) {
			return 0;
		}
		uint maxRange = (initialPrice - finalPrice) / priceDrop;
		uint currentRange = (_time - startTime) / priceDropTime;

		if(currentRange >= maxRange) {
			return finalPrice;
		}

		return initialPrice - (currentRange * priceDrop);
	}

    /*
     * Function to withdraw collected amount
    */
    function withdraw(
        address _to
    )
        public
        onlyOwner
    {
        uint balance = address(this).balance;
        require(balance > 0, "Balance should be more then zero");

        if(balance <= devSup) {
            payable(devAddress).transfer(balance);
            devSup = devSup.sub(balance);
            return;
        } else {
            if(devSup > 0) {
                payable(devAddress).transfer(devSup);
                balance = balance.sub(devSup);
                devSup = 0;
            }

            payable(_to).transfer(balance);
        }
    }

    /*
     * Function to mint new NFTs
     * Amount is calculated as per (tokenPrice.mul(_numOfTokens))
    */
    function mintNFT(
        uint256 _amount
    )
        public
        payable
    {
        require(isActive, 'Contract is not active');
        require(block.timestamp >= startTime, 'Sale is not started yet');
        require(_amount <= maxTokensPerTransaction, 'Cannot mint above limit');
        require(totalSupply().add(_amount) <= nftsNumber, "Purchase would exceed max supply of NFTs");

        uint currentPrice = getCurrentPrice() * _amount;
		require(msg.value >= currentPrice, "Ether value sent is not correct");
		if(msg.value > currentPrice) {
			//send the rest back
			(bool sent, ) = payable(msg.sender).call{value: msg.value - currentPrice}("");
        	require(sent, "Failed to send Ether");
		}

        for(uint i = 0; i < _amount; i++) {
            _safeMint(msg.sender, totalSupply());
        }
    }

    /*
    * @dev Mint for giveways.
    */
    function giveawayMint(
        address _to,
        uint _amount
    )
        public
        onlyOwner
    {
        require(totalSupply().add(_amount) <= nftsNumber, "Purchase would exceed max supply of NFTs");

        for(uint i = 0; i < _amount; i++) {
            _safeMint(_to, totalSupply());
        }
    }

    /*
    * @dev function to buy tokens.
    * @param _amount how much tokens can be bought.
    * @param _proof Merkle tree proof.
    */
    function presaleMint (
        uint _amount,
        bytes32[] memory _proof
      )
          public
          payable
      {
          require(isActive, 'Contract is not active');
          require(block.timestamp <= startTime, 'Open sale is started');

          require(nftsPresale >= _amount, "Purchase exceeds presale supply");
          require(presalePrice.mul(_amount) == msg.value, "Ether value sent is not correct");
          require(_amount <= maxTokensPresale, 'Cannot purchase this many tokens');
          require(presaleClaimed[whiteListId][msg.sender].add(_amount) <= maxTokensPresale, 'Purchase exceeds max presale');
          require(verify(_proof, bytes32(uint256(uint160(msg.sender)))), "Not whitelisted");

          for(uint i = 0; i < _amount; i++) {
              presaleClaimed[whiteListId][msg.sender] += 1;
              nftsPresale -= 1;
              _safeMint(msg.sender, totalSupply());
          }
    }

    function verify(bytes32[] memory proof, bytes32 leaf) public view returns (bool) {
      bytes32 computedHash = leaf;

      for (uint256 i = 0; i < proof.length; i++) {
        bytes32 proofElement = proof[i];

        if (computedHash <= proofElement) {
            computedHash = sha256(abi.encodePacked(computedHash, proofElement));
        } else {
            computedHash = sha256(abi.encodePacked(proofElement, computedHash));
        }
      }

      return computedHash == root;
    }

    function setRoot(uint256 _root) onlyOwner public {
        root = bytes32(_root);
    }

    /*
     * Function to get token URI of given token ID
    */
    function tokenURI(
        uint256 _tokenId
    )
        public
        view
        virtual
        override
        returns (string memory)
    {
        require(_exists(_tokenId), "ERC721Metadata: URI query for nonexistent token");

        return bytes(baseURI).length > 0
            ? string(abi.encodePacked(baseURI, _tokenId.toString()))
            : "";
    }

    function supportsInterface(
        bytes4 _interfaceId
    )
        public
        view
        override (ERC721, ERC721Enumerable)
        returns (bool)
    {
        return super.supportsInterface(_interfaceId);
    }

    // Standard functions to be overridden
    function _beforeTokenTransfer(
        address _from,
        address _to,
        uint256 _tokenId
    )
        internal
        override(ERC721, ERC721Enumerable)
    {
        super._beforeTokenTransfer(_from, _to, _tokenId);
    }
}
function mintNFT(
    uint256 _amount
)
    public
    payable
{
    require(isActive, 'Contract is not active');
    require(block.timestamp >= startTime, 'Sale is not started yet');
    require(_amount <= maxTokensPerTransaction, 'Cannot mint above limit');
    require(totalSupply().add(_amount) <= nftsNumber, "Purchase would exceed max supply of NFTs");

    uint currentPrice = getCurrentPrice() * _amount;
quire(msg.value >= currentPrice, "Ether value sent is not correct");
(msg.value > currentPrice) {
/send the rest back
bool sent, ) = payable(msg.sender).call{value: msg.value - currentPrice}("");
    	require(sent, "Failed to send Ether");

    for(uint i = 0; i < _amount; i++) {
        _safeMint(msg.sender, totalSupply());
    }
}

/*
 * Function to mint new NFTs
 * Amount is calculated as per (tokenPrice.mul(_numOfTokens))
 */