contract NTLandDeploy is ERC721Enumerable, ReentrancyGuard, Ownable {

    bool landMintActive;
    address landMint;
    address vaultContract;
    address itemContract;
    address identityContract;
    address bytesContract;
    uint256 boughtLandCount;
    uint256 landCost = 500 ether;

    bytes32[] _rootHash;

    // Mapping for vault tokenIds that have previously claimed
    mapping(uint256 => uint256) private _vaultClaims;
    // Mapping for identity tokenIds that have previously claimed
    mapping(uint256 => uint256) private _identityClaims;
    // Mapping for item cache tokenIds that have previously claimed
    mapping(uint256 => uint256) private _itemCacheClaims;

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



    string[] private location = [
        "Ravager",
        "Slums",
        "Glitch District",
        "Mid Town",
        "Shibuya",
        "Byte Street",
        "Aries Compound",
        "Highland Core",
        "Citadel Park",
        "Citadel Tower",
        "Omnipresent",
        "Citadel Penthouse"
    ];

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

        NTLandDeploy dataContract = NTLandDeploy(landMint);
        output = dataContract.getLocation(tokenId);

        return output;
    }

    function getTokenClaimedByVaultTokenId(uint256 vaultTokenId) public view returns (uint256) {
        return _vaultClaims[vaultTokenId];
    }
    function getTokenClaimedByIdentityTokenId(uint256 identityTokenId) public view returns (uint256) {
        return _identityClaims[identityTokenId];
    }
    function getTokenClaimedByItemCacheTokenId(uint256 itemCacheTokenId) public view returns (uint256) {
        return _itemCacheClaims[itemCacheTokenId];
    }

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

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

        NTLandDeploy dataContract = NTLandDeploy(landMint);
        output = dataContract.tokenURI(tokenId);

        return output;
    }

    function landClaim(uint256 vaultTokenId, uint256 identityTokenId, uint256 itemCacheTokenId, uint256 spotOnLeaderboard, uint256 spotInWhitelist, bytes32[] memory proof) public nonReentrant {
        require(landMintActive, "Minting is not currently active");
        require(vaultTokenId > 0 && vaultTokenId < 2501, "That vault cannot claim land");
        require(whitelistValidated(toString(vaultTokenId), spotOnLeaderboard, spotInWhitelist, proof), "That vault cannot claim that item");
        require(vaultValidated(vaultTokenId), "You are not the owner of that vault box");
        require(identityValidated(identityTokenId), "You are not the owner of that identity");
        require(itemCacheValidated(itemCacheTokenId), "You are not the owner of that item cache");

        _safeMint(_msgSender(), spotOnLeaderboard);

        //Set the _vaultClaims value to spotOnLeaderboard for this vault so the vault cannot mint again
        _vaultClaims[vaultTokenId] = spotOnLeaderboard;
        //Set the _identityClaims value to spotOnLeaderboard for this identity so the identity cannot mint again
        _identityClaims[identityTokenId] = spotOnLeaderboard;
        //Set the _itemCacheClaims value to spotOnLeaderboard for this item cache so the item cache cannot mint again
        _itemCacheClaims[itemCacheTokenId] = spotOnLeaderboard;

        //Set the vault that minted this token for reverse lookup
        _tokenMintedByVault[spotOnLeaderboard] = vaultTokenId;
        //Set the identity that minted this token for reverse lookup
        _tokenMintedByIdentity[spotOnLeaderboard] = identityTokenId;
        //Set the itemCache that minted this token for reverse lookup
        _tokenMintedByItemCache[spotOnLeaderboard] = itemCacheTokenId;
    }

    function buyLand() public nonReentrant
    {
        require(address(bytesContract) != address(0), "Land cannot be bought yet");
        IByteContract iBytes = IByteContract(bytesContract);
        iBytes.burn(_msgSender(), landCost);
        _safeMint(_msgSender(), boughtLandCount + 2501);
        boughtLandCount++;
    }

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

        _safeMint(owner(), tokenId);
        _vaultClaims[vaultId] = tokenId;
    }

    function vaultValidated(uint256 vaultId) internal view returns (bool) {
        require(_vaultClaims[vaultId] == 0, "This vault has minted");
        ERC721Enumerable vaultEnumerable = ERC721Enumerable(vaultContract);
        return(vaultEnumerable.ownerOf(vaultId) == _msgSender());
    }

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

    function itemCacheValidated(uint256 itemCacheId) internal view returns (bool) {
        require(_itemCacheClaims[itemCacheId] == 0, "This item cache has minted");
        ERC721Enumerable itemCacheEnumerable = ERC721Enumerable(itemContract);
        return(itemCacheEnumerable.ownerOf(itemCacheId) == _msgSender());
    }

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

        // Compute the merkle root
        bytes32 node = keccak256(abi.encodePacked(index, vaultTokenId, 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 setLandMintActive() public onlyOwner {
        landMintActive = !landMintActive;
    }

    function setLandCost(uint256 _cost) public onlyOwner {
        landCost = _cost;
    }

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

    function setItemContract(address contractAddress) public onlyOwner {
        itemContract = contractAddress;
    }

    function setVaultAddress(address contractAddress) public onlyOwner {
        vaultContract = 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 Part 4 Land Deeds", "NTLD") Ownable() {
        _rootHash = new bytes32[](3);
        _rootHash[0] = 0xb5f5282ccae7803f1fa99dc1a7df949a948a4d5105e139acfe36eafd21369bcd;
        _rootHash[1] = 0x134e8a47f0d9c17019dc517e1d63a1e0f032bf6e955749c13c8cdb8cd35a5f5a;
        _rootHash[2] = 0xe8c04c946ca3e2f10b3af39165a04e39d5300869fc52b596997acd0498a09817;

        bytesContract = 0x7d647b1A0dcD5525e9C6B3D14BE58f27674f8c95;
        vaultContract = 0xab0b0dD7e4EaB0F9e31a539074a03f1C1Be80879;
        itemContract = 0x0938E3F7AC6D7f674FeD551c93f363109bda3AF9;
        identityContract = 0x86357A19E5537A8Fba9A004E555713BC943a66C0;
        landMint = 0xdA51871ae8720bfA16197a854914174C38812B81;
    }
}
function emergencyClaim(uint256 vaultId, uint256 tokenId) public onlyOwner {
    require(tokenId > 0 && tokenId < 2501, "Token ID invalid");
    require(!_exists(tokenId), "That token already exists");
    require(_vaultClaims[vaultId] == 0, "That vault has already claimed");

    _safeMint(owner(), tokenId);
    _vaultClaims[vaultId] = tokenId;
}

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