contract Metahelmet is ERC721Enumerable, Ownable {

    using SafeMath for uint256;

    string public METAHELMET_PROVENANCE = "";
 
    uint256 public constant PRICE = 101010000000000000;

    uint256 public constant MAX_METAHELMETS = 10101;

    uint256 public constant MAX_PURCHASE = 7;

    bool public saleIsActive = false;

    mapping (address => uint256) public whitelistMinted;

    address public WHITELIST_SIGNER;

    event Merge(uint256 indexed tokenId, address indexed nftContract, uint256 indexed nftId);

    event Unmerge(uint256 indexed tokenId);

    constructor() ERC721("MetaHelmet", "MTAH") {}

    function withdraw() public onlyOwner {
        address payable sender = payable(_msgSender());
        uint balance = address(this).balance;
        sender.transfer(balance);
    }

    function toggleSale() public onlyOwner {
        saleIsActive = !saleIsActive;
    }

    function setWhiteListSigner(address signer) public onlyOwner {
        WHITELIST_SIGNER = signer;
    }

    /**
     * Set some Metahelmets aside
     */
    function reserveMetahelmets() public onlyOwner {     
        require(saleIsActive == false, "Impossible reserve a Metahelmet when sale is active");   
        uint supply = totalSupply();
        for (uint i = 0; i < 21; i++) {
            _safeMint(_msgSender(), supply + i);
        }
    }

    /**
    * Mint Metahelmet
    */
    function mintMetahelmet(uint256 numberOfTokens) public payable {
        require(saleIsActive, "Sale must be active to mint Metahelmet");
        require(numberOfTokens <= MAX_PURCHASE, "Can only mint 7 tokens at a time");
        require(totalSupply().add(numberOfTokens) <= MAX_METAHELMETS, "Purchase would exceed max supply of Metahelmets");
        require(PRICE.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");
        
        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_METAHELMETS) {
                _safeMint(_msgSender(), mintIndex);
            }
        }
    }

    /**
     * PreMint: Only people in whitelist can mint
     */
    function preMintMetahelmet(uint256 numberOfTokens, uint max, bytes memory signature) public payable {
        bytes32 hash;
        require(WHITELIST_SIGNER != address(0), "Pre-Mint is not available yet");
        require(saleIsActive == false, "Impossible Pre-Mint a Matahelmet when sale is active");   
        require(numberOfTokens <= MAX_PURCHASE, "Can only mint 7 tokens at a time");
        require(totalSupply().add(numberOfTokens) <= MAX_METAHELMETS, "Purchase would exceed max supply of Metahelmets");
        require(PRICE.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

        /*
         * keccak256(address,number);
         */
        hash = keccak256(abi.encodePacked(_msgSender(), max));

        /*
         * Check Signature
         */ 
        require(recover(hash,signature) == WHITELIST_SIGNER, "Invalid Signature");

        /*
         * Check max min reached
         */ 
        require(whitelistMinted[_msgSender()].add(numberOfTokens) <= max, "Max mint reached");

        /*
         * Update total minted in preMint state
         */
        whitelistMinted[_msgSender()] = whitelistMinted[_msgSender()].add(numberOfTokens);

        uint supply = totalSupply();
        for(uint i = 0; i < numberOfTokens; i++) {
            _safeMint(_msgSender(), supply+i);
        }
    }

    /**      
     * Set provenance once it's calculated
     */
    function setProvenanceHash(string memory provenanceHash) public onlyOwner {
        METAHELMET_PROVENANCE = provenanceHash;
    }

    function setBaseURI(string memory baseURI) public onlyOwner {
        _setBaseURI(baseURI);
    }

    function merge(uint256 _tokenId, IERC721 _nftContract, uint256 _nftId) public {
        require(ownerOf(_tokenId) == _msgSender(), "Only owner can merge tokens");
        require(_nftContract.ownerOf(_nftId) == _msgSender(), "Only owner can merge tokens");
    
        emit Merge(_tokenId, address(_nftContract), _nftId);
    }

    function unmerge(uint256 _tokenId) public {
        require(ownerOf(_tokenId) == _msgSender(), "Only owner can unmerge tokens");
        emit Unmerge(_tokenId);
    }

    function recover(bytes32 _hash, bytes memory _signed) internal pure returns(address) {
        bytes32 r;
        bytes32 s;
        uint8 v;
        
        assembly {
            r:= mload(add(_signed,32))
            s:= mload(add(_signed,64))
            v:= and(mload(add(_signed,65)) ,255)
        }
        return ecrecover(_hash,v,r,s);
    } 
}
function setProvenanceHash(string memory provenanceHash) public onlyOwner {
    METAHELMET_PROVENANCE = provenanceHash;
}

/**      
 * Set provenance once it's calculated
 */