/** 
 *	Matchmaking common library
 */

#Const Version		"2017-05-12"
#Const ScriptName	"Libs/Nadeo/MatchmakingCommon.Script.txt"

// ---------------------------------- //
// Constants
// ---------------------------------- //
// Matchmaking modes
#Const C_Matchmaking_Off			0	///< Matchmaking off on this server
#Const C_Matchmaking_Lobby			1	///< Is lobby server
#Const C_Matchmaking_Match			2	///< Is match  server
#Const C_Matchmaking_UniversalLobby	3	///< Is universal lobby
#Const C_Matchmaking_UniversalMatch	4	///< Is universal match
// Request types
#Const C_Request_GetPlayers		0	///< /lobby-server/player-connection?login=somelogin&lobbylogin=anotherlogin
#Const C_Request_PostPlayers	1	///< /lobby-server/matchmaking-live
#Const C_Request_GetMatches		2	///< /match-server/match?serverlogin=somelogin
#Const C_Request_PostStatus		3	///< /match-server/live
#Const C_Request_PostResults	4	///< /match-server/result
#Const C_Request_PostMatches	5	///< /lobby-server/match
// Misc
#Const C_MessagePrefix			"$000»$09f"	///< Prefix used before the messages sent in the chat by the matchmaking
#Const C_PingInterval			5000	///< Interval between ping from the player

// ---------------------------------- //
// Globales
// ---------------------------------- //
declare Integer G_MB_Mode; //< Do not reset in Unload()/Load()

// ---------------------------------- //
// Functions
// ---------------------------------- //
// ---------------------------------- //
// Public
// ---------------------------------- //
// ---------------------------------- //
/** Return the version number of the script
 *
 *	@return				The version number of the script
 */
Text GetScriptVersion() {
	return Version;
}

// ---------------------------------- //
/** Return the name of the script
 *
 *	@return				The name of the script
 */
Text GetScriptName() {
	return ScriptName;
}

// ---------------------------------- //
/** Check if the matchmaking mode setting
 *	was updated in the mode base
 *
 *	@param	_Mode											The new mode
 *
 *	@return														True if the mode was updated, Fals otherwise
 */
Boolean MB_SettingMatchmakingModeUpdated(Integer _Mode) {
	if (G_MB_Mode != _Mode) {
		G_MB_Mode = _Mode;
		return True;
	}
	return False;
}

// ---------------------------------- //
/** Initialize the matchmaking mode setting
 *
 *	@param	_Mode											The default mode
 */
Void MB_InitSettingMatchmakingMode(Integer _Mode) {
	G_MB_Mode = _Mode;
}

// ---------------------------------- //
// Functions to avoid compilation warning
// "Comparing a value of type CSmModeEvent::EType to a value of type CTmModeEvent::EType"
//Boolean IsEventType(CTmModeEvent _Event, CTmModeEvent::EType _Type) {
//	return _Event.Type == _Type;
//}
//Boolean IsEventType(CTmModeEvent _Event, CSmModeEvent::EType _Type) {
//	return False;
//}
//Boolean IsEventType(CSmModeEvent _Event, CTmModeEvent::EType _Type) {
//	return False;
//}
Boolean IsEventType(CSmModeEvent _Event, CSmModeEvent::EType _Type) {
	return _Event.Type == _Type;
}
// ---------------------------------- //
/** Return the prefix used by the matchmaking to display its messages
 *
 *	@return				The prefix
 */
Text GetMessagePrefix() {
	return C_MessagePrefix;
}

// ---------------------------------- //
/** Set the error message to display to the players
 *
 *	@param	_Message	The error message
 */
Void SetErrorMessage(Text _Message) {
	declare Text LibMMCommon_ErrorMessage for This;
	LibMMCommon_ErrorMessage = _Message;
}

// ---------------------------------- //
/** Get the error message to display to the players
 *
 *	@return				The error message
 */
Text GetErrorMessage() {
	declare Text LibMMCommon_ErrorMessage for This;
	return LibMMCommon_ErrorMessage;
}

// ---------------------------------- //
/** Turn on/off the progressive matchmaking
 *
 *	@param	_Active		True to turn on, False to turn off
 */
