// ip, name, connection port, queryPort
typedef Param4<string, string, int, int> CachedServerInfo;

// Script File

enum ESortType
{
	HOST,		
	TIME,
	POPULATION,
	SLOTS,
	PING
	FAVORITE,
	PASSWORDED,
	QUEUE,
	MAP
};

enum ESortOrder
{
	ASCENDING,		
	DESCENDING,
};

class ServerBrowserHelperFunctions
{
	static ref ServerBrowserHelperFunctions s_ServerBrowserHelperFunctions;
	protected static const string CHERNARUS_MAP_IMAGE = ""; // placeholder image path
	protected static const string LIVONIA_MAP_IMAGE = ""; // placeholder image path
	protected static const string SAKHAL_MAP_IMAGE = ""; // placeholder image path
	protected static const string LOWERCASE_ALPHABET = "abcdefghijklmnopqrstuvwxyz"; // used for temporary hotfix
	static ref map<string, string> INTERNAL_MAP_NAMES = new map<string, string>;

	void ServerBrowserHelperFunctions()
	{
		INTERNAL_MAP_NAMES.Insert("chernarusplus", "Chernarus");
		INTERNAL_MAP_NAMES.Insert("enoch", "Livonia");
		INTERNAL_MAP_NAMES.Insert("sakhal", "Sakhal");
	}
	
	// Use this function to add maps to the server browser map filter options
	static void AddMapInfo(string mapName, string mapDisplayName)
	{
		string mdn;
		if (!INTERNAL_MAP_NAMES.Find(mapName, mdn))
			INTERNAL_MAP_NAMES.Insert(mapName, mapDisplayName);
	}

	// Returns internal map name (mission world name) depending on given map display name if it can be fetched from CfgWorlds config.
	static string GetInternalMapName(string mapName)
	{
		string internalMapName;	
		foreach (string mn, string mdp: INTERNAL_MAP_NAMES)	
		{
			if (mdp == mapName)
			{
				internalMapName = mn;
				break;
			}
		}
		
		return internalMapName;
	}
	
	// Returns map display name depending on given internal map name (mission world name).
	static string GetMapDisplayName(string mapName)
	{
		string displayMapName;
		string internalMapName = mapName;
		internalMapName.ToLower();

		foreach (string mn, string mdp: INTERNAL_MAP_NAMES)	
		{
			if (mn == internalMapName)
			{
				displayMapName = mdp;
				break;
			}
		}
		
		if (displayMapName == "")
		{
			displayMapName = mapName;
			string fc = displayMapName[0];
			if (fc != "")
			{
				// temporary fix for VME until fixed internaly
				if (LOWERCASE_ALPHABET.IndexOf(fc) > -1)
				{
					fc.ToUpper();
				}

				displayMapName[0] = fc;
			}
		}

		return displayMapName;
	}
	
	// Returns map image texture path depending on given internal map name (mission world name).
	static string GetServerMapImagePath(string mapName)
	{
		string image;
		mapName.ToLower();
		switch (mapName)
		{
			case "enoch":
			{
				image = LIVONIA_MAP_IMAGE;
				break;
			}
			case "chernarusplus":
			{
				image = CHERNARUS_MAP_IMAGE;
				break;
			}
			case "sakhal":
			{
				image = SAKHAL_MAP_IMAGE;
				break;
			}
		}
		
		return image;
	}
	
	static ServerBrowserHelperFunctions GetInstance()
	{
		if (!s_ServerBrowserHelperFunctions)
			s_ServerBrowserHelperFunctions = new ServerBrowserHelperFunctions;
		
		return s_ServerBrowserHelperFunctions;
	}
}

class GetServerModListResult
{
	string m_Id;	// server id (IP:Port)
	array<string> m_Mods;
}

//! GetServersResultRow the output structure of the GetServers operation that represents one game server
class GetServersResultRow
{
	string	m_Id;					// PC is IP:Port
	int		m_Priority;				// PC something is working -> 667223046
	string	m_Name;					// PC is name of server
	string	m_Description; 			// PC not work
	string	m_HostIp; 				// PC not work
	int		m_HostPort; 			// PC is works
	bool	m_Invisible;
	bool	m_Official;
	string	m_MapNameToRun; 		// map that server is running: "enoch" for Livonia, "chernarusplus" for Chernarus, "sakhal" for Sakhal
	bool	m_Modded; 				// specifies whether a PC server uses mods
	int		m_ModeId; 				// identifies if third person is allowed on a CONSOLE server. On PC always 0
	bool	m_AntiCheat;
	int		m_RegionId; 			// PC not work always 0
	int		m_MinPlayers;
	int		m_MaxPlayers; 			// PC - max players
	int		m_FreeSlots; 			// PC - max players
	int		m_CurrentNumberPlayers;
	int		m_PlayersInQueue;
	string	m_GameVersion; 			// PC not work alway ""
	bool	m_IsPasswordProtected;	// PC work
	string	m_CreatedAt;
	string	m_UpdatedAt;
	bool	m_MouseAndKeyboardEnabled;
	bool	m_WhitelistEnabled;
	bool	m_IsDLC;
	
