contract ConsoleNFT_Vault is ERC721Enumerable, ReentrancyGuard, Ownable, VRFConsumerBase {
	
	address dataContract;

    // Mapping for wallet addresses that have previously minted
    mapping(address => uint256) private _whitelistMinters;

	string internal baseTokenURI;
	string internal baseTokenURI_extension;
	
	uint public constant maxTokens = 500;
    uint public total = 0;
	uint mintCost = 0.05 ether;
	
	bool whitelistActive;
	bool sysAdminMinted;
	bool error404Minted;
	bool code200Minted;
	bool giveawaysMinted;
	
	address error404Address;
	address code200Address;
	address giveawaysAddress;
	
	bytes32 _rootHash;
	
	string[] private rarity = [
        "Legendary",
        "Epic",
        "Rare",
        "Uncommon",
        "Common"
    ];
	
	string[] private numberOfItemsInside = [
        "6",
        "5",
        "4",
        "3",
        "2"
    ];
	
	string[] private creditsMultiplier = [
        "Ultra",
        "Very high",
        "High",
        "Medium",
        "Low"
    ];
	
	string[] private creditsAmount = [
        "Ultra",
        "Very high",
        "High",
        "Medium",
        "Low"
    ];
	
	string[] private firstItemRarity = [
        "Legendary",
        "Epic",
        "Rare",
        "Uncommon",
        "Common"
    ];
	
	string[] private secondItemRarity = [
        "Legendary",
        "Epic",
        "Rare",
        "Uncommon",
        "Common"
    ];
	
	string[] private firstItemClass = [
        "Computer",
        "Weapon",
        "Keylogger",
        "Rainbow tables",
        "Packet sniffer",
        "Smartphone",
        "De-auth device",
        "NFC device",
        "RFID blocker",
        "Drone",
        "Screen crab",
        "Programmable chip",
        "Lockpick",
        "Land",
        "Armor",
        "Clothing",
        "CD device",
        "USB device",
        "Floppy disc",
        "- None -"
    ];

	
	function getRarity(uint256 tokenId) public view returns (string memory) {
        require(_exists(tokenId), "Nonexistent token");
		
        string memory output;
		
		SourceData source_data = SourceData(dataContract);
		
        output = rarity[source_data.getRarity(tokenId)];
		
        return output;
    }
	
	function getNumberOfItemsInside(uint256 tokenId) public view returns (string memory) {
        require(_exists(tokenId), "Nonexistent token");
		
        string memory output;
		
		SourceData source_data = SourceData(dataContract);
		
        output = numberOfItemsInside[source_data.getNumberOfItems(tokenId)];
		
        return output;
    }
	
	function getCreditsMultiplier(uint256 tokenId) public view returns (string memory) {
        require(_exists(tokenId), "Nonexistent token");
		
        string memory output;
		
		SourceData source_data = SourceData(dataContract);
		
        output = creditsMultiplier[source_data.getCreditsMultiplier(tokenId)];
		
        return output;
    }
	
	function getCreditsAmount(uint256 tokenId) public view returns (string memory) {
        require(_exists(tokenId), "Nonexistent token");
		
        string memory output;
		
		SourceData source_data = SourceData(dataContract);
		
        output = creditsAmount[source_data.getCreditsAmount(tokenId)];
		
        return output;
    }
	
	function getFirstItemRarity(uint256 tokenId) public view returns (string memory) {
        require(_exists(tokenId), "Nonexistent token");
		
        string memory output;
		
		SourceData source_data = SourceData(dataContract);
		
        output = firstItemRarity[source_data.getFirstItemRarity(tokenId)];
		
        return output;
    }
	
	function getSecondItemRarity(uint256 tokenId) public view returns (string memory) {
        require(_exists(tokenId), "Nonexistent token");
		
        string memory output;
		
		SourceData source_data = SourceData(dataContract);
		
        output = secondItemRarity[source_data.getSecondItemRarity(tokenId)];
		
        return output;
    }
	
	function getFirstItemClass(uint256 tokenId) public view returns (string memory) {
        require(_exists(tokenId), "Nonexistent token");
		
        string memory output;
		
		SourceData source_data = SourceData(dataContract);
		
        output = firstItemClass[source_data.getFirstItemClass(tokenId)];
		
        return output;
    }
		
	// Setters
	function setSourceData(address _dataContract) external onlyOwner {
        dataContract = _dataContract;
    }
	
	function setBaseTokenURI(string memory _uri) external onlyOwner {
        baseTokenURI = _uri;
    }
	
	function setBaseTokenURI_extension(string memory _ext) external onlyOwner {
        baseTokenURI_extension = _ext;
    }
	
	function tokenURI(uint tokenId_) public view override returns (string memory) {
        require(_exists(tokenId_), "Query for non-existent token!");
        return string(abi.encodePacked(baseTokenURI, Strings.toString(tokenId_), baseTokenURI_extension));
    }
	
	function setError404Address(address _setAddress) external onlyOwner {
        error404Address = _setAddress;
    }
	
	function setCode200Address(address _setAddress) external onlyOwner {
        code200Address = _setAddress;
    }
	
	function setGiveawaysAddress(address _setAddress) external onlyOwner {
        giveawaysAddress = _setAddress;
    }
	
	function setRootHash(bytes32 rootHash) external onlyOwner {
        _rootHash = rootHash;
    }
	
	function setWhitelistState() external onlyOwner
    {
        whitelistActive = !whitelistActive;
    }
	
	// Claim
	function sysAdminClaim() public onlyOwner nonReentrant {
		require(sysAdminMinted == false, "Already minted!");
		
		sysAdminMinted = true;
		uint tokenId = getVRFRandomIndex();
        total++;
        _safeMint(owner(), tokenId);
    }
	
	function error404Claim() public nonReentrant {
		require(msg.sender == error404Address, "Not Error 404");
		require(error404Minted == false, "Already minted!");
		
		error404Minted = true;
		uint tokenId = getVRFRandomIndex();
        total++;
        _safeMint(msg.sender, tokenId);
    }
	
	function code200Claim() public nonReentrant {
		require(msg.sender == code200Address, "Not Code 200");
		require(code200Minted == false, "Already minted!");
		
		code200Minted = true;
		uint tokenId = getVRFRandomIndex();
        total++;
        _safeMint(msg.sender, tokenId);
    }
	
	function giveawaysClaim() public nonReentrant {
		// only 2, we can hardcode
		require(msg.sender == giveawaysAddress, "Not Giveaways wallet");
		require(giveawaysMinted == false, "Already minted!");
		
		giveawaysMinted = true;
		
		uint tokenId = getVRFRandomIndex();
        total++;
		_safeMint(msg.sender, tokenId);
		
		uint tokenId_second = getVRFRandomIndex();
        total++;
        _safeMint(msg.sender, tokenId_second);
    }
	
	function whitelistClaim(bytes32[] memory proof) public nonReentrant payable {
        require(whitelistActive, "The whitelist is not active yet");
        require(total < maxTokens, "All tokens have been already minted");
		require(msg.value >= mintCost, "Incorrect mint cost value");
		
		// Merkle tree validation
		bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
		require(MerkleProof.verify(proof, _rootHash, leaf), "Invalid proof");
		
        require(_whitelistMinters[_msgSender()] < 1, "You've already minted");
		
		uint tokenId = getVRFRandomIndex();
        total++;

        _safeMint(_msgSender(), tokenId);

        //Set the _whitelistMinters value to tokenId for this address as it has minted
        _whitelistMinters[_msgSender()] = tokenId;
    }
	
    constructor() ERC721("Console NFT Vaults", "Cnsl-NFT-V") VRFConsumerBase(
        0xf0d54349aDdcf704F77AE15b96510dEA15cb7952,
        0x514910771AF9Ca656af840dff83E8264EcF986CA
    )
	Ownable() {
		keyHash = 0xAA77729D3466CA35AE8D28B3BBAC7CC36A5031EFDC430821C02BC31A238AF445;
        fee = 2 * (10**18);
		
		_rootHash = 0x944df87eb207e113ec142134130993ec225e847b9341eff16eb0c99ed8eb620a;
		
		error404Address = 0x24Db9e45f6aC29175030A083B985C184A02c2d64;
		code200Address = 0x1C0d3B190B18b4452BD4d0928D7f425eA9A0B3F9;
		giveawaysAddress = 0x7e95c71bDF0E0526eA534Fb5191ceD999190c117;
    }
	

	////////////////////////// VRF /////////////////////////////
	
    bytes32 internal keyHash;
    uint internal fee;
    uint internal randomResult;
    
    // VRF Functions
    function getRandomNumber() public onlyOwner returns (bytes32 requestId) {
        require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK!");
        return requestRandomness(keyHash, fee);
    }
    function fulfillRandomness(bytes32 requestId, uint randomness) internal override {
        randomResult = randomness;
    }
    function getRandomResult() public view onlyOwner returns (uint) {
        return randomResult;
    }

    // >>> Now, the VRF is stored in [uint internal randomResult]
    ////////////////////////// VRF /////////////////////////////

    /////////////////////// Token ID Generator ////////////////////////
    // ** Thanks LarvaLabs for this big brain method. **             //
    // This function was orginally written by 0xInuarashi            //
    // and modified by SysAdmin to exclude Token ID,                 //
    // now including the usage of generated input from VRF.          //
    ///////////////////////////////////////////////////////////////////

    uint[maxTokens] internal indices;
    uint32 internal nonce;

    function getVRFRandomIndex() internal returns (uint) {
        require(randomResult != 0, "VRF Random Result has not been set!");
        uint _tokensRemaining = maxTokens - total; // require that this calculation is possible from all caller functions
        uint _maxIndex = _tokensRemaining == 0 ? 0 : _tokensRemaining - 1; // shorthand if for safety
        uint _rand = uint(keccak256(abi.encodePacked(randomResult, nonce, msg.sender, block.difficulty, block.timestamp))) % _tokensRemaining;
    
        uint _output = 0;

        _output = indices[_rand] != 0 ? indices[_rand] :_rand;
        indices[_rand] = indices[_maxIndex] == 0 ? _maxIndex : indices[_maxIndex];

        uint32 _nonceAdd = uint32(uint256(keccak256(abi.encodePacked(randomResult, nonce, msg.sender, block.difficulty, block.timestamp)))) % 10;
        nonce += _nonceAdd; 

        return _output;
    }

    /////////////////////// Token ID Generator ////////////////////////
	
	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);
    }
	
	// Withdraw Ether
    function withdrawEther() public onlyOwner {
        payable(msg.sender).transfer(address(this).balance); 
    }

}
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);
   }

/////////////////////// Token ID Generator ////////////////////////