Void SetProgressiveMatchmaking(Boolean _Active) {
	declare Boolean LibMMCommon_ProgressiveMatchmaking for This;
	LibMMCommon_ProgressiveMatchmaking = _Active;
}

// ---------------------------------- //
/** Check if the matchmaking is progressive or not
 *
 *	@return				True if the matchmaking is progressive, False otherwise
 */
Boolean GetProgressiveMatchmaking() {
	declare Boolean LibMMCommon_ProgressiveMatchmaking for This;
	return LibMMCommon_ProgressiveMatchmaking;
}

// ---------------------------------- //
/** Set the current matchmaking format
 *	It's the current format that can be modified by the progressive matchmaking function
 *
 *	@param	_Format		The new format
 */
Void SetProgressiveMatchFormats(Integer[][] _Format) {
	declare Integer[][] LibMMCommon_ProgressiveMatchFormats for This;
	LibMMCommon_ProgressiveMatchFormats = _Format;
}

// ---------------------------------- //
/** Get the current matchmaking format
 *	It's the current format that can be modified by the progressive matchmaking function
 *	[NbPlayersTeam1, NbPlayersTeam2, ..., NbPlayersTeamN]
 *
 *	@return				The current format
 */
Integer[][] GetProgressiveMatchFormats() {
	declare Integer[][] LibMMCommon_ProgressiveMatchFormats for This;
	return LibMMCommon_ProgressiveMatchFormats;
}

// ---------------------------------- //
/** Set the current matchmaking format
 *	It's the current format that can be modified by the progressive matchmaking function
 *
 *	@param	_Format		The new format
 */
Void SetCurrentMatchFormat(Integer[] _Format) {
	declare Integer[] LibMMCommon_CurrentMatchFormat for This;
	LibMMCommon_CurrentMatchFormat = _Format;
}

// ---------------------------------- //
/** Get the current matchmaking format
 *	It's the current format that can be modified by the progressive matchmaking function
 *	[NbPlayersTeam1, NbPlayersTeam2, ..., NbPlayersTeamN]
 *
 *	@return				The current format
 */
Integer[] GetCurrentMatchFormat() {
	declare Integer[] LibMMCommon_CurrentMatchFormat for This;
	return LibMMCommon_CurrentMatchFormat;
}

// ---------------------------------- //
/** Set the matchmaking format
 *	It's the desired format
 *	[NbPlayersTeam1, NbPlayersTeam2, ..., NbPlayersTeamN]
 *
 *	@param	_Format		The new format
 */
Void SetMatchFormat(Integer[] _Format) {
	declare Integer[] LibMMCommon_MatchFormat for This;
	LibMMCommon_MatchFormat = _Format;
}

// ---------------------------------- //
/** Get the matchmaking format
 *	It's the desired format
 *	[NbPlayersTeam1, NbPlayersTeam2, ..., NbPlayersTeamN]
 *
 *	@return				The current format
 */
Integer[] GetMatchFormat() {
	declare Integer[] LibMMCommon_MatchFormat for This;
	return LibMMCommon_MatchFormat;
}

// ---------------------------------- //
/** Set the maximum number of players in a clan
 *
 *	@param	_MaxPlayers		The new maximum number of players
 */
Void MM_SetMaxPlayers(Integer _MaxPlayers) {
	declare Integer LibMMCommon_MaxPlayers for This;
	LibMMCommon_MaxPlayers = _MaxPlayers;
}

// ---------------------------------- //
/** Get the maximum number of players in a clan
 *
 *	@return					The maximum number of players in a clan
 */
Integer GetMaxPlayers() {
	declare Integer LibMMCommon_MaxPlayers for This;
	return LibMMCommon_MaxPlayers;
}

// ---------------------------------- //
/** Set the api url
 *
 *	@param	_Url		The new url
 */
Void SetApiUrl(Text _Url) {
	declare Text LibMMCommon_ApiUrl for This;
	LibMMCommon_ApiUrl = _Url;
}

// ---------------------------------- //
/** Get the api url
 *
 *	@return					The api url
 */
Text GetApiUrl() {
	declare Text LibMMCommon_ApiUrl for This;
	return LibMMCommon_ApiUrl;
}

// ---------------------------------- //
/** Get the api url appened with a custom path
 *
 *	@param	_Path			Custom path to append to the api url
 *
 *	@return					The composed url
 */
