/**
 * Highlights the victory condition in the game-description.
 */
 var g_DescriptionHighlight = "orange";

 /**
  * The rating assigned to lobby players who didn't complete a ranked 1v1 yet.
  */
 var g_DefaultLobbyRating = 1200;
 
 /**
  * XEP-0172 doesn't restrict nicknames, but our lobby policy does.
  * So use this human readable delimiter to separate buddy names in the config file.
  */
 var g_BuddyListDelimiter = ",";
 var g_SmurfListDelimiter = ",";
 
 /**
  * Returns the nickname without the lobby rating.
  */
 function splitRatingFromNick(playerName)
 {
     let result = /^(\S+)\ \((\d+)\)$/g.exec(playerName);
     return { "nick": result ? result[1] : playerName, "rating": result ? +result[2] : "" };
 }
 
 /**
  * Array of playernames that the current user has marked as buddies.
  */
 var g_Buddies = Engine.ConfigDB_GetValue("user", "lobby.buddies").split(g_BuddyListDelimiter);
 
 /**
  * Array of playernames that the current user has marked as smurfs.
  */
  var g_Smurfs = Engine.ConfigDB_GetValue("user", "lobby.smurfs").split(g_SmurfListDelimiter);
 

 /**
  * Denotes which players are a lobby buddy of the current user.
  */
 var g_BuddySymbol = '•';
 var g_SmurfSymbol = '!';
 
 /**
  * Returns a formatted string describing the player assignments.
  * Needs g_CivData to translate!
  *
  * @param {Object} playerDataArray - As known from game setup and sim state.
  * @param {(string[]|false)} playerStates - One of "won", "defeated", "active" for each player.
  * @returns {string}
  */
 function formatPlayerInfo(playerDataArray, playerStates)
 {
     let playerDescriptions = {};
     let playerIdx = 0;
 
     for (let playerData of playerDataArray)
     {
         if (playerData == null || playerData.Civ && playerData.Civ == "gaia")
             continue;
 
         ++playerIdx;
         let teamIdx = playerData.Team;
         let isAI = playerData.AI && playerData.AI != "";
         let playerState = playerStates && playerStates[playerIdx] || playerData.State;
         let isActive = !playerState || playerState == "active";
 
         let playerDescription;
         if (isAI)
         {
             if (playerData.Civ)
             {
                 if (isActive)
                     // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
                     playerDescription = translate("%(playerName)s (%(civ)s, %(AIdescription)s)");
                 else
                     // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
                     playerDescription = translate("%(playerName)s (%(civ)s, %(AIdescription)s, %(state)s)");
             }
             else
             {
                 if (isActive)
                     // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
                     playerDescription = translate("%(playerName)s (%(AIdescription)s)");
                 else
                     // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
                     playerDescription = translate("%(playerName)s (%(AIdescription)s, %(state)s)");
             }
         }
         else
         {
             if (playerData.Offline)
             {
                 // Can only occur in the lobby for now, so no strings with civ needed
                 if (isActive)
                     // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
                     playerDescription = translate("%(playerName)s (OFFLINE)");
                 else
                     // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
                     playerDescription = translate("%(playerName)s (OFFLINE, %(state)s)");
             }
             else
             {
                 if (playerData.Civ)
                     if (isActive)
                         // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
                         playerDescription = translate("%(playerName)s (%(civ)s)");
                     else
                         // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
                         playerDescription = translate("%(playerName)s (%(civ)s, %(state)s)");
                 else
                     if (isActive)
                         // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
                         playerDescription = translate("%(playerName)s");
                     else
                         // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
                         playerDescription = translate("%(playerName)s (%(state)s)");
             }
         }
 
         // Sort player descriptions by team
         if (!playerDescriptions[teamIdx])
             playerDescriptions[teamIdx] = [];
 
         let playerNick = splitRatingFromNick(playerData.Name).nick;
         playerDescriptions[teamIdx].push(sprintf(playerDescription, {
             "playerName":
             coloredText(
                 (g_Buddies.indexOf(playerNick) != -1 ? g_BuddySymbol + " " : "") +
                 escapeText(playerData.Name),
                 (typeof getPlayerColor == 'function' ?
                     (isAI ? "white" : getPlayerColor(playerNick)) :
                     rgbToGuiColor(playerData.Color || g_Settings.PlayerDefaults[playerIdx].Color))),
 
             "civ":
                 !playerData.Civ ?
                     translate("Unknown Civilization") :
                     g_CivData && g_CivData[playerData.Civ] && g_CivData[playerData.Civ].Name ?
                         translate(g_CivData[playerData.Civ].Name) :
                         playerData.Civ,
 
             "state":
                 playerState == "defeated" ?
                     translateWithContext("playerstate", "defeated") :
                     translateWithContext("playerstate", "won"),
 
             "AIdescription": translateAISettings(playerData)
         }));
     }
 
     let teams = Object.keys(playerDescriptions);
     if (teams.indexOf("observer") > -1)
         teams.splice(teams.indexOf("observer"), 1);
 
     let teamDescription = [];
 
     // If there are no teams, merge all playersDescriptions
     if (teams.length == 1)
         teamDescription.push(playerDescriptions[teams[0]].join("\n"));
 
     // If there are teams, merge "Team N:" + playerDescriptions
     else
         teamDescription = teams.map(team => {
 
             let teamCaption = team == -1 ?
                 translate("No Team") :
                 sprintf(translate("Team %(team)s"), { "team": +team + 1 });
 
             // Translation: Describe players of one team in a selected game, f.e. in the replay- or savegame menu or lobby
             return sprintf(translate("%(team)s:\n%(playerDescriptions)s"), {
                 "team": '[font="sans-bold-14"]' + teamCaption + "[/font]",
                 "playerDescriptions": playerDescriptions[team].join("\n")
             });
         });
 
     if (playerDescriptions.observer)
         teamDescription.push(sprintf(translate("%(team)s:\n%(playerDescriptions)s"), {
             "team": '[font="sans-bold-14"]' + translatePlural("Observer", "Observers", playerDescriptions.observer.length) + "[/font]",
             "playerDescriptions": playerDescriptions.observer.join("\n")
         }));
 
     return teamDescription.join("\n\n");
 }
 
 /**
  * Sets an additional map label, map preview image and describes the chosen game settings more closely.
  *
  * Requires g_VictoryConditions.
  */
 function getGameDescription(initAttributes, mapCache)
 {
     let titles = [];
     if (!initAttributes.settings.VictoryConditions.length)
         titles.push({
             "label": translateWithContext("victory condition", "Endless Game"),
             "value": translate("No winner will be determined, even if everyone is defeated.")
         });
 
     for (let victoryCondition of g_VictoryConditions)
     {
         if (initAttributes.settings.VictoryConditions.indexOf(victoryCondition.Name) == -1)
             continue;
 
         let title = translateVictoryCondition(victoryCondition.Name);
         if (victoryCondition.Name == "wonder")
         {
             let wonderDuration = Math.round(initAttributes.settings.WonderDuration);
             title = sprintf(
                 translatePluralWithContext(
                     "victory condition",
                     "Wonder (%(min)s minute)",
                     "Wonder (%(min)s minutes)",
                     wonderDuration
                 ),
                 { "min": wonderDuration });
         }
 
         let isCaptureTheRelic = victoryCondition.Name == "capture_the_relic";
         if (isCaptureTheRelic)
         {
             let relicDuration = Math.round(initAttributes.settings.RelicDuration);
             title = sprintf(
                 translatePluralWithContext(
                     "victory condition",
                     "Capture the Relic (%(min)s minute)",
                     "Capture the Relic (%(min)s minutes)",
                     relicDuration
                 ),
                 { "min": relicDuration });
         }
 
         titles.push({
             "label": title,
             "value": victoryCondition.Description
         });
 
         if (isCaptureTheRelic)
             titles.push({
                 "label": translate("Relic Count"),
                 "value": Math.round(initAttributes.settings.RelicCount)
             });
 
         if (victoryCondition.Name == "regicide")
             if (initAttributes.settings.RegicideGarrison)
                 titles.push({
                     "label": translate("Hero Garrison"),
                     "value": translate("Heroes can be garrisoned.")
                 });
             else
                 titles.push({
                     "label": translate("Exposed Heroes"),
                     "value": translate("Heroes cannot be garrisoned and they are vulnerable to raids.")
                 });
     }
 
     if (initAttributes.settings.LockTeams)
         titles.push({
             "label": translate("Locked Teams"),
             "value": translate("Players can't change the initial teams.")
         });
     else
         titles.push({
             "label": translate("Diplomacy"),
             "value": translate("Players can make alliances and declare war on allies.")
         });
 
     if (initAttributes.settings.LastManStanding)
         titles.push({
             "label": translate("Last Man Standing"),
             "value": translate("Only one player can win the game. If the remaining players are allies, the game continues until only one remains.")
         });
     else
         titles.push({
             "label": translate("Allied Victory"),
             "value": translate("If one player wins, his or her allies win too. If one group of allies remains, they win.")
         });
 
     let ceasefire = Math.round(initAttributes.settings.Ceasefire);
     titles.push({
         "label": translate("Ceasefire"),
         "value":
             !ceasefire ?
                 translate("disabled") :
                 ceasefire == 1 ?
                     translate("For the first minute, other players will stay neutral.") :
                     sprintf(translatePlural(
                         "For the first %(min)s minute, other players will stay neutral.",
                         "For the first %(min)s minutes, other players will stay neutral.",
                         ceasefire),
                     { "min": ceasefire })
     });
 
     if (initAttributes.map == "random")
         titles.push({
             "label": translateWithContext("Map Selection", "Random Map"),
             "value": translate("Randomly select a map from the list.")
         });
     else
     {
         titles.push({
             "label": translate("Map Name"),
             "value": mapCache.translateMapName(
                 mapCache.getTranslatableMapName(initAttributes.mapType, initAttributes.map, initAttributes))
         });
 
         titles.push({
             "label": translate("Map Description"),
             "value": mapCache.getTranslatedMapDescription(initAttributes.mapType, initAttributes.map)
         });
     }
 
     titles.push({
         "label": translate("Map Type"),
         "value": g_MapTypes.Title[g_MapTypes.Name.indexOf(initAttributes.mapType)]
     });
 
     if (initAttributes.mapType == "random")
     {
         let mapSize = g_MapSizes.Name[g_MapSizes.Tiles.indexOf(initAttributes.settings.Size)];
         if (mapSize)
             titles.push({
                 "label": translate("Map Size"),
                 "value": mapSize
             });
     }
 
     if (initAttributes.settings.Biome)
     {
         let biome = g_Settings.Biomes.find(b => b.Id == initAttributes.settings.Biome);
         titles.push({
             "label": biome ? biome.Title : translateWithContext("biome", "Random Biome"),
             "value": biome ? biome.Description : translate("Randomly select a biome from the list.")
         });
     }
 
     if (initAttributes.settings.TriggerDifficulty !== undefined)
     {
         let triggerDifficulty = g_Settings.TriggerDifficulties.find(difficulty => difficulty.Difficulty == initAttributes.settings.TriggerDifficulty);
         titles.push({
             "label": triggerDifficulty.Title,
             "value": triggerDifficulty.Tooltip
         });
     }
 
     if (initAttributes.settings.Nomad !== undefined)
         titles.push({
             "label": initAttributes.settings.Nomad ? translate("Nomad Mode") : translate("Civic Centers"),
             "value":
                 initAttributes.settings.Nomad ?
                     translate("Players start with only few units and have to find a suitable place to build their city.") :
                     translate("Players start with a Civic Center.")
         });
 
     if (initAttributes.settings.StartingResources !== undefined)
         titles.push({
             "label": translate("Starting Resources"),
             "value":
                 initAttributes.settings.PlayerData &&
                 initAttributes.settings.PlayerData.some(pData => pData && pData.Resources !== undefined) ?
                     translateWithContext("starting resources", "Per Player") :
                     sprintf(translate("%(startingResourcesTitle)s (%(amount)s)"), {
                         "startingResourcesTitle":
                             g_StartingResources.Title[
                                 g_StartingResources.Resources.indexOf(
                                     initAttributes.settings.StartingResources)],
                         "amount": initAttributes.settings.StartingResources
                     })
         });
 
     if (initAttributes.settings.PopulationCap !== undefined)
         titles.push({
             "label": translate("Population Limit"),
             "value":
                 initAttributes.settings.PlayerData &&
                 initAttributes.settings.PlayerData.some(pData => pData && pData.PopulationLimit !== undefined) ?
                     translateWithContext("population limit", "Per Player") :
                     g_PopulationCapacities.Title[
                         g_PopulationCapacities.Population.indexOf(
                             initAttributes.settings.PopulationCap)]
         });
 
     if (initAttributes.settings.WorldPopulationCap !== undefined)
         titles.push({
             "label": translate("World Population Cap"),
             "value":
                 g_WorldPopulationCapacities.Title[
                     g_WorldPopulationCapacities.Population.indexOf(
                         initAttributes.settings.WorldPopulationCap)]
         });
 
     titles.push({
         "label": translate("Treasures"),
         "value": initAttributes.settings.DisableTreasures ?
             translateWithContext("treasures", "Disabled") :
             translateWithContext("treasures", "As defined by the map.")
     });
 
     titles.push({
         "label": translate("Explored Map"),
         "value": initAttributes.settings.ExploreMap
     });
 
     titles.push({
         "label": translate("Revealed Map"),
         "value": initAttributes.settings.RevealMap
     });
 
     titles.push({
         "label": translate("Allied View"),
         "value": initAttributes.settings.AllyView
     });
 
     titles.push({
         "label": translate("Cheats"),
         "value": initAttributes.settings.CheatsEnabled
     });
     const ratingDescription = translate("When the winner of this match is determined, the lobby score will be adapted.");
 
     if (initAttributes.settings.RatingEnabled &&
         initAttributes.settings.PlayerData.length == 2)
         titles.push({
             "label": translate("Rated game"),
             "value": ratingDescription
     });
     else if (g_IsNetworked && Engine.HasXmppClient())
         titles.push({
             "label": translate("Rated game"),
             "value": initAttributes.settings.RatingEnabled === true ?  ratingDescription : translateWithContext("game setup option", "disabled")
     });
 
     return titles.map(title => sprintf(translate("%(label)s %(details)s"), {
         "label": coloredText(title.label, g_DescriptionHighlight),
         "details":
             title.value === true ? translateWithContext("game setup option", "enabled") :
                 title.value || translateWithContext("game setup option", "disabled")
     })).join("\n");
 }
 
 /**
  * Sets the win/defeat icon to indicate current player's state.
  */
 function setOutcomeIcon(state, image)
 {
     if (state == "won")
     {
         image.sprite = "stretched:session/icons/victory.png";
         image.tooltip = translate("Victorious");
     }
     else if (state == "defeated")
     {
         image.sprite = "stretched:session/icons/defeat.png";
         image.tooltip = translate("Defeated");
     }
 }
 
 function translateAISettings(playerData)
 {
     if (!playerData.AI)
         return "";
 
     return sprintf(translate("%(AIdifficulty)s %(AIbehavior)s %(AIname)s"), {
         "AIname": translateAIName(playerData.AI),
         "AIdifficulty": translateAIDifficulty(playerData.AIDiff),
         "AIbehavior": translateAIBehavior(playerData.AIBehavior),
     });
 }
 