/**
 * Number of milliseconds to display network warnings.
 */
 var g_NetworkWarningTimeout = 3000;

 /**
  * Currently displayed network warnings. At most one message per user.
  */
 var g_NetworkWarnings = {};
 
 /**
  * Message-types to be displayed.
  */
 var g_NetworkWarningTexts = {
 
	 "server-timeout": (msg, username) =>
		 sprintf(translate("Losing connection to server (%(seconds)ss)"), {
			 "seconds": Math.ceil(msg.lastReceivedTime / 1000)
		 }),
 
	 "client-timeout": (msg, username) =>
		 sprintf(translate("%(player)s losing connection (%(seconds)ss)"), {
			 "player": username,
			 "seconds": Math.ceil(msg.lastReceivedTime / 1000)
		 }),
 
	 "server-latency": (msg, username) =>
		 sprintf(translate("Bad connection to server (%(milliseconds)sms)"), {
			 "milliseconds": msg.meanRTT
		 }),
 
	 "client-latency": (msg, username) =>
		 sprintf(translate("Bad connection to %(player)s (%(milliseconds)sms)"), {
			 "player": username,
			 "milliseconds": msg.meanRTT
		 })
 };
 
 var g_NetworkCommands = {
	 "/kick": argument => kickPlayer(argument, false),
	 "/ban": argument => kickPlayer(argument, true),
	 "/kickspecs": argument => kickObservers(false),
	 "/banspecs": argument => kickObservers(true),
	 "/list": argument => addChatMessage({ "type": "clientlist" }),
	 "/clear": argument => clearChatMessages(),
	 "/letsgo (godseye)": argument => clearChatMessages(),
	 "/showip (godseye)":  argument =>  { 

		if (g_ServerIP && g_ServerPort)
		{
// 			let game = g_GameList[gamesBox.selected];
// 			let ip = game.stunIP ? game.stunIP : game.ip;
// 			let port = game.stunPort ? game.stunPort : game.port;

 g_ServerIP = initData.serverIP;
 g_ServerPort = initData.serverPort;
 g_UseSTUN = initData.useSTUN;
 g_HostJID = initData.hostJID;
// g_ServerName = !!initData.attribs && !!initData.attribs.gameName && initData.attribs.gameName;
// g_PlayerName = !!initData.playerName ? initData.playerName : "";
			addChatMessage({
				"from": "system",
				"text": (g_ServerName ? "IP from \"" + escapeText(g_ServerName) + "\"" : "IP") + ": " + g_ServerIP + ":" + g_ServerPort + "(stunned: " + (!!g_UseSTUN ? "true" : "false") + ")"
			});
			return true;
		}
		return false;
}
 };
 
 var g_ValidPorts = { "min": 1024, "max": 65535 };
 // TODO: This check should be performed exclusively on the C++ side, currently this is sort of redundant.
 function getValidPort(port)
 {
	 if (isNaN(+port) || +port < g_ValidPorts.min || +port > g_ValidPorts.max)
		 return Engine.GetDefaultPort();
 
	 return +port;
 }
 
 /**
  * Must be kept in sync with source/network/NetHost.h
  */
 function getDisconnectReason(id, wasConnected)
 {
	 switch (id)
	 {
	 case 0: return wasConnected ? "" :
		 translate("This is often caused by UDP port 20595 not being forwarded on the host side, by a firewall, or anti-virus software.");
	 case 1: return translate("The host has ended the game.");
	 case 2: return translate("Incorrect network protocol version.");
	 case 3: return translate("Game is loading, please try again later.");
	 case 4: return translate("Game has already started, no observers allowed.");
	 case 5: return translate("You have been kicked.");
	 case 6: return translate("You have been banned.");
	 case 7: return translate("Player name in use. If you were disconnected, retry in few seconds.");
	 case 8: return translate("Server full.");
	 case 9: return translate("Secure lobby authentication failed. Join via lobby.");
	 case 10: return translate("Error: Server failed to allocate a unique client identifier.");
	 case 11: return translate("Error: Client commands were ready for an unexpected game turn.");
	 case 12: return translate("Error: Client simulated an unexpected game turn.");
	 case 13: return translate("Password is invalid.");
	 case 14: return translate("Could not find an unused port for the enet STUN client.");
	 case 15: return translate("Could not find the STUN endpoint.");
	 default:
		 warn("Unknown disconnect-reason ID received: " + id);
		 return sprintf(translate("\\[Invalid value %(id)s]"), { "id": id });
	 }
 }
 
 /**
  * Show the disconnect reason in a message box.
  *
  * @param {number} reason
  */
 function reportDisconnect(reason, wasConnected)
 {
	 messageBox(
		 400, 200,
		 (wasConnected ?
			 translate("Lost connection to the server.") :
			 translate("Failed to connect to the server.")
		 ) + "\n\n" + getDisconnectReason(reason, wasConnected),
		 translate("Disconnected")
	 );
 }
 
 function kickError()
 {
	 addChatMessage({
		 "type": "system",
		 "text": translate("Only the host can kick clients!")
	 });
 }
 
 function kickPlayer(username, ban)
 {
	 if (g_IsController)
		 Engine.KickPlayer(username, ban);
	 else
		 kickError();
 }
 
 function kickObservers(ban)
 {
	 if (!g_IsController)
	 {
		 kickError();
		 return;
	 }
 
	 for (let guid in g_PlayerAssignments)
		 if (g_PlayerAssignments[guid].player == -1)
			 Engine.KickPlayer(g_PlayerAssignments[guid].name, ban);
 }
 
 /**
  * Sort GUIDs of connected users sorted by playerindex, observers last.
  * Requires g_PlayerAssignments.
  */
 function sortGUIDsByPlayerID()
 {
	 return Object.keys(g_PlayerAssignments).sort((guidA, guidB) => {
 
		 let playerIdA = g_PlayerAssignments[guidA].player;
		 let playerIdB = g_PlayerAssignments[guidB].player;
 
		 if (playerIdA == -1) return +1;
		 if (playerIdB == -1) return -1;
 
		 return playerIdA - playerIdB;
	 });
 }
 
 /**
  * Get a colorized list of usernames sorted by player slot, observers last.
  * Requires g_PlayerAssignments and colorizePlayernameByGUID.
  *
  * @returns {string}
  */
 function getUsernameList()
 {
	 let usernames = sortGUIDsByPlayerID().map(guid => colorizePlayernameByGUID(guid));
 
	 // Translation: Number of currently connected players/observers and their names
	 return sprintf(translate("Users (%(num)s): %(users)s"), {
		 "users": usernames.join(translate(", ")),
		 "num": usernames.length
	 });
 }
 
 /**
  * Execute a command locally. Requires addChatMessage.
  *
  * @param {string} input
  * @returns {boolean} whether a command was executed
  */
 function executeNetworkCommand(input)
 {
	 if (input.indexOf("/") != 0)
		 return false;
 
	 let command = input.split(" ", 1)[0];
	 let argument = input.substr(command.length + 1);
 
	 if (g_NetworkCommands[command])
		 g_NetworkCommands[command](argument);
 
	 return !!g_NetworkCommands[command];
 }
 
 /**
  * Remember this warning for a few seconds.
  * Overwrite previous warnings for this user.
  *
  * @param msg - GUI message sent by NetServer or NetClient
  */
 function addNetworkWarning(msg)
 {
	 if (!g_NetworkWarningTexts[msg.warntype])
	 {
		 warn("Unknown network warning type received: " + uneval(msg));
		 return;
	 }
 
	 if (Engine.ConfigDB_GetValue("user", "overlay.netwarnings") != "true")
		 return;
 
	 g_NetworkWarnings[msg.guid || "server"] = {
		 "added": Date.now(),
		 "msg": msg
	 };
 }
 
 /**
  * Colorizes and concatenates all network warnings.
  * Returns text and textWidth.
  */
 function getNetworkWarnings()
 {
	 // Remove outdated messages
	 for (let guid in g_NetworkWarnings)
		 if (Date.now() > g_NetworkWarnings[guid].added + g_NetworkWarningTimeout ||
			 guid != "server" && !g_PlayerAssignments[guid])
			 delete g_NetworkWarnings[guid];
 
	 // Show local messages first
	 let guids = Object.keys(g_NetworkWarnings).sort(guid => guid != "server");
 
	 let font = Engine.GetGUIObjectByName("gameStateNotifications").font;
 
	 let messages = [];
	 let maxTextWidth = 0;
 
	 for (let guid of guids)
	 {
		 let msg = g_NetworkWarnings[guid].msg;
 
		 // Add formatted text
		 messages.push(g_NetworkWarningTexts[msg.warntype](msg, colorizePlayernameByGUID(guid)));
 
		 // Add width of unformatted text
		 let username = guid != "server" && g_PlayerAssignments[guid].name;
		 let textWidth = Engine.GetTextWidth(font, g_NetworkWarningTexts[msg.warntype](msg, username));
		 maxTextWidth = Math.max(textWidth, maxTextWidth);
	 }
 
	 return {
		 "messages": messages,
		 "maxTextWidth": maxTextWidth
	 };
 }
 