	//Scripted
	bool		m_IsExpanded;
	int 		m_SortName;	
	int			m_SortTime;
	bool		m_IsSelected;

	//characters alive
	string m_CharactersAlive;
	//steam friends - list of names separated by comma
	string m_SteamFriends;
	
	int m_Ping;	
  	string m_TimeOfDay;
  	//! disable3rdPerson servers for mode regular/hardcore
  	int m_Disable3rdPerson;  //1 for disabled, ie. hardcore
	//! time multiplier of environment
	float m_EnvironmentTimeMul;
	float m_EnvironmentNightTimeMul;
	bool		m_AllowedFilePatching;
	string m_ShardId;
	int m_SteamQueryPort;
	
	bool m_Favorite;

	string GetIpPort()
	{
#ifdef PLATFORM_WINDOWS
		return m_Id;
#else
		return GetIP() + ":" + m_HostPort;
#endif
	}
	
	string GetIP()
	{
#ifdef PLATFORM_WINDOWS
		// Hack - In new Serverborwser on PC has bad m_HostIp but ID contains correct IP
		array<string> parts = new array<string>;
		m_Id.Split(":", parts);
		return parts[0];
#else
		return m_HostIp;
#endif
	}
	
	bool IsSelected()
	{
		return m_IsSelected;
	}
	
	string GetValueStr(ESortType sort_type)
	{
		switch( sort_type )
		{
			case ESortType.HOST:
			{
				return m_Name;
			}
			
			case ESortType.MAP:
			{
				// m_MapNameToRun should never be a empty string but just in case we check before getting the map display name
				if (m_MapNameToRun != "")
					return ServerBrowserHelperFunctions.GetMapDisplayName(m_MapNameToRun);
			}
		}
		
		return "";
	}	
	
	int GetValueInt(ESortType sort_type)
	{
		switch( sort_type )
		{
			case ESortType.TIME:
			{
				return m_SortTime;
			}
			case ESortType.POPULATION:
			{
				return m_CurrentNumberPlayers;
			}
			case ESortType.SLOTS:
			{
				return m_MaxPlayers;
			}
			case ESortType.PING:
			{
				return m_Ping;
			}
			case ESortType.QUEUE:
			{
				return m_PlayersInQueue;
			}
		}		
		return 0;
	}
	
	// Returns 0 if values are equal, 
	// a positive number if this entry is "greater than" other,
	// and a negative number if this entry is "less than" other 
	int CompareTo(GetServersResultRow other, ESortType sortType)
	{	
		// string comparison
		if (sortType == ESortType.HOST || sortType == ESortType.MAP)
		{
			string val1 = this.GetValueStr(sortType);
			string val2 = other.GetValueStr(sortType);
			
			if (val1 == val2)
				return 0;
			
			if (val1 < val2)
				return 1;
			
			return -1;
		}

		// int comparison
		int comparisonResult = other.GetValueInt(sortType) - this.GetValueInt(sortType);
		if (comparisonResult == 0)
		{
			// if sorting by POPULATION, break ties using QUEUE size
			if (sortType == ESortType.POPULATION)
			{
				comparisonResult = this.CompareTo(other, ESortType.QUEUE);	
			}
		}
		
		return comparisonResult;
	}
};


typedef array<ref GetServersResultRow> GetServersResultRowArray;

class GetFirstServerWithEmptySlotResult
{
	GetServersResult m_Result;
};

//! GetServersInput the input structure of the GetServers operation
class GetFirstServerWithEmptySlotInput
{
	bool		m_Official;
	string		m_GameVersion;
	int			m_RegionId;

	bool		m_UseOfficial;
	bool		m_UseGameVersion;
	bool		m_UseRegionId;
		
	void SetOfficial( bool Official )
	{
		m_Official					= Official;
		m_UseOfficial				= true;
	}
	
	void SetGameVersion( string GameVersion )
	{
		m_GameVersion				= GameVersion;
		m_UseGameVersion			= true;
	}
	
	void SetRegionId( int RegionId )
	{
		m_RegionId					= RegionId;
		m_UseRegionId				= true;
	}
};

//! GetServersResult the output structure of the GetServers operation
class GetServersResult
{
	int								m_Page;
	int								m_Pages;
	ref GetServersResultRowArray	m_Results;

	int m_NumServers;
};

