// Contract: TravelerLoot
// Target function: function addressURI(address addr) external view returns (string memory)
10000;
    uint16 public constant MAX_FOR_OWNER = 100;
    uint16 public constant MAX_FOR_GUILDS = 900;
    uint16 public constant LOCK_TIME = 5760 * 3; //it's three days
    struct Traveler {
        string familyType;
        string familyName;
        string color;
        uint256 counter;
        bool verified;
    }

    struct Conqueror {
      address addr;
      uint16 count;
      bool elected;
    }

    struct Treasurer {
      address old;
      address current;
      uint256 blockNumber;
    }

    struct Initial{
        address addr;
        string symbol;
    }
    Conqueror public conqueror;
    Treasurer public treasurer;
    bool public canChangeTreasurer = true;
    mapping(address => Traveler) public detailsByAddress;
    mapping(uint256 => address) public addressList;
    uint8 public counterOwner = 0;
    uint16 public counterStandard = 0;
    uint16 public counterGuild = 0;
    uint16 public counterPatrons = 0;
    uint160 public priceForPatrons = INITIAL_PRICE_FOR_PATRONS;
    uint256 public blockActivation = 0;
    string[] private categories = ["ENVIRONMENT","TALENT","PLACE","CHARACTER","TRANSPORT","LANGUAGE","EXPERIENCE","OCCUPATION","ACCOMMODATION","BAG"];

    address[] public whiteListedGuilds;
    mapping(string => string[]) elements;



    constructor() ERC721("TravelerLoot", "TRVLR") Ownable(){
      uint256 blockNumber = block.number;
      treasurer.old = address(0);
      treasurer.current = INITIAL_TREASURER;
      treasurer.blockNumber = blockNumber;
      elements[categories[0]] = ["Beaches", "Mountains", "Urban", "Countrysides", "Lakes", "Rivers", "Farms", "Tropical areas", "Snowy places", "Forests", "Historical cities", "Islands", "Wilderness", "Deserts", "Natural parks", "Old towns", "Lakes", "Villages", "Forests", "Coral Reefs", "Wetlands", "Rainforests", "Grasslands", "Chaparral"];
      elements[categories[1]] = ["Cooking", "Painting", "Basketball", "Tennis", "Football", "Soccer", "Climbing", "Surfing", "Photographer", "Fishing", "Painting", "Writing", "Dancing", "Architecture", "Singing", "Dancing", "Baking", "Running", "Sword-fighting", "Boxing", "Jumping", "Climbing", "Hiking", "Kitesurfing", "Sailing", "Comforting others", "Flipping NFTs", "Katana sword fighter", "Programming Solidity", "Creating Memes"];
      elements[categories[2]] = ["Eiffel Tower", "Colosseum", "Taj Mahal", "Forbidden City", "Las Vegas", "Sagrada Familia", "Statue of Liberty", "Pompeii", "Tulum", "St. Peter's Basilica", "Bangkok", "Tower of London", "Alhambra", "San Marco Square", "Ciudad de las Artes y las Ciencias", "Moscow Kremlin", "Copacabana", "Great Wall of China", "Havana", "Arc de Triomphe", "Neuschwanstein Castle", "Machu Picchu", "Gili Islands", "Maya Bay", "Etherscan", "0x0000000000000000000000000000000000000000", "::1", "42.452483,-6.051345","Parcel 0, CityDAO", "Wyoming"];
      elements[categories[3]] = ["Energetic", "Good-Natured", "Enthusiastic", "Challenging", "Charismatic", "Wise", "Modest", "Honest", "Protective", "Perceptive", "Providential", "Prudent", "Spontaneous", "Insightful", "Intelligent", "Intuitive", "Precise", "Sharing", "Simple", "Sociable", "Sophisticated", "Benevolent", "Admirable", "Brilliant", "Accessible", "Calm", "Capable", "Optimistic", "Respectful", "Responsible"];
      elements[categories[4]] = ["Train", "Car", "Airplane", "Cruise", "4 wheel drive car", "Bus", "Convertible car", "Bicycle", "Motorbike", "Campervan", "Trailer", "Sailboat", "Electric car", "Sidecar", "Scooter", "Tram", "Cinquecento", "Hitch-hiking", "VW Beetle", "VW Bus", "Truck", "Off-road Vehicle", "Cab", "Lambo", "Ferrari", "Rocket", "DeLorean", "Kia Sedona", "Magic carpet", "Broomstick"];
      elements[categories[5]] = ["English", "Mandarin Chinese", "Hindi", "Spanish", "Arabic", "French", "Russian", "Portuguese", "Indonesian", "German", "Japanese", "Turkish", "Korean", "Vietnamese", "Iranian Persian", "Swahili", "Javanese", "Italian", "Thai", "Filipino", "Burmese", "Polish", "Croatian", "Danish", "Serbian", "Slovenian", "Czech", "Slovakian", "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]]);
    }