contract DeadHandzGenesis is ERC721Enumerable, Ownable {
    using Strings for uint256;
    
    /// ============ Mutable storage ============
    
    /// @notice URI for revealed metadata
    string public baseURI;
    /// @notice URI for hidden metadata
    string public hiddenURI;
    /// @notice Extension for metadata files
    string public baseExtension = ".json";
    
    /// @notice Mint cost per NFT
    uint256 public COST = 0.09 ether;
    /// @notice Max supply of NFTs
    uint256 public MAX_SUPPLY = 100;
    /// @notice Max NFTs to mint per address
    uint256 public MAX_MINT_PER_ADDRESS = 1;
    
    /// @notice Pause or Resume minting
    bool public paused = true;
    /// @notice Reveal metadata
    bool public revealed = false;
    
    /// @notice Whitelist map of addresses to true/false
    mapping(address => bool) public whitelisted;
    /// @notice Map address to minted amount
    mapping(address => uint256) public addressMintedBalance;

    /// ============ Constructor ============
    
    /// @param _name name of NFT
    /// @param _symbol symbol of NFT
    /// @param _initBaseURI URI for revealed metadata in format: ipfs://HASH/
    /// @param _initHiddenURI URI for hidden metadata in format: ipfs://HASH/
    /// @param _whitelist Map of addresses to true/false in format: ["address", "address01"]
    /// @param _OWNER_MINT_AMOUNT Number of NFTs to mint to the owners address
    constructor(
        string memory _name,
        string memory _symbol,
        string memory _initBaseURI,
        string memory _initHiddenURI,
        address[] memory _whitelist,
        uint256 _OWNER_MINT_AMOUNT
    ) ERC721(_name, _symbol) {
        baseURI = _initBaseURI;
        hiddenURI = _initHiddenURI;
        
        /// @notice Populate the whitelist
        for (uint256 i = 0; i < _whitelist.length; i++) {
            whitelisted[_whitelist[i]] = true;
        }
        
        /// @notice mint to owners address
        reserveTokens(owner(), _OWNER_MINT_AMOUNT);
    }

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

    /// @notice Mint NFTs to senders address
    /// @param _mintAmount Number of NFTs to mint
    function mint(uint256 _mintAmount) external payable {
        /// @notice Check the mint is active or the sender is whitelisted
        require(!paused || whitelisted[msg.sender], "Minting paused");
        require(_mintAmount > 0, "Min amount");
        uint256 supply = totalSupply();
        require(
            supply + _mintAmount <= MAX_SUPPLY,
            "Supply limit reached"
        );
        require(
            addressMintedBalance[msg.sender] + _mintAmount <= MAX_MINT_PER_ADDRESS,"Mint limit reached"
        );
        require(msg.value == COST * _mintAmount, "Incorrect ETH");

        /// @notice Safely mint the NFTs
        addressMintedBalance[msg.sender] = addressMintedBalance[msg.sender] + _mintAmount;
        for (uint256 i = 1; i <= _mintAmount; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }
    
    /// @notice Reserved mint function for owner only
    /// @param _to Address to send tokens to
    /// @param _mintAmount Number of NFTs to mint
    function reserveTokens(address _to, uint256 _mintAmount) public onlyOwner {
        uint256 supply = totalSupply();
        /// @notice Safely mint the NFTs
        addressMintedBalance[msg.sender] = addressMintedBalance[msg.sender] + _mintAmount;
        for (uint256 i = 1; i <= _mintAmount; i++) {
            _safeMint(_to, supply + i);
        }
    }

    /// @return Returns a conststructed string in the format: //ipfs/HASH/[tokenId].json
    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI query for NonExistent Token."
        );

        if (!revealed) {
            return hiddenURI;
        }

        string memory currentBaseURI = _baseURI();
        return
            bytes(currentBaseURI).length > 0
                ? string(
                    abi.encodePacked(
                        currentBaseURI,
                        tokenId.toString(),
                        baseExtension
                    )
                )
                : "";
    }

    /// @notice Reveal metadata
    function reveal() external onlyOwner {
        revealed = true;
    }

    /// @notice Update COST
    /// @param _newCost New cost per NFT in ether
    function setCost(uint256 _newCost) external onlyOwner {
        COST = _newCost;
    }

    /// @notice Set the URI of the hidden metadata
    /// @param _hiddenURI URI for hidden metadata NOTE: This URI must be the link to the exact file in format: ipfs//HASH/
    function setHiddenURI(string memory _hiddenURI) external onlyOwner {
        hiddenURI = _hiddenURI;
    }

    /// @notice Set URI of the metadata
    /// @param _newBaseURI URI for revealed metadata
    function setBaseURI(string memory _newBaseURI) external onlyOwner {
        baseURI = _newBaseURI;
    }

    /// @notice Set the base extension for the metadata
    /// @param _newBaseExtension Base extension value
    function setBaseExtension(string memory _newBaseExtension)
        external
        onlyOwner
    {
        baseExtension = _newBaseExtension;
    }

    /// @notice Set the paused value
    /// @param _state paused true or false
    function pause(bool _state) external onlyOwner {
        paused = _state;
    }

    /// @notice Add user to whitelist
    /// @param _user Address of the user
    function whitelistUser(address _user) external onlyOwner {
        whitelisted[_user] = true;
    }

    /// @notice Remove user from whitelist
    /// @param _user Address of the user
    function removeWhitelistedUser(address _user) external onlyOwner {
        whitelisted[_user] = false;
    }

    /// @notice Withdraw proceeds from contract address to owners address
    function withdraw() external {
        require(payable(owner()).send(address(this).balance));
    }
}
/// @title Dead Handz Smart Contract for the Genesis collection
/// @author Aric Kuter
/// @dev All function calls are currently implemented without events to save on gas
function setHiddenURI(string memory _hiddenURI) external onlyOwner {
    hiddenURI = _hiddenURI;
}

/// @notice Set the URI of the hidden metadata
/// @param _hiddenURI URI for hidden metadata NOTE: This URI must be the link to the exact file in format: ipfs//HASH/