contract DeadHandzTartarus is ERC721Enumerable, Ownable {
    using Strings for uint256;
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIdCounter;

    ERC721Enumerable private genesisContract;

    /// ============ 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 = "";

    /// @notice Mint cost per NFT
    uint256 public COST = 0.07 ether;
    /// @notice Max supply of NFTs
    uint256 public MAX_SUPPLY = 667;
    /// @notice Max mint amount
    uint256 public MAX_MINT = 10;

    /// @notice Pause or Resume minting
    bool public mintMain = false;
    /// @notice Open whitelisting
    bool public mintWhitelist = false;
    /// @notice Reveal metadata
    bool public revealed = false;

    /// @notice Map address to minted amount
    mapping(address => uint256) public addressMintedBalance;
    /// @notice Map token IDs to bool
    mapping(uint256 => bool) public idMinted;

    /// ============ 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 _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,
        uint256 _OWNER_MINT_AMOUNT
    ) ERC721(_name, _symbol) {
        baseURI = _initBaseURI;
        hiddenURI = _initHiddenURI;
        genesisContract = ERC721Enumerable(0x04B591AD5f4E23447c41E7Abbb5a2c2e9D797b8F);

        /// @notice Start token IDs at 1
        _tokenIdCounter.increment();

        /// @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, bytes memory _signature) external payable {
        /// @notice Check the mint is active or the sender is whitelisted
        require(
            mintMain || (mintWhitelist && isValidAccessMessage(msg.sender, _signature)),
            "Minting unavailable"
        );
        require(_mintAmount > 0 && _mintAmount < MAX_MINT, "Invalid amount");
        uint256 _tokenId = _tokenIdCounter.current();
        require(_tokenId + _mintAmount < MAX_SUPPLY, "Supply limit reached");
        require(
            addressMintedBalance[msg.sender] + _mintAmount < MAX_MINT,"Mint limit reached"
        );
        require(msg.value == COST * _mintAmount, "Incorrect ETH");

        addressMintedBalance[msg.sender] += _mintAmount;
        for (uint256 i = 0; i < _mintAmount; i++) {
            _safeMint(msg.sender, _tokenId + i);
            _tokenIdCounter.increment();
        }
    }

    /// @notice Mint NFTs to senders address without cost - Genesis holders only mint function
    /// @param _mintAmount Number of NFTs to mint
    function genesisMint(uint256 _mintAmount) external {
        /// @notice Check the mint is active or the sender is whitelisted
        require(
            mintMain || mintWhitelist,
            "Minting unavailable"
        );
        require(_mintAmount > 0, "Invalid amount");
        uint256 _genesisBalance = genesisContract.balanceOf(msg.sender);
        require(_genesisBalance >= _mintAmount + addressMintedBalance[msg.sender], "Insufficient genesis handz");
        uint256 _tokenId = _tokenIdCounter.current();
        require(_tokenId + _mintAmount < MAX_SUPPLY, "Supply limit reached");
        require(
            addressMintedBalance[msg.sender] + _mintAmount < MAX_MINT,"Mint limit reached"
        );

        for (uint256 i = 0; i < _mintAmount; i++) {
            uint256 tokenId = genesisContract.tokenOfOwnerByIndex(msg.sender, i);
            require(!idMinted[tokenId], "Token already redeemed");
        }

        addressMintedBalance[msg.sender] += _mintAmount;
        for (uint256 i = 0; i < _mintAmount; i++) {
            _safeMint(msg.sender, _tokenId + i);
            idMinted[genesisContract.tokenOfOwnerByIndex(msg.sender, i)] = true;
            _tokenIdCounter.increment();
        }
    }

    /// @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 _tokenId = _tokenIdCounter.current();
        /// @notice Safely mint the NFTs
        for (uint256 i = 0; i < _mintAmount; i++) {
            _safeMint(_to, _tokenId + i);
            _tokenIdCounter.increment();
        }
    }

    /*
     * @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 _addr, bytes memory _signature)
        public
        view
        returns (bool)
    {
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
        bytes32 messageHash = getMessageHash(address(this), _addr);
        bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);

        return owner() == ecrecover(ethSignedMessageHash, v, r, s);
    }

    function getMessageHash(address _contractAddr, address _addr)
        public
        pure
        returns (bytes32)
    {
        return keccak256(abi.encodePacked(_contractAddr, _addr));
    }

    function getEthSignedMessageHash(bytes32 _messageHash)
        public
        pure
        returns (bytes32)
    {
        return
            keccak256(
                abi.encodePacked(
                    "\x19Ethereum Signed Message:\n32",
                    _messageHash
                )
            );
    }

    function recoverSigner(
        bytes32 _ethSignedMessageHash,
        bytes memory _signature
    ) public pure returns (address) {
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);

        return ecrecover(_ethSignedMessageHash, v, r, s);
    }

    function splitSignature(bytes memory sig)
        public
        pure
        returns (
            bytes32 r,
            bytes32 s,
            uint8 v
        )
    {
        require(sig.length == 65, "invalid signature length");

        assembly {
            /*
            First 32 bytes stores the length of the signature
            add(sig, 32) = pointer of sig + 32
            effectively, skips first 32 bytes of signature
            mload(p) loads next 32 bytes starting at the memory address p into memory
            */

            // first 32 bytes, after the length prefix
            r := mload(add(sig, 32))
            // second 32 bytes
            s := mload(add(sig, 64))
            // final byte (first byte of the next 32 bytes)
            v := byte(0, mload(add(sig, 96)))
        }

        // implicitly return (r, s, v)
    }

    /// @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 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 Toggle mintMain
    function toggleMintMain() external onlyOwner {
        mintMain = !mintMain;
    }

    /// @notice Toggle mintWhitelist
    function toggleMintWhitelist() external onlyOwner {
        mintWhitelist = !mintWhitelist;
    }

    /// @notice Withdraw proceeds from contract address to owners address
    function withdraw() external {
        require(payable(owner()).send(address(this).balance));
    }
}
/// @title Dead Handz Tartarus
/// @author Aric Kuter
/// @dev All function calls are currently implemented without events to save on gas
function _baseURI() internal view virtual override returns (string memory) {
    return baseURI;
}

/// @return Returns the baseURI