// Contract: TravelerLoot
// Target function: function processing(uint256 id, address addr, uint8 percentage, bool isPositive) internal
lovakian", "Greek", "Hungarian"];
      elements[categories[6]] = ["2", "3", "4", "5", "6", "7", "8", "9","10","100","0","1"];
      elements[categories[7]] = ["Host", "Cook", "Writer", "DeeJay", "Employee", "Doctor", "Traveler", "Tour Guide", "Ship Pilot", "DAO Member", "Driver", "NFT flipper", "Meme creator", "Sales Manager", "Play 2 Earner", "NFT collector", "Hotel receptionist", "Hotel Manager", "Digital Nomad", "Crypto Trader", "Head of Growth", "PoS validator", "Lightning Network Developer", "Anonymous DeFi Protocol Lead", "Yacht owner (in bull markets)", "Web3 Engineer", "Blockchain Consultant", "Crypto VC", "Miner","Metaverse Realtor"];
      elements[categories[8]] = ["Hotel", "Apartment", "Hostel", "Tent", "BnB", "Guest House", "Chalet", "Cottage", "Boat", "Caravan", "Motorhome", "5 stars Hotel", "Suite in 5 Stars Hotel", "Tipi", "Tree House", "Bungalow", "Ranch", "Co-living", "Gablefront cottage", "Longhouse", "Villa", "Yurt", "Housebarn", "Adobe House", "Castle", "Rammed earth", "Underground living", "Venetian palace", "Igloo", "Trullo"];
      elements[categories[9]] = ["Pen", "eBook reader", "Water", "Cigarettes", "Swiss knife", "Mobile phone", "Notebook", "Laptop", "Digital Camera", "Lighter", "Earphones", "Beauty case", "Toothbrush", "Toothpaste", "Slippers", "Shirts", "Pants", "T-shirts", "Socks", "Underwear","Condoms"];

      detailsByAddress[PH_USERS] = Traveler({color:BLACK,familyType:"",familyName:"",counter:0,verified:true});
      detailsByAddress[PH_PATRONS] = Traveler({color:"#F87151",familyType:PATRON,familyName:"",counter:0,verified:true});
      detailsByAddress[PH_OG_LOOT] = Traveler({color:GOLD,familyType:PATRON,familyName:"Loot (for Adventurers)",counter:0,verified:true});
      detailsByAddress[PH_CONQUERORS] = Traveler({color:WHITE,familyType:CONQUEROR,familyName:"",counter:0,verified:true});
      detailsByAddress[PH_OWNER] = Traveler({color:BLACK,familyType:" ",familyName:"",counter:0,verified:true});

      whiteListedGuilds.push(ADDRESS_OG_LOOT);
      detailsByAddress[ADDRESS_OG_LOOT] = Traveler({color:BLACK,familyType:PLATINUM,familyName:"LOOT",counter:0,verified:true});
    }

    modifier checkStart() {
      require(blockActivation > 0 && block.number >= blockActivation,ERROR_COME_BACK_LATER);
      _;
    }

    /// @notice                     Everyone can claim an available tokenId for free (+ gas)
    function claim() external nonReentrant checkStart {
        require(owner() != _msgSender(),ERROR_OWNER_NOT_ALLOWED);
        uint16 adjusted = 1 + counterStandard + MAX_FOR_GUILDS + MAX_FOR_OWNER;
        require(adjusted <= MAX_ID, ERROR_TOKEN_ID_INVALID);
        processing(adjusted,PH_USERS,1,false);
        _safeMint(_msgSender(), adjusted);
        counterStandard++;
    }

    /// @notice                     Guilds can use this function to mint a forged Traveler Loot.
    ///                             Forged TLs are recognizable by a colored line on the NFT.
    ///                             When all the forgeable TLs are minted, the guild who forged
    ///                             the most becomes Conqueror. Its members gain access to
    ///                             claimByConquerors() function
    /// @param                      tokenId         derivative's tokenId owned by the user
    /// @param                      contractAddress derivative's address pretending to be guild
    function claimByGuilds(uint256 tokenId, address contractAddress) external nonReentrant checkStart{
        require(!conqueror.elected, ERROR_COMPETITION_ENDED);
        Traveler storage details = detailsByAddress[contractAddress];
        require(details.verified, ERROR_ADDRESS_NOT_VERIFIED);
        IERC721 looter = IERC721(contractAddress);
        require(tokenId > 0 && looter.ownerOf(tokenId) == _msgSender(), ERROR_NOT_THE_OWNER);
        if (details.counter == 0 && contractAddress != whiteListedGuilds[0]){
            details.color = pickAColor();
        }
        uint16 discretId = uint16(tokenId % MAX_FOR_GUILDS);

        if (counterGuild == MAX_FOR_GUILDS - 1 ){
            (conqueror.addr, conqueror.count) = whoIsWinning();
            conqueror.elected = true;
            detailsByAddress[PH_CONQUERORS].color = details.color;
            detailsByAddress[PH_CONQUERORS].familyName = details.familyName;
        }
        // after this mint, the price for patrons will be increased by 1%
        uint16 finalId = discretId == 0 ? MAX_FOR_GUILDS : discretId;
        processing(finalId, contractAddress, 1, true);
        _safeMint(_msgSender(), finalId);
        counterGuild++;
    }

    /// @notice                     Becoming a Patron. Requires payment
    function claimByPatrons() external payable nonReentrant checkStart{
      require(msg.value >= priceForPatrons, ERROR_LOW_VALUE);
      if (priceForPatrons < INITIAL_PRICE_FOR_PATRONS){
        priceForPatrons == INITIAL_PRICE_FOR_PATRONS;
      }
      // After this mint, the price for next patrons will be increased by 5%
      reservedMinting(PH_PATRONS, 5, true);
      counterPatrons++;
    }

    /// @notice                     - Loot (for Adventurers) holders can become Patrons for free if:
    ///                             . the Conqueror Guild is not yet elected,
    ///                             . or Dominik Hoffman (@ dhof on twitter) is still younger than 40 y/o.
    function claimByOGLooters() external nonReentrant checkStart{
      require(IERC721(whiteListedGuilds[0]).balanceOf(_msgSender()) > 0, ERROR_NOT_THE_OWNER);
      require(!conqueror.elected, ERROR_COMPETITION_ENDED);
      require(block.timestamp <= DISCOUNT_EXPIRATION, ERROR_DOM_40TH_BIRTHDAY);

      // After this mint, the price for patrons will be decreased by 5%
      reservedMinting(PH_OG_LOOT, 5, false);
      counterPatrons++;
    }

    /// @notice                     Conquerors can mint a CONQUEROR Traveler Loot for free + gas
    ///                             only valid if they have not already minted a PATRON type before
    function claimByConquerors() external nonReentrant checkStart{
        require(conqueror.elected, ERROR_COMPETITION_ONGOING);
        require(IERC721(conqueror.addr).balanceOf(_msgSender()) > 0, ERROR_NOT_THE_OWNER);
        // After this mint, the price for patrons is increased by 2%
        reservedMinting(PH_CONQUERORS, 2, true);
        counterPatrons++;
    }

    /// @notice                     Owner can claim its reserved tokenIds.
    function claimByOwner() external nonReentrant onlyOwner checkStart{
        uint16 adjusted = 1 + counterOwner + MAX_FOR_GUILDS;
        require(adjusted <= MAX_FOR_GUILDS + MAX_FOR_OWNER, ERROR_TOKEN_ID_INVALID);
        //after this mint, the price for patrons will remain the same
        processing(adjusted,PH_OWNER,0,true);
        _safeMint(_msgSender(), adjusted);
        counterOwner++;
    }

    /// @notice                     Owner can call this function to activate the mintings.
    ///                             Note that the activation is delayed of LOCK_TIME blocks
    function activateClaims() external onlyOwner{
      require (blockActivation == 0, ERROR_ALREADY_ACTIVATED);
      blockActivation = block.number + LOCK_TIME;
    }

    /// @notice                     Owner can order the withdraw all the funds coming from Patron mints
    ///                             to the Treasurer address previously set.
    function withdraw() external onlyOwner {
      require(block.number >= treasurer.blockNumber, ERROR_COME_BACK_LATER);
      canChangeTreasurer = true; //release treasurer
      payable(treasurer.current).transfer(address(this).balance);
    }


    /// @notice                     . Owner calls this function with lockTreasurer = true if the intent is
    ///                             to do a donation to a public-good project. Lock guarantees that nobody
    ///                             (not even the owner) can change treasurer till the donation is made.
    ///                             . Owner calls this function with lockTreasurer = false if the intent is
    ///                             to keep open the chance to change the treasurer even if no any
    ///                             withdrawal has been performed in the meanwhile.
    /// @param      newAddress      the new treasurer address
    /// @param      lockTreasurer   true if the the treasurer has to be locked till one withdraw is performed
    function setTreasurer(address newAddress, bool lockTreasurer) external onlyOwner{
      require(canChangeTreasurer, ERROR_WITHDRAW_NEEDED); //this is a safety measure for when donating to public goods
      if (lockTreasurer){
        canChangeTreasurer = false;
      }
      uint256 blockNumber = block.number + LOCK_TIME;
      treasurer.old = treasurer.current;
      treasurer.current = newAddress;
      treasurer.blockNumber = blockNumber;
    }

    /// @notice                     You can register new Guilds to the whitelist, if:
    ///                             - You own at least 50 Traveler Loots in your address
    ///                             - Or if you have mint a Patron version by using claimByPatrons() or
    ///                             claimByOGLooters() functions (no matter if you dont own them anymore)
    ///                             - Or if you are the owner of this Contract
    ///                             NOTE: no Guilds can be registered once the competition has ended
    /// @param      addresses       List of contract addresses to be whitelisted for the game.
    ///                             They'll be allowed to mint GUILD type Traveler Loot
    function addNewGuildToWhiteList(address[] calldata addresses) nonReentrant external {
        require(!conqueror.elected, ERROR_COMPETITION_ENDED);
        for (uint8 j = 0; j< addresses.length; j++){
            address addr = addresses[j];
            require(address(this) != addr, ERROR_CANNOT_ADD_THIS_CONTRACT);
            require(IERC721(address(this)).balanceOf(_msgSender()) > 50
                        || _exists(uint160(_msgSender()))
                        || owner() == _msgSender()
                    ,ERROR_NOT_ENTITLED);
            for (uint16 i = 0; i < whiteListedGuilds.length; i++){
                require(whiteListedGuilds[i] != addr, ERROR_GUILD_ALREADY_WHITELISTED);
            }
            whiteListedGuilds.push(addr);
            detailsByAddress[addr] = Traveler({color:BLACK,familyType:GUILD,familyName:familyName(addr),counter:0,verified:true});
        }
    }

    /// @notice                     Given a Guild address, returns the count for that addr
    /// @param      addr            Address of the Guild to be checked (the contract address)
    /// @return                     Number of the NFT minted from the members of the given Guild address
    function getCounterForAddress(address addr) external view returns (uint256){
        Traveler memory details = detailsByAddress[addr];
        require(details.verified, ERROR_ADDRESS_NOT_VERIFIED);
        return details.counter;
    }

    /// @notice                     Call this function if you want to know the list of the current Guilds
    /// return                      Array containing the addresses of all the whitelisted guilds
    function getWhiteListedGuilds() external view returns (address[] memory){
        return whiteListedGuilds;
    }

    /// @notice                     Call this function to see the element for a given tokenId and categoryId
    /// @param      tokenId         The id of the Traveler Loot you want to check
    /// @param      categoryId      The category of the Traveler Loot you want to check
    /// @return                     The element of the given Token Id for the given categoryId
    function getElement(uint256 tokenId, uint8 categoryId) public view returns (string memory){
      return pluck(tokenId, categories[categoryId], elements[categories[categoryId]]);
    }

    /// @notice                     Provides metadata and image of the PATRONS and CONQUEROR
    /// @param      addr            Address of the user who minted the PATRON or CONQUEROR you are checking
    /// @return                     The json containing the NFT info
    function addressURI(address addr) external view returns (string memory){
        return tokenURI(uint160(addr));
    }

    /// @notice                     Provides metadata and image of the Traveler Loot
    /// @param      tokenId         Token if of the Traveler Loot to process
    /// @return                     The json containing the NFT info
    function tokenURI(uint256 tokenId) override public view returns (string memory) {
        Traveler memory details = detailsByAddress[addressList[tokenId]];
        string[4] memory parts;
        parts[0] = string(abi.encodePacked('<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><style>.b { fill:white; font-family: serif; font-size: 14px; }</style> <rect width="100%" height="100%" fill="black" /><text x="10" y="20" class="b">'));
        parts[1] = string(abi.encodePacked(getElement(tokenId,0),'</text><text x="10" y="40" class="b">',getElement(tokenId,1),'</text><text x="10" y="60" class="b">',getElement(tokenId,2),'</text><text x="10" y="80" class="b">',getElement(tokenId,3),'</text><text x="10" y="100" class="b">',getElement(tokenId,4),'</text><text x="10" y="120" class="b">',getElement(tokenId,5)));
        parts[2] = string(abi.encodePacked('</text><text x="10" y="140" class="b">',getElement(tokenId,6),'</text><text x="10" y="160" class="b">',getElement(tokenId,7),'</text><text x="10" y="180" class="b">',getElement(tokenId,8),'</text><text x="10" y="200" class="b">',getElement(tokenId,9),'</text>'));
        parts[3] = string(abi.encodePacked('<line x1="0" x2="350" y1="300" y2="300" stroke="',details.color,'" stroke-width="4"/>','<text x="340" y="294" text-anchor="end" class="b">',details.familyType,'</text></svg>'));

        string memory compact = string(abi.encodePacked(parts[0], parts[1], parts[2],parts[3]));
        string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Traveler #', toString(tokenId), '", "description": "Traveler Loot is a Loot derivative for the Travel Industry, generated and stored on chain. Feel free to use the Traveler Loot in any way you want", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(compact)), '","attributes":[',metadata(tokenId,details),']}'))));

        return string(abi.encodePacked('data:application/json;base64,', json));
    }

    /// @notice                     Call this method to check the Guild which is owning the most Forged Traveler Loot
    ///
    /// @return                     The address of the guild who has the most Forged Traveler Loot so far
    /// @return                     The amount of Forged Traveler Loot minted by the Winning Guild so far
    function whoIsWinning() public view returns (address, uint16){
      if (conqueror.elected){
        return (conqueror.addr,conqueror.count);
      }
      address winningLoot = whiteListedGuilds[0];
      uint16 winningCount = uint16(detailsByAddress[winningLoot].counter);

      for (uint8 i = 1; i < whiteListedGuilds.length; i++){
        address addr = whiteListedGuilds[i];
        (winningLoot, winningCount) = checkWinning(winningLoot,winningCount, addr, uint16(detailsByAddress[addr].counter));
      }
      return (winningLoot, winningCount);
    }

    /// @notice                     Call this function to get a random color using block difficulty, timestamp as seed
    /// @return                     A string containing the HEX number of a random color
    function pickAColor() internal view returns (string memory){
        string[16] memory list = ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];
        string memory color = "#";
        for (uint8 i=0; i<6;i++){
          uint rand = uint(keccak256(abi.encodePacked(block.difficulty, block.timestamp, i )));
          color = string(abi.encodePacked(color,list[rand % list.length]));
        }
        return color;
    }

    /// @notice                     This function calls an external NFT contract and gets the symbol to be used into the metadata
    /// @param      addr            The address of the contract address to whitelist
    /// @return                     The symbol of the given external contract address
    function familyName(address addr) internal view returns (string memory){
      return IERC721Metadata(addr).symbol();
    }

    function random(string memory input) internal pure returns (uint256) {
        return uint256(keccak256(abi.encodePacked(input)));
    }