Text GetApiUrl(Text _Path) {
	declare Text LibMMCommon_ApiUrl for This;
	return LibMMCommon_ApiUrl^_Path;
}

// ---------------------------------- //
/// Get the request type constants
Integer RequestType_GetPlayers() { return C_Request_GetPlayers; }
Integer RequestType_PostPlayers() { return C_Request_PostPlayers; }
Integer RequestType_GetMatches() { return C_Request_GetMatches; }
Integer RequestType_PostStatus() { return C_Request_PostStatus; }
Integer RequestType_PostResults() { return C_Request_PostResults; }
Integer RequestType_PostMatches() { return C_Request_PostMatches; }

// ---------------------------------- //
/** Add a request to the pending requests array
 *
 *	@param	_RequestId		The id of the request
 *	@param	_Type			The type of request
 */
Void AddPendingRequest(Ident _RequestId, Integer _Type) {
	declare Integer[Ident] LibMMCommon_PendingRequests for This;
	LibMMCommon_PendingRequests[_RequestId] = _Type;
}

// ---------------------------------- //
/** Remove a request from the pending requests array
 *
 *	@param	_RequestId		The id of the request
 */
Void RemovePendingRequest(Ident _RequestId) {
	declare Integer[Ident] LibMMCommon_PendingRequests for This;
	declare Removed = LibMMCommon_PendingRequests.removekey(_RequestId);
}

// ---------------------------------- //
/// Clear the pending requests array
Void ClearPendingRequests() {
	declare Integer[Ident] LibMMCommon_PendingRequests for This;
	foreach (RequestId => RequestType in LibMMCommon_PendingRequests) {
		if (!Http.Requests.existskey(RequestId)) continue;
		Http.Destroy(Http.Requests[RequestId]);
	}
	LibMMCommon_PendingRequests.clear();
}

// ---------------------------------- //
/** Chec if a request is in the pending requests array
 *
 *	@param	_RequestId		The id of the request to check
 *
 *	@return					True if the request is in the pending requests array, False otherwise
 */
Boolean IsInPendingRequests(Ident _RequestId) {
	declare Integer[Ident] LibMMCommon_PendingRequests for This;
	return LibMMCommon_PendingRequests.existskey(_RequestId);
}

// ---------------------------------- //
/** Chec if a type of request is in the pending requests array
 *
 *	@param	_Type			The type of the request to check
 *
 *	@return					True if the type of request is in the pending requests array, False otherwise
 */
Boolean IsInPendingRequests(Integer _Type) {
	declare Integer[Ident] LibMMCommon_PendingRequests for This;
	return LibMMCommon_PendingRequests.exists(_Type);
}

// ---------------------------------- //
/** Get the type of a request
 *
 *	@param	_RequestId		The id of the request to check
 *
 *	@return					The type of request if found, -1 otherwise
 */
Integer GetPendingRequestType(Ident _RequestId) {
	declare Integer[Ident] LibMMCommon_PendingRequests for This;
	if (LibMMCommon_PendingRequests.existskey(_RequestId)) {
		return LibMMCommon_PendingRequests[_RequestId];
	}
	return -1;
}

// ---------------------------------- //
/** Set if the server is used in a channel or not
 *
 *	@param	_IsChannel		This is a channel server or not
 */
Void SetChannelServer(Boolean _IsChannel) {
	declare Boolean LibMMCommon_IsChannel for This;
	LibMMCommon_IsChannel = _IsChannel;
}

// ---------------------------------- //
/** Get the api url
 *
 *	@return					The api url
 */
Boolean IsChannelServer() {
	declare Boolean LibMMCommon_IsChannel for This;
	return LibMMCommon_IsChannel;
}

// ---------------------------------- //
/** Create a link to join another server in the same title
 *
 *	@param	_ServerLogin	The login of the server to join
 *	@param	_AddProtocol	Add the protocol to the link
 *
 *	@return								A join link for the given server login
 */
Text GetServerJoinLink(Text _ServerLogin, Boolean _AddProtocol) {
	declare Protocol = "";
	if (_AddProtocol) Protocol = "maniaplanet://";
	
	if (IsChannelServer()) {
		return Protocol^"#channel_server="^_ServerLogin^"@"^LoadedTitle.TitleId;
	}
	
	return Protocol^"#qjoin="^_ServerLogin^"@"^LoadedTitle.TitleId;
}

