// Contract: TravelerLoot
// Target function: function addNewGuildToWhiteList(address[] calldata addresses) nonReentrant external
 TripsCommunity
/// @notice Text-based NFTs thought for the Travel Industry appannage. Hotels, restaurants, hospitality etc can use them both in the real or metaverse worlds
contract TravelerLoot is ERC721Enumerable, ReentrancyGuard, Ownable {

    uint256 private constant DISCOUNT_EXPIRATION = 1790553599; //Sun 27 Sep 2026 21:59:59 UTC
    string private constant WHITE = "white";
    string private constant BLACK = "black";
    string private constant GOLD = "gold";
    string private constant PLATINUM = "#D5D6D8";
    string private constant GUILD = "GUILD";
    string private constant PATRON = "PATRON";
    string private constant CONQUEROR = "CONQUEROR";
    uint160 private constant INITIAL_PRICE_FOR_PATRONS = 1 ether;
    string private constant ERROR_TOKEN_ID_INVALID = "Token ID invalid";
    string private constant ERROR_ADDRESS_NOT_VERIFIED = "Address not verified. Try another";
    string private constant ERROR_NOT_THE_OWNER = "You do not own token(s) of the address";
    string private constant ERROR_DOM_40TH_BIRTHDAY = "Only valid till Dom's 40th bday";
    string private constant ERROR_LOW_VALUE = "Set a higher value";
    string private constant ERROR_COMPETITION_ENDED = "Competition has ended. Check the Conqueror!";
    string private constant ERROR_COMPETITION_ONGOING = "Conqueror not yet elected!";
    string private constant ERROR_OWNER_NOT_ALLOWED = "Use claimByOwner() instead";
    string private constant ERROR_ALREADY_ACTIVATED = "Already activated";
    string private constant ERROR_COME_BACK_LATER = "Come back later";
    string private constant ERROR_WITHDRAW_NEEDED = "Treasurer already changed. Perform a withdrawal before changing it";
    string private constant ERROR_GUILD_ALREADY_WHITELISTED = "Guild already whitelisted!";
    string private constant ERROR_CANNOT_ADD_THIS_CONTRACT = "Not possible";
    string private constant ERROR_NOT_ENTITLED = "Check conditions before whitelisting";

    address private constant INITIAL_TREASURER = 0xce73904422880604e78591fD6c758B0D5106dD50; //TripsCommunity address
    address private constant ADDRESS_OG_LOOT = 0xFF9C1b15B16263C61d017ee9F65C50e4AE0113D7;
    address private constant PH_USERS = address(1);
    address private constant PH_PATRONS = address(2);
    address private constant PH_OG_LOOT = address(3);
    address private constant PH_CONQUERORS = address(4);
    address private constant PH_OWNER = address(0);

    uint16 public constant MAX_ID = 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;
    }