//! GetServersInput the input structure of the GetServers operation
class GetServersInput
{
	bool		m_AntiCheat;
	int			m_RowsPerPage;
	string		m_SortBy; //na of property
	ESortOrder	m_SortOrder;
	string		m_Name;
	string		m_GameType;
	int			m_Platform; // 1 - PC, 2 - xbox, 3 - PSN
	int			m_ModeId; // identifies if third person is allowed on a CONSOLE server. On PC always 0
	int			m_Page;
	string		m_GameVersion;
	bool		m_Official;
	bool		m_Joinable;
	string		m_MapNameToRun;
	bool		m_IsModded;
	int			m_MinPlayers; // minimum 1
	int			m_MaxPlayers; // maximum 99
	bool		m_IsPasswordProtected;
	int			m_RegionId; // 1 - Americas, 2 - Europe, 3 - Asia
	int			m_Priority;
	int 		m_FreeSlotsMin;
	int 		m_FreeSlotsMax;
	string		m_HostIp;
	int			m_HostPort;
	string		m_FavoriteServers;
	bool		m_MouseAndKeyboardEnabled;
	bool		m_WhitelistEnabled;
	bool		m_IsDLC;

	bool m_UseAntiCheat;
	bool m_UseName;
	bool m_UseGameType;
	bool m_UseModeId;
	bool m_UseGameVersion;
	bool m_UseOfficial;
	bool m_UseJoinable;
	bool m_UseMapNameToRun;
	bool m_UseIsModded;
	bool m_UseMinPlayers; 
	bool m_UseMaxPlayers; 
	bool m_UseIsPasswordProtected;
	bool m_UseRegionId; 
	bool m_UsePriority;
	bool m_UseFreeSlotsMin;
	bool m_UseFreeSlotsMax;
	bool m_UseHostIp;
	bool m_UseHostPort;
	bool m_UseHostIps;
	bool m_UseMouseAndKeyboardEnabled;
	bool m_UseWhitelistEnabled;
	bool m_UseIsDLC;
	
	void SetAntiCheatFilter( bool anti_cheat )
	{
		m_AntiCheat					= anti_cheat;
		m_UseAntiCheat				= true;
	}
	
	void SetNameFilter( string name )
	{
		m_Name						= name;
		m_UseName					= true;
	}
	
	void SetGameTypeFilter( string game_type )
	{
		m_GameType					= game_type;
		m_UseGameType				= true;
	}
	
	void SetModeIdFilter( int mode_id )
	{
		m_ModeId					= mode_id;
		m_UseModeId					= true;
	}
	
	void SetGameVersionFilter( string game_version )
	{
		m_GameVersion				= game_version;
		m_UseGameVersion			= true;
	}
	
	void SetOfficialFilter( bool official )
	{
		m_Official					= official;
		m_UseOfficial				= true;
	}
	
	void SetJoinableFilter( bool joinable )
	{
		m_Joinable					= joinable;
		m_UseJoinable				= true;
	}
	
	void SetMapNameToRun( string mapNameToRun )
	{
		m_MapNameToRun = mapNameToRun;
		m_UseMapNameToRun = true;
	}
	
	void SetIsModdedFilter( bool is_modded )
	{
		m_IsModded					= is_modded;
		m_UseIsModded				= true;
	}
	
	void SetMinPlayersFilter( int min_players )
	{
		m_MinPlayers				= min_players;
		m_UseMinPlayers				= true;
	}
	
	void SetMaxPlayersFilter( int max_players )
	{
		m_MaxPlayers				= max_players;
		m_UseMaxPlayers				= true;
	}
	
	void SetIsPasswordProtectedFilter( bool password_protected )
	{
		m_IsPasswordProtected		= password_protected;
		m_UseIsPasswordProtected	= true;
	}
	
	void SetRegionIdFilter( int region )
	{
		m_RegionId					= region;
		m_UseRegionId				= true;
	}
	
	void SetPriorityFilter( int priority )
	{
		m_Priority					= priority;
		m_UsePriority				= true;
	}
	
	void SetFreeSlotsMin( int freeSlotsMin )
	{
		m_FreeSlotsMin = freeSlotsMin;
		m_UseFreeSlotsMin = true;
	}
	
	void SetFreeSlotsMax( int freeSlotsMax )
	{
		m_FreeSlotsMax = freeSlotsMax;
		m_UseFreeSlotsMax = true;
	}
	
	void SetHostIp( string hostIp )
	{
		m_HostIp = hostIp;
		m_UseHostIp = true;
	}
	
	void SetHostPort( int hostPort )
	{
		m_HostPort = hostPort;
		m_UseHostPort = true;
	}
	