// ---------------------------------- //
/** Create a link to join another server in the same title
 *
 *	@param	_ServerLogin	The login of the server to join
 */
Text GetServerJoinLink(Text _ServerLogin) {
	return GetServerJoinLink(_ServerLogin, True);
}

// ---------------------------------- //
/** Set the last time an user was sent to another server
 *
 *	@param	_User			The user to update
 *	@param	_TransfertTime	The time of the transfert
 */
Void SetLastTransfertTime(CUser _User, Integer _TransfertTime) {
	if (_User == Null) return;
	declare G_LibMMCommon_LastTransfertTime for _User = -1;
	G_LibMMCommon_LastTransfertTime = _TransfertTime;
}

// ---------------------------------- //
/** Get the last time an user was sent to another server
 *
 *	@param	_User			The user to check
 *
 *	@return					The last transfert time if any, -1 otherwise
 */
Integer GetLastTransfertTime(CUser _User) {
	if (_User == Null) return -1;
	declare G_LibMMCommon_LastTransfertTime for _User = -1;
	return G_LibMMCommon_LastTransfertTime;
}

// ---------------------------------- //
/// Get the matchmaking mode constants
Integer MatchmakingMode_Off() { return C_Matchmaking_Off; }
Integer MatchmakingMode_Lobby() { return C_Matchmaking_Lobby; }
Integer MatchmakingMode_Match() { return C_Matchmaking_Match; }
Integer MatchmakingMode_UniversalLobby() { return C_Matchmaking_UniversalLobby; }
Integer MatchmakingMode_UniversalMatch() { return C_Matchmaking_UniversalMatch; }

// ---------------------------------- //
/** Set the mode of matchmaking
 *
 *	@param	_Mode			The new matchmaking mode
 */
Void SetMode(Integer _Mode) {
	declare Integer LibMMCommon_Mode for This;
	
	switch (_Mode) {
		case C_Matchmaking_Lobby			: LibMMCommon_Mode = C_Matchmaking_Lobby;
		case C_Matchmaking_Match			: LibMMCommon_Mode = C_Matchmaking_Match;
		case C_Matchmaking_UniversalLobby	: LibMMCommon_Mode = C_Matchmaking_UniversalLobby;
		case C_Matchmaking_UniversalMatch	: LibMMCommon_Mode = C_Matchmaking_UniversalMatch;
		default								: LibMMCommon_Mode = C_Matchmaking_Off;
	}
}

// ---------------------------------- //
/** Get the mode of matchmaking
 *
 *	@return					The matchmaking mode
 */
Integer GetMode() {
	declare Integer LibMMCommon_Mode for This;
	return LibMMCommon_Mode;
}

// ---------------------------------- //
/** Check if a server is in match mode
 *
 *	@return		True if it's a match server, false otherwise
 */
Boolean IsMatchServer() {
	declare Integer LibMMCommon_Mode for This;
	return (LibMMCommon_Mode == C_Matchmaking_Match || LibMMCommon_Mode == C_Matchmaking_UniversalMatch);
}

// ---------------------------------- //
/** Check if a server is in lobby mode
 *
 *	@return		True if it's a lobby server, false otherwise
 */
Boolean IsLobbyServer() {
	declare Integer LibMMCommon_Mode for This;
	return (LibMMCommon_Mode == C_Matchmaking_Lobby || LibMMCommon_Mode == C_Matchmaking_UniversalLobby);
}

// ---------------------------------- //
/** Check if we are in universal mode
 *
 *	@return		True if it's an universal server, false otherwise
 */
Boolean IsUniversalServer() {
	declare Integer LibMMCommon_Mode for This;
	return (LibMMCommon_Mode == C_Matchmaking_UniversalLobby || LibMMCommon_Mode == C_Matchmaking_UniversalMatch);
}

// ---------------------------------- //
/** Check if we are on a matchmaking server
 *
 *	@return		True if it's a matchmaking server, false otherwise
 */
Boolean IsMatchmakingServer() {
	declare Integer LibMMCommon_Mode for This;
	return (LibMMCommon_Mode != C_Matchmaking_Off);
}

