contract Encounter is ERC721Enumerable, ReentrancyGuard, Ownable {
    
        uint256 private currentId = 1;
        
        uint256 private uniqueId = (random("WORLDEATERUNIQUEID")%6666)+1;
        bool public uniqueMinted = false;

        uint256 public price = 20000000000000000; //0.02 ETH
        uint256 public characterOrLootPrice = 10000000000000000; //0.01 ETH

        //Loot Contract
        address public lootAddress = 0xFF9C1b15B16263C61d017ee9F65C50e4AE0113D7;
        AdventurerInterface public lootContract = AdventurerInterface(lootAddress);
        
        //Characters Contract
        address public characterAddress = 0x7403AC30DE7309a0bF019cdA8EeC034a5507cbB3;
        AdventurerInterface public characterContract = AdventurerInterface(characterAddress);
        
        string[] private commonDescriptors = [
        "Empty",
        "Chilling",
        "Clammy",
        "cramped",
        "Rotten",
        "Rusty",
        "Deserted",
        "Gloomy",
        "Dark",
        "Bright",
        "Hot",
        "Slippery",
        "Slimy",
        "Sandy",
        "Cold",
        "Damp",
        "Fancy",
        "Remote",
        "Regal",
        "Small",
        "Big",
        "Charming",
        "Quiet",
        "Dirty",
        "Traditional",
        "Sunny",
        "Disgusting",
        "Stinking",
        "Draining"
    ];
    
        string[] private rareDescriptors = [
        "Filthy",
        "Frozen",
        "Decaying",
        "Neglected",
        "Weathered And Tough",
        "Magical",
        "Spongy and Wet",
        "Large And Bright",
        "Creepy",
        "Ancient",
        "Mystical",
        "Strange and Mysterious",
        "Ineffable",
        "Radiant"
    ];
    
        string[] private legendaryDescriptors = [
        "Dilapidated",
        "Petrifying",
        "Shadowy and Silent",
        "Twisted",
        "Repulsive",
        "Misty and Murky",
        "Glowing",
        "Enchanted"
    ];
    
        string[] private commonLocations = [
        "Dungeon",
        "Courtyard",
        "Forest",
        "Basement",
        "Room",
        "Field",
        "Meadows",
        "Dark closet",
        "General Store",
        "Swamp",
        "Vast Desert",
        "Goblin Valley",
        "Sewer Tunnel",
        "Ruined City",
        "Watch Tower",
        "Cave",
        "Empty Stairwell",
        "Mountain cave",
        "Goblin encampment",
        "Town Market",
        "Wooded Grove",
        "Coastal Clearing"
    ];
    
        string[] private rareLocations = [
        "Tower",
        "Abandoned Village",
        "Iron Woods",
        "Elven Forest",
        "Dark Plains",
        "High Castle",
        "Wizard Tower",
        "Abandoned Castle",
        "Toll Bridge",
        "Magical Island",
        "Lighthouse",
        "Forge",
        "Volcano Tube",
        "Wistful Wild",
        "Lair of a Giant",
        "Badland Highway"
    ];
    
        string[] private legendaryLocations = [
        "Hidden Temple",
        "Giant Castle",
        "Enchanted Dungeon",
        "Gold Bank",
        "Bustling Barracks",
        "Mighty Citadel",
        "Sacred Sanctuary",
        "Fungal Forrest",
        "Place Between Time",
        "Astral Plane",
        "Pocket Dimension",
        "Ghost Ship"
    ];
    
        string[] private uniqueLocations = [
        '<tspan fill="gold">The Edge of Time and Space</tspan>'
    ];
    
        string[] private commonCreatures = [
        "Goblin",
        "Troll",
        "Wolf",
        "Wizard",
        "Spider",
        "Zombie",
        "City Guard",
        "Cave slime",
        "Thug",
        "Troll Runt",
        "Dwarf",
        "Mugger",
        "Grizzly Bear",
        "Giant Spider",
        "Wild Dog",
        "Wraith",
        "Minotaur",
        "Ghoul",
        "Mudskipper",
        "Giant Scorpion",
        "Giant Rat",
        "Sphinx",
        "Vampire",
        "Basilisks",
        "Orc"
    ];
    
        string[] private rareCreatures = [
        "Hobgoblin",
        "Seething Devil",
        "Hellhound",
        "Cyclops",
        "Ice Dragon",
        "Fire Dragon",
        "Skeleton Dragon",
        "Werewolf",
        "Great Demon",
        "Mummy",
        "Abyssal Demon",
        "ice Prince",
        "Scarab Swarm",
        "High Priest",
        "Gargoyle",
        "Night Shifter",
        "High Elf",
        "Night Elf"
    ];
    
        string[] private legendaryCreatures = [
        "Yeti",
        "Lich King",
        "Kraken",
        "Dark Wyvern",
        "Revenant Beast",
        "Shadow Lord",
        "Onyx Warlord",
        "Zombie Lord",
        "Scarab Queen",
        "Wyvern of Undying"
    ];
    
        string[] private uniqueCreatures = [
        '<tspan fill="gold">The Great World Eater</tspan>'
    ];
        
        string[] private creatureDescriptors = [
        '<tspan fill="red">Supreme</tspan>',
        '<tspan fill="darkviolet">Exalted</tspan>',
        '<tspan fill="crimson">Enraged</tspan>',
        '<tspan fill="greenyellow">Diseased</tspan>',
        '<tspan fill="lightsalmon">Gigantic</tspan>',
        '<tspan fill="orangered">Firey</tspan>',
        '<tspan fill="goldenrod">Ancient</tspan>',
        '<tspan fill="darkmagenta">Cursed</tspan>',
        '<tspan fill="aquamarine">Flying</tspan>',
        '<tspan fill="slategrey">Invisible</tspan>',
        '<tspan fill="firebrick">Engulfed</tspan>',
        '<tspan fill="steelblue">Frost</tspan>'
    ];
    
    receive() external payable {}
        
    
    function random(string memory input) internal pure returns (uint256) {
        return uint256(keccak256(abi.encodePacked(input)));
    }

    
    
    //returns a location in svg format <text>output</text>
    function pluckLocation(uint256 tokenId) internal view returns (string memory, uint256) {
        uint256 rarity = 0;
        
        uint256 rand1 = random(string(abi.encodePacked("LOCATION", toString(tokenId))));
        uint256 rand2 = random(string(abi.encodePacked("LOCATIONDESCRIPTOR", toString(tokenId))));
        
        string memory suffix = '</text>';
        
        string memory location;
        string memory descriptor;
        string memory output;
        
        uint256 locationRarity = rand1 % 100; //0-99
        
        if(locationRarity < 60)
        {
            //common
            rarity = rarity + 1;
            location = commonLocations[rand1 % commonLocations.length];
        }
        else if(locationRarity < 90)
        {
            //rare
            rarity = rarity + 5;
            location = rareLocations[rand1 % rareLocations.length];
        }
        else
        {
            //legendary
            rarity = rarity + 15;
            location = legendaryLocations[rand1 % legendaryLocations.length];
        }
        
        uint256 descriptorRarity = rand2 % 100;
        
        if(descriptorRarity < 60)
        {
            //common
            rarity = rarity + 1;
            descriptor = commonDescriptors[rand2 % commonDescriptors.length];
        }
        else if(descriptorRarity < 90)
        {
            //rare
            rarity = rarity + 5;
            descriptor = rareDescriptors[rand2 % rareDescriptors.length];
        }
        else
        {
            //legendary
            rarity = rarity + 15;
            descriptor = legendaryDescriptors[rand2 % legendaryDescriptors.length];
        }
        output = string(abi.encodePacked(descriptor, " ", location, suffix));
        
        return (output , rarity);
    }
    
    function pluckCreature(uint256 tokenId, uint256 yValue) internal view returns (string memory, uint256) {
        uint256 rarity = 0;
        
        uint256 rand1 = random(string(abi.encodePacked("CREATURE", toString(tokenId))));
        uint256 rand2 = random(string(abi.encodePacked("CREATUREMODIFIER", toString(tokenId))));
        
        string memory prefix = string(abi.encodePacked('<text x="30" y="', toString(yValue), '" class="base">'));
        string memory suffix = '</text>';
        
        string memory creature;
        string memory output;
        
        uint256 creatureRarity = rand1 % 100; //0-99
        
        if(creatureRarity < 60)
        {
            //common
            rarity = rarity + 1;
            creature = commonCreatures[rand1 % commonCreatures.length];
        }
        else if(creatureRarity < 90)
        {
            //rare
            rarity = rarity + 5;
            creature = rareCreatures[rand1 % rareCreatures.length];
        }
        else
        {
            //legendary
            rarity = rarity + 15;
            creature = legendaryCreatures[rand1 % legendaryCreatures.length];
        }
        
        if((rand2 % 100) < 10) //add a descriptr
        {
            rarity = rarity + 15;
            creature = string(abi.encodePacked(creatureDescriptors[rand2 % creatureDescriptors.length], " ", creature));
        }
        output = string(abi.encodePacked(prefix, creature, suffix));
        
        
        return (output, rarity);
    }
    
    function getRarityLevel(uint256 rarity) internal view returns (string memory) {
        if(rarity < 20)
        {
            return("Common");
        }
        else if(rarity < 35)
        {
            return("Rare");
        }
        else if(rarity < 55)
        {
            return("Legendary");
        }
        else
        {
            return("Godlike");
        }
    }
    
    function getNumCreatures(uint256 tokenId) internal view returns (uint256)
    {
        uint256 rand1 = random(string(abi.encodePacked("CREATURE", toString(tokenId))));
        
        uint256 numberRarity = rand1 % 100;
        
        if(numberRarity < 50)
        {
            return 2;
        }
        else if(numberRarity < 80)
        {
            return 3;
        }
        else if(numberRarity < 95)
        {
            return 4;
        }
        else
        {
            return 5;
        }
    }

    function tokenURI(uint256 tokenId) override public view returns (string memory) {
        string[8] memory parts;
        
        
        uint256 rarity = 0;
        uint256 rarityHolder = 0;
        
        uint256 numCreatures = getNumCreatures(tokenId);
        
        
        parts[0] = '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><style>.base { fill: white; font-family: serif; font-size: 14px; }</style><rect width="100%" height="100%" fill="black" /><text x="10" y="20" class="base">';

        (parts[1], rarity) = pluckLocation(tokenId); //location

        (parts[2], rarityHolder) = pluckCreature(tokenId+12345, 40); //creature 1
        rarity = rarity + rarityHolder;

        (parts[3], rarityHolder) = pluckCreature(tokenId+12345+12345, 60); //creature 2
        rarity = rarity + rarityHolder;
        
         if(numCreatures >= 3)
        {
            (parts[4], rarityHolder) = pluckCreature(tokenId+12345+12345+12345, 80); //creature 3
            rarity = rarity + rarityHolder;
        }
        else
        {
            parts[4] = '';
        }
        
        
         if(numCreatures >= 4)
        {
            (parts[5], rarityHolder) = pluckCreature(tokenId+12345+12345+12345+12345, 100); //creature 4
            rarity = rarity + rarityHolder;
        }
        else
        {
            parts[5] = '';
        }
        

         if(numCreatures >= 5)
        {
            (parts[6], rarityHolder) = pluckCreature(tokenId+12345+12345+12345+12345+12345, 120); //creature 5
            rarity = rarity + rarityHolder;
        }
        else
        {
            parts[6] = '';
        }

        parts[7] = '</svg>';
        
        string memory rarityLevel = getRarityLevel(rarity);
        
        if(tokenId == uniqueId)
        {
            rarityLevel = "1 of 1";
            rarity = 180;
            parts[1] = '<tspan fill="gold">The Edge of Time and Space</tspan></text>';
            parts[2] = '<text x="30" y="40" class="base"><tspan fill="gold">The Great World Eater</tspan></text>';
            parts[3] = '';
            parts[4] = '';
            parts[5] = '';
            parts[6] = '';
        }

        string memory output = string(abi.encodePacked(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7]));
        
        string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Encounter #', toString(tokenId), '", "description": "Encounters are randomized adventure encounters generated and stored on chain. Stats, images, and other functionality are intentionally omitted for others to interpret. Feel free to use Encounters in any way you want. Encounters is inspired by and compatable with Loot (for Adventurers) and Characters (for Adventurers)", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(output)), '","attributes":[{"trait_type":"Rarity", "value": "', rarityLevel, '"},{"trait_type": "Rarity Value","value":', toString(rarity), '}]','}'))));
        output = string(abi.encodePacked('data:application/json;base64,', json));
        
        return output;
    }

    function mint() public payable nonReentrant {
        require(currentId < 6666, "No more tokens to mint");
        require(price <= msg.value, "Ether value sent is not correct");
        _safeMint(_msgSender(), currentId);
        if(currentId == uniqueId) uniqueMinted = true;
        currentId = currentId + 1;
    }
    
    function multiMint(uint256 number) public payable nonReentrant {
        require((price * number) <= msg.value, "Ether value sent is not correct");
        require(currentId + number < 6666, "Mint would exceed maximum token amount");
        require(number < 20, "Cannot mint more than 20 tokens at once");
        for (uint256 i = 0; i < number; i++) {
            _safeMint(msg.sender, currentId);
            if(currentId == uniqueId) uniqueMinted = true;
            currentId = currentId + 1;
        }
    }
    
    function mintWithCharacterOrLoot() public payable nonReentrant {
        require(lootContract.balanceOf(msg.sender) > 0 || characterContract.balanceOf(msg.sender) > 0, "You need either a Loot or Character NFT for discounted minting");
        require(currentId < 6666, "No more tokens to mint");
        require(characterOrLootPrice <= msg.value, "Ether value sent is not correct");
        _safeMint(_msgSender(), currentId);
        if(currentId == uniqueId) uniqueMinted = true;
        currentId = currentId + 1;
    }
    
    function multiMintWithCharacterOrLoot(uint256 number) public payable nonReentrant {
        require(lootContract.balanceOf(msg.sender) > 0 || characterContract.balanceOf(msg.sender) > 0, "You need either a Loot or Character NFT for discounted minting");
        require((characterOrLootPrice * number) <= msg.value, "Ether value sent is not correct");
        require(currentId + number < 6666, "Mint would exceed maximum token amount");
        require(number < 20, "Cannot mint more than 20 tokens at once");
        for (uint256 i = 0; i < number; i++) {
            _safeMint(msg.sender, currentId);
            if(currentId == uniqueId) uniqueMinted = true;
            currentId = currentId + 1;
        }
    }
    
    function withdraw() public onlyOwner {
        payable(0x0fa3fB3931C854E01Ef97897bb8fE5902c61A2a2).transfer(address(this).balance);
    }
    
    function ownerClaim(uint256 tokenId) public nonReentrant onlyOwner {
        require(tokenId > 6666 && tokenId < 7001, "Token ID invalid");
        _safeMint(owner(), tokenId);
    }
    
    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("Encounters", "ENCOUNTERS") Ownable() {}
}
function pluckLocation(uint256 tokenId) internal view returns (string memory, uint256) {
    uint256 rarity = 0;
        
    uint256 rand1 = random(string(abi.encodePacked("LOCATION", toString(tokenId))));
    uint256 rand2 = random(string(abi.encodePacked("LOCATIONDESCRIPTOR", toString(tokenId))));
        
    string memory suffix = '</text>';
        
    string memory location;
    string memory descriptor;
    string memory output;
        
    uint256 locationRarity = rand1 % 100; //0-99
        
    if(locationRarity < 60)
    {
        //common
        rarity = rarity + 1;
        location = commonLocations[rand1 % commonLocations.length];
    }
    else if(locationRarity < 90)
    {
        //rare
        rarity = rarity + 5;
        location = rareLocations[rand1 % rareLocations.length];
    }
    else
    {
        //legendary
        rarity = rarity + 15;
        location = legendaryLocations[rand1 % legendaryLocations.length];
    }
        
    uint256 descriptorRarity = rand2 % 100;
        
    if(descriptorRarity < 60)
    {
        //common
        rarity = rarity + 1;
        descriptor = commonDescriptors[rand2 % commonDescriptors.length];
    }
    else if(descriptorRarity < 90)
    {
        //rare
        rarity = rarity + 5;
        descriptor = rareDescriptors[rand2 % rareDescriptors.length];
    }
    else
    {
        //legendary
        rarity = rarity + 15;
        descriptor = legendaryDescriptors[rand2 % legendaryDescriptors.length];
    }
    output = string(abi.encodePacked(descriptor, " ", location, suffix));
        
    return (output , rarity);
}

//returns a location in svg format <text>output</text>