contract NTS2Items is ERC721Enumerable, ReentrancyGuard, Ownable {

    bool itemMintActive;
    address identityContract;
    address itemMint;
    address bytesContract;
    uint256 boughtItemsCount;
    uint256 itemCost = 500 ether;

    bytes32[] _rootHash;

    // Mapping for identity tokenIds that have previously claimed
    mapping(uint256 => uint256) private _identityClaims;

    // Mapping to look up what identity minted a specific token
    mapping(uint256 => uint256) private _tokenMintedByIdentity;

    function setItemMint() external onlyOwner
    {
        itemMintActive = !itemMintActive;
    }

    function getWeapon(uint256 tokenId) public view returns (string memory)
    {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        string memory output;

        NTS2Items dataContract = NTS2Items(itemMint);
        output = dataContract.getWeapon(tokenId);

        return output;
    }

    function getVehicle(uint256 tokenId) external view returns (string memory)
    {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        string memory output;

        NTS2Items dataContract = NTS2Items(itemMint);
        output = dataContract.getVehicle(tokenId);

        return output;
    }
    
    function getApparel(uint256 tokenId) public view returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        string memory output;
        
        NTS2Items dataContract = NTS2Items(itemMint);
        output = dataContract.getApparel(tokenId);

        return output;
    }

    function getHelm(uint256 tokenId) public view returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        string memory output;
        
        NTS2Items dataContract = NTS2Items(itemMint);
        output = dataContract.getHelm(tokenId);

        return output;
    }

    function getTokenClaimedByIdentityTokenId(uint256 identityTokenId) public view returns (uint256) {
        return _identityClaims[identityTokenId];
    }

    function getClaimantIdentityIdByTokenId(uint256 tokenId) public view returns (uint256) {
        return _tokenMintedByIdentity[tokenId];
    }

    function addRootHash(bytes32 _hash) external onlyOwner {
        _rootHash.push(_hash);
    }

    function tokenURI(uint256 tokenId) override public view returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        
        string memory output;

        NTS2Items dataContract = NTS2Items(itemMint);
        output = dataContract.tokenURI(tokenId);


        return output;
    }

    function identityClaim(uint256 identityTokenId, uint256 spotOnLeaderboard, uint256 spotInWhitelist, bytes32[] memory proof) public nonReentrant {
        require(itemMintActive, "Minting is not currently active");
        require(identityTokenId > 0 && identityTokenId < 4501, "That is not a claimable item");
        require(whitelistValidated(identityTokenId, spotOnLeaderboard, spotInWhitelist, proof), "That identity cannot claim that item");
        require(identityValidated(identityTokenId), "You are not the owner of that identity ");

        _safeMint(_msgSender(), spotOnLeaderboard);

        //Set the identityMinters value to tokenId for this address so the identity cannot mint again
        _identityClaims[identityTokenId] = spotOnLeaderboard;

        //Set the s2 identity that minted this token for reverse lookup
        _tokenMintedByIdentity[spotOnLeaderboard] = identityTokenId;
    }

    function buyItems() public nonReentrant
    {
        require(address(bytesContract) != address(0), "Items cannot be bought yet");
        IByteContract iBytes = IByteContract(bytesContract);
        iBytes.burn(_msgSender(), itemCost);
        _safeMint(_msgSender(), boughtItemsCount + 4501);
        boughtItemsCount++;
    }

    //Used only in cases where the original identity owner does not or cannot claim an item
    function emergencyClaim(uint256 identityId, uint256 tokenId) public onlyOwner {
        require(tokenId > 0 && tokenId < 4501, "Token ID invalid");
        require(!_exists(tokenId), "That token already exists");
        require(_identityClaims[identityId] == 0, "That identity has already claimed");

        _safeMint(owner(), tokenId);
        _identityClaims[identityId] = tokenId;
    }

    function identityValidated(uint256 identityId) internal view returns (bool) {
        require(_identityClaims[identityId] == 0, "This ID has minted");
        ERC721Enumerable identityEnumerable = ERC721Enumerable(identityContract);
        return(identityEnumerable.ownerOf(identityId) == _msgSender());
    }

    function whitelistValidated(uint256 identityTokenId, uint256 leaderboardSpot,uint256 index, bytes32[] memory proof) internal view returns (bool) {

        // Compute the merkle root
        bytes32 node = keccak256(abi.encodePacked(index, identityTokenId, leaderboardSpot));
        uint256 path = index;
        for (uint16 i = 0; i < proof.length; i++) {
            if ((path & 0x01) == 1) {
                node = keccak256(abi.encodePacked(proof[i], node));
            } else {
                node = keccak256(abi.encodePacked(node, proof[i]));
            }
            path /= 2;
        }

        // Check the merkle proof against the root hash array
        for(uint i = 0; i < _rootHash.length; i++)
        {
            if (node == _rootHash[i])
            {
                return true;
            }
        }

        return false;
    }

    function setItemMintActive() public onlyOwner {
        itemMintActive = !itemMintActive;
    }

    function setItemCost(uint256 _cost) public onlyOwner {
        itemCost = _cost;
    }

    function setContract(address contractAddress) public onlyOwner {
        itemMint = contractAddress;
    }

    function setIdentityAddress(address contractAddress) public onlyOwner {
        identityContract = contractAddress;
    }

    function setBytesAddress(address contractAddress) public onlyOwner {
        bytesContract = contractAddress;
    }
    
    
    function toString(uint256 value) internal pure returns (string memory) {
    // Inspired by OraclizeAPI's implementation - MIT license
    // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }
    
    constructor() ERC721("Neo Tokyo Outer Item Caches", "NTOITEM") Ownable() {
        _rootHash = new bytes32[](3);
        _rootHash[0] = 0x86e4637379cb8024ef5fb0057ff9ac66a1e2cfdea857e885d06300e5d51ae422;
        _rootHash[1] = 0x57565c6c4f4d123ebbd263aec5abc4913540333fe0d0f320668cf61b603c5856;
        _rootHash[2] = 0x98125ab05cdded40d733f4dab397ddee91e0f183528945948fddf816f234d50a;

        bytesContract = 0x7d647b1A0dcD5525e9C6B3D14BE58f27674f8c95;
        identityContract = 0x698FbAACA64944376e2CDC4CAD86eaa91362cF54;
        itemMint = 0xAE3aBff59A0a0EC71c650364F59518D7af74d2eC;
    }
}
function emergencyClaim(uint256 identityId, uint256 tokenId) public onlyOwner {
    require(tokenId > 0 && tokenId < 4501, "Token ID invalid");
    require(!_exists(tokenId), "That token already exists");
    require(_identityClaims[identityId] == 0, "That identity has already claimed");

    _safeMint(owner(), tokenId);
    _identityClaims[identityId] = tokenId;
}

//Used only in cases where the original identity owner does not or cannot claim an item