// ---------------------------------- //
/** Check if the matchmaking is progressive
 *
 *	@return		True if it's progressive, false otherwise
 */
Boolean IsProgressiveMatchmaking() {
	 return (GetProgressiveMatchmaking() && !IsUniversalServer());
}

// ---------------------------------- //
/** Get the log to display status
 *
 *	@param	_LogName	The name of the log to check
 *
 *	@return				True if the log must be displayed, false otherwise
 */
Boolean GetLogDisplay(Text _LogName) {
	declare Boolean[Text] LibMMCommon_LogDisplay for This;
	if (!LibMMCommon_LogDisplay.existskey(_LogName)) return False;
	return LibMMCommon_LogDisplay[_LogName];
}

// ---------------------------------- //
/** Create the send to server manialink
 *
 *	@return		The manialink
 */
Text Private_GetMLSendToServer() {
	return """
<manialink version="1" name="ModeMatchmaking:SendToServer">
<script><!--
declare netread Integer Net_Lobby_JoinLinkUpdate for UI;
declare netread Text Net_Lobby_JoinLink for UI;
declare netwrite Integer Net_Lobby_JoinLinkReceived for UI;
declare netwrite Integer Net_Lobby_Ping for UI;
Net_Lobby_Ping = -{{{C_PingInterval}}};

declare PrevUpdate = Net_Lobby_JoinLinkUpdate;

while (True) {
	yield;
	if (InputPlayer == Null || !PageIsVisible) continue;
	
	if (PrevUpdate != Net_Lobby_JoinLinkUpdate) {
		PrevUpdate = Net_Lobby_JoinLinkUpdate;
		Net_Lobby_JoinLinkReceived = Net_Lobby_JoinLinkUpdate;
		
		if ({{{GetLogDisplay("MiscDebug")}}}) {
			log(Now^"> [CLIENT] "^InputPlayer.User.Login^" > OpenLink: "^Net_Lobby_JoinLink);
		}
		
		if (Net_Lobby_JoinLink != "") {
			OpenLink(Net_Lobby_JoinLink, CMlScript::LinkType::ManialinkBrowser);
		}
	}
	
	if (Net_Lobby_Ping + {{{C_PingInterval}}} <= GameTime) {
		Net_Lobby_Ping = GameTime;
	}
}
--></script>
</manialink>""";
}

// ---------------------------------- //
/** Set the log to display or not
 *
 *	@param	_LogName	The name of the log
 *	@param	_Display	Display th elog or not
 */
Void SetLogDisplay(Text _LogName, Boolean _Display) {
	declare Boolean[Text] LibMMCommon_LogDisplay for This;
	LibMMCommon_LogDisplay[_LogName] = _Display;
	
	declare Ident LibMMCommon_LayerSendToServerId for This;
	if (UIManager.UILayers.existskey(LibMMCommon_LayerSendToServerId)) {
		declare LayerSendToServer = UIManager.UILayers[LibMMCommon_LayerSendToServerId];
		LayerSendToServer.ManialinkPage = Private_GetMLSendToServer();
	}
}

// ---------------------------------- //
/** Send a player to another server
 *
 *	@param	_Player				The player to send
 *	@param	_ServerLogin	The server login where to send the player
 *	@param	_UpdateTransfertTime Update the time of the last transfert
 */
Void SendToServer(CPlayer _Player, Text _ServerLogin, Boolean _UpdateTransfertTime) {
	if (_Player == Null || _ServerLogin == "") return;
	
	if (_UpdateTransfertTime) SetLastTransfertTime(_Player.User, Now);
	
	declare UI <=> UIManager.GetUI(_Player);
	if (UI == Null) return;
	
	declare netwrite Integer Net_Lobby_JoinLinkUpdate for UI;
	declare netwrite Text Net_Lobby_JoinLink for UI;
	Net_Lobby_JoinLinkUpdate = Now;
	Net_Lobby_JoinLink = GetServerJoinLink(_ServerLogin);
}

// ---------------------------------- //
/** Send a player to another server
 *
 *	@param	_Player				The player to send
 *	@param	_ServerLogin	The server login where to send the player
 */
Void SendToServer(CPlayer _Player, Text _ServerLogin) {
	SendToServer(_Player, _ServerLogin, True);
}