	void SetMouseAndKeyboardEnabled(bool enabledMouseAndKeyboard)
	{
		m_MouseAndKeyboardEnabled = enabledMouseAndKeyboard;
		m_UseMouseAndKeyboardEnabled = true;
	}
	
	void SetFavorited( bool show )
	{
		m_SortBy += "F";
		AddShow( show );
		
	}
	
	void SetFriendsPlaying( bool show )
	{
		m_SortBy += "P";
		AddShow( show );
	}
	
	void SetPreviouslyPlayed( bool show )
	{
		m_SortBy += "R";
		AddShow( show );
	}
	
	void SetProperVersionMatch( bool show )
	{
		m_SortBy += "V";
		AddShow( show );
	}
	
	void SetFullServer( bool show )
	{
		m_SortBy += "S";
		AddShow( show );
	}
	
	void SetThirdPerson( bool show )
	{
		m_SortBy += "3";
		AddShow( show );
	}
	
	void SetPublic( bool show )
	{
		m_SortBy += "L";
		AddShow( show );
	}
	
	void SetAcceleratedTime( bool show )
	{
		m_SortBy += "A";
		AddShow( show );
	}

	void SetAllowedFilePatching( bool show )
	{
		m_SortBy += "7";
		AddShow( show );
	}
	
	void SetLAN()
	{
		m_SortBy += "N";
	}
	
	void SetPingFilter( int pingMaxValue )
	{
		m_SortBy += "M" + pingMaxValue + ";";
	}
	
	void SetBattleyeProtection(bool show)
	{
		m_SortBy += "B";
		AddShow(show);
	}
		
	void SetPassworded(bool show)
	{
		m_SortBy += "C";
		AddShow(show);
	}

	void AddShow( bool show )
	{
		if( show )
			m_SortBy += "+";
		else
			m_SortBy += "-";
	}
	
	void AddFavourite(string ip, int port)
	{
		m_FavoriteServers += ip + ";" + port + ";";
	}
	
	void SetWhitelistEnabled(bool whitelistEnabled)
	{
		m_WhitelistEnabled = whitelistEnabled;
		m_UseWhitelistEnabled = true;
		
		m_SortBy += "W";
		AddShow(whitelistEnabled);
	}
	
	void SetIsDLC(bool isDLC)
	{
		m_IsDLC = isDLC;
		m_UseIsDLC = true;
		
		m_SortBy += "D";
		AddShow(isDLC);
	}
};

class BiosLobbyService
{
	//! Query game servers
	/*!
		The async result is returned in the OnDoneAsync callback.
		Expected errors:
			BAD_PARAMETER -	if atleast one of the permissions is not supported on the current platform, 
							or atleast one of the input arrays is NULL.
			
		@param inputValues input structure
		
	*/
	proto native EBiosError GetServers(GetServersInput inputValues);

	//! First 1st free server
	/*!
		The async result is returned in the OnGetFirstServerWithEmptySlot callback.
				
	*/
	proto native EBiosError GetFirstServerWithEmptySlot(GetFirstServerWithEmptySlotInput inputValues);
	
	proto native void AddServerFavorite(string ipAddress, int port, int steamQueryPort);
	proto native void RemoveServerFavorite(string ipAddress, int port, int steamQueryPort);
	proto native void GetFavoriteServers(TStringArray favServers);
	
	//! Get cached info about favorited servers (ONLY ON WINDOWS)
	/*!
		@param favServersInfoCache gets populated with data with format: key = Query End Point, value.param1 = Server Name, value.param2 = Connection Port
	*/
	proto native void GetCachedFavoriteServerInfo(array<ref CachedServerInfo> favServersInfoCache);
	
	//! Async function to retrieve info about mods for specific server (only for PC)
	//! @param serverId have to be an id returned by callbacks issued by last call to GetServers
	//! if GetServers is used another time the id's must be upated
	proto native EBiosError GetServerModList(string serverId);
	
	//! Async callback for GetServers
	/*!
		@param result_list result object
		@param error error indicating success or fail of the async operation.
		@param response for debugging - this is the data returned by the server; or an empty string ;)
	*/
	
	void OnDoneAsync(GetServersResult result_list, EBiosError error, string response)
	{
		/*
		if (result_list.m_Results != null && result_list.m_Results.Count() > 0)
			Print(result_list.m_Results[0].m_TimeOfDay);
		*/
		OnlineServices.OnLoadServersAsync( result_list, error, response );
	}
	
	void OnGetFirstServerWithEmptySlot(GetFirstServerWithEmptySlotResult result_list, EBiosError error)
	{
		OnlineServices.OnAutoConnectToEmptyServer( result_list, error );
	}
	
	//! Async callback for GetServerModList
	void OnServerModList(GetServerModListResult result_list, EBiosError error)
	{
		OnlineServices.OnGetServerModList( result_list, error );
	}
};