// ---------------------------------- //
/** Check if a player received the last
 *	transfert instruction
 *
 *	@return														True if the instruction was received
 *																		False otherwise
 */
Boolean SendToServerReceived(CPlayer _Player) {
	if (_Player == Null) return False;
	declare UI <=> UIManager.GetUI(_Player);
	if (UI == Null) return False;
	
	declare netwrite Integer Net_Lobby_JoinLinkUpdate for UI;
	declare netread Integer Net_Lobby_JoinLinkReceived for UI;
	
	return Net_Lobby_JoinLinkUpdate == Net_Lobby_JoinLinkReceived;
}

// ---------------------------------- //
/** Get the transfer link forced by
 *	the channel if any
 *
 *	@return														The transfer link
 */
Text GetChannelTransferUrl() {
	if (
		IsChannelServer() &&
		ServerAdmin != Null &&
		ServerAdmin.ServerInfo != Null &&
		ServerAdmin.ServerInfo.SendToServerAfterMatchUrl != ""
	) {
		return ServerAdmin.ServerInfo.SendToServerAfterMatchUrl;
	}
	
	return "";
}

// ---------------------------------- //
/** Get the last ping of an user
 *
 *	@param	_User			The user to check
 *
 *	@return					The last ping of the user
 */
Integer GetLastPing(CUser _User) {
	if (_User == Null) return 0;
	declare UI <=> UIManager.GetUI(_User);
	if (UI == Null) return 0;
	declare netread Integer Net_Lobby_Ping for UI;
	return Net_Lobby_Ping;
}

// ---------------------------------- //
/** Inject the ping related functions
 *
 *	@return				The ping related functions
 */
Text InjectPingHelpers() {
	return """
Integer MMCommon_GetLastPing() {
	declare netwrite Integer Net_Lobby_Ping for UI;
	return Net_Lobby_Ping;
}	
""";
}

// ---------------------------------- //
/// Unload the library
Void Unload() {
	// Clear globales
	declare Integer LibMMCommon_Mode for This;
	declare Boolean[Text] LibMMCommon_LogDisplay for This;
	declare Integer[] LibMMCommon_CurrentMatchFormat for This;
	declare Integer[][] LibMMCommon_ProgressiveMatchFormats for This;
	declare Integer[] LibMMCommon_MatchFormat for This;
	declare Integer LibMMCommon_MaxPlayers for This;
	declare Text LibMMCommon_ApiUrl for This;
	declare Boolean LibMMCommon_ProgressiveMatchmaking for This;
	declare Text LibMMCommon_ErrorMessage for This;
	declare Boolean LibMMCommon_IsChannel for This;
	LibMMCommon_Mode = 0;
	LibMMCommon_LogDisplay.clear();
	LibMMCommon_CurrentMatchFormat.clear();
	LibMMCommon_CurrentMatchFormat.clear();
	LibMMCommon_MatchFormat.clear();
	LibMMCommon_MaxPlayers = 0;
	LibMMCommon_ApiUrl = "";
	LibMMCommon_ProgressiveMatchmaking = False;
	LibMMCommon_ErrorMessage = "";
	LibMMCommon_IsChannel = False;
	
	// Destroy the vote map layer
	declare Ident LibMMCommon_LayerSendToServerId for This;
	if (UIManager.UILayers.existskey(LibMMCommon_LayerSendToServerId)) {
		declare LayerSendToServer = UIManager.UILayers[LibMMCommon_LayerSendToServerId];
		declare Removed = UIManager.UIAll.UILayers.remove(LayerSendToServer);
		UIManager.UILayerDestroy(LayerSendToServer);
		LibMMCommon_LayerSendToServerId = NullId;
	}
	
	// Destroy the matchmaking http requests
	ClearPendingRequests();	
}

// ---------------------------------- //
/// Load the library
Void Load() {
	Unload();
	
	// Create the send to server layer
	declare Ident LibMMCommon_LayerSendToServerId for This;
	declare LayerSendToServer = UIManager.UILayerCreate();
	LibMMCommon_LayerSendToServerId = LayerSendToServer.Id;
	LayerSendToServer.ManialinkPage = Private_GetMLSendToServer();
	if (!UIManager.UIAll.UILayers.exists(LayerSendToServer)) {
		UIManager.UIAll.UILayers.add(LayerSendToServer);
	}
}