/** 
 *	ClubLink library
 *	Allow a mode to get informations about the teams 
 *	on the server from Club xml files
 */

#Const Version		"2013-03-26"
#Const ScriptName	"Clublink.Script.txt"

#Include "TextLib" as TL

// ---------------------------------- //
// Constants
// ---------------------------------- //
#Const C_LibClubLink_TeamDefaultName	["Blue", "Red"]
#Const C_LibClubLink_TeamDefaultColor	[<0.,0.,1.>, <1.,0.,0.>]
#Const C_LibClubLink_RequestTimeout		5000

// ---------------------------------- //
// Globales
// ---------------------------------- //
declare Boolean					G_LibClubLink_Loaded;				///< State of the library
declare Boolean					G_LibClubLink_SettingForceClans;	///< Only the players listed in the Clubs can play
declare Boolean					G_LibClubLink_ForceClans;			///< Only the players listed in the Clubs can play
declare Ident[Integer]			G_LibClubLink_SourcePlayerId;		///< Id of the player owning the ClubLink to load
declare Ident[Integer]			G_LibClubLink_LoadingPlayerId;		///< Id of the player owning the ClubLink currently loading
declare Ident[Integer]			G_LibClubLink_LoadedPlayerId;		///< Id of the player owning the loaded ClubLink
declare Integer[Integer]		G_LibClubLink_RequestStartTime;		///< StartTime of the player ClubLink HTTP request
declare CHttpRequest[Integer]	G_LibClubLink_Request;				///< ClubLink request of each clan
declare Ident					G_LibClubLink_LayerTeamsId;			///< Id of the team layer
declare Text[]					G_LibClubLink_TeamDefaultName;		///< Default name for each team
declare Vec3[]					G_LibClubLink_TeamDefaultColor;		///< Default color for each team

// ---------------------------------- //
// Functions
// ---------------------------------- //

// ---------------------------------- //
// Private
// ---------------------------------- //

// ---------------------------------- //
/** Create the teams layer
 *
 *	@return		The teams layer
 */
Text Private_CreateLayerTeams() {
	return """
<frame posn="0 40" id="Frame_Spectators" hidden="1">
	<quad posn="-159 0" sizen="40 20" halign="left"  id="Quad_SponsorTeam1" />
	<quad posn=" 159 0" sizen="40 20" halign="right" id="Quad_SponsorTeam2" />
</frame>
<script><!--
#Const C_SponsorCyclingDuration 5000

main() {
	declare Frame_Spectators <=> (Page.GetFirstChild("Frame_Spectators") as CMlFrame);
	declare Quad_Sponsor = [
		(Frame_Spectators.GetFirstChild("Quad_SponsorTeam1") as CMlQuad),
		(Frame_Spectators.GetFirstChild("Quad_SponsorTeam2") as CMlQuad)
	];
	
	while (True) {
		sleep(1000);
		
		// Display sponsors for spectators at the start/end of the map and the end of the round
		declare DisplaySponsors =	IsSpectatorMode 
								 	&& (
										UI.UISequence == CUIConfig::EUISequence::Podium 
										|| UI.UISequence == CUIConfig::EUISequence::EndRound 
										|| UI.UISequence == CUIConfig::EUISequence::Intro
									);
		Frame_Spectators.Visible = DisplaySponsors;
		
		if (DisplaySponsors) {
			for (I, 0, 1) {
				declare netread Text[] LibClubLink_TeamSponsors for Teams[I];
				declare Text Sponsor = "";
				if (LibClubLink_TeamSponsors.count > 0) Sponsor = LibClubLink_TeamSponsors[(Now / C_SponsorCyclingDuration) % LibClubLink_TeamSponsors.count];
				Quad_Sponsor[I].ImageUrl	= Sponsor;
				Quad_Sponsor[I].Visible		= (Sponsor != "");
			}
		} else {
			for (I, 0, 1) Quad_Sponsor[I].Hide();
		}
	}
}
--></script>
""";
}

// ---------------------------------- //
/** Read values from a node
 *
 *	@param	_Node		The parent node to use
 *	@param	_ChildName	The node to search
 *	@param	_AttribName	The attribute to read, if empty get the node text content
 *
 *	@return		The value if found, an empty string otherwise
 */
Text Private_ReadXmlText(CXmlNode _Node, Text _ChildName, Text _AttribName) {
	if (_Node == Null) return "";
	declare Elem = _Node.GetFirstChild(_ChildName);
	if (Elem == Null) return "";
	if (_AttribName != "") return Elem.GetAttributeText(_AttribName, "");
	
	return Elem.TextContents;
}

// ---------------------------------- //
/// Overload of Text Private_ReadXmlText(CXmlNode _Node, Text _ChildName, Text _AttribName)
Text Private_ReadXmlText(CXmlNode _Node, Text _ChildName) {
	return Private_ReadXmlText(_Node, _ChildName, "");
}

// ---------------------------------- //
/** Check that all the players in a clan are listed in the clublink before activating it
 *
 *	@param	_ClanNb		The clan to set
 *	@param	_Xml		The XML document containing the info
 *	@param	_ClubLinkUrl	The url of the clublink to use
 *
 *	@return				True if all the players of the clan are listed in the clublink
 */
Boolean Private_CheckPlayersList(Integer _ClanNb, CXmlDocument _Xml, Text _ClubLinkUrl) {
	declare CXmlNode Root;
	if (_Xml != Null && _Xml.Root != Null && _Xml.Root.Name == "club") Root <=> _Xml.Root;
	else return False;
	
	declare Logins = Text[];
	declare TeamPlayers = Root.GetFirstChild("players");
	if (TeamPlayers != Null) {
		foreach (Player in TeamPlayers.Children) {
			if (Player.Name != "player") continue;
			declare Login = Player.GetAttributeText("login", "");
			if (Login == "") continue;
			Logins.add(Login);
		}
	}
	
	foreach (Player in Players) {
		if (Player.CurrentClan != _ClanNb) continue;
		if (Player.User.ClubLink != _ClubLinkUrl) return False;
		if (!Logins.exists(Player.Login)) return False;
	}
	
	return True;
}

// ---------------------------------- //
/// Check if a team has a defined player list
Void Private_SwitchForceClan() {
	if (!G_LibClubLink_SettingForceClans) return;
	
	declare HasPlayerList = False;
	for (I, 0, 1) {
		declare Text[] LibClubLink_TeamPlayerLogins for Teams[I];
		if (LibClubLink_TeamPlayerLogins.count > 0) {
			HasPlayerList = True; 
			break;
		}
	}
	if (HasPlayerList) {
		UseForcedClans				= True;
		G_LibClubLink_ForceClans	= True;
	} else {
		UseForcedClans				= False;
		G_LibClubLink_ForceClans	= False;
	}
}

// ---------------------------------- //
/** Reset the team info to their default value
 *
 *	@param	_ClanNb		The clan to reset
 */
Void Private_ResetClan(Integer _ClanNb) {
	if (G_LibClubLink_Loaded) {
		G_LibClubLink_SourcePlayerId[_ClanNb]	= NullId;
		G_LibClubLink_LoadingPlayerId[_ClanNb]	= NullId;
		G_LibClubLink_LoadedPlayerId[_ClanNb]	= NullId;
		G_LibClubLink_RequestStartTime[_ClanNb]	= -1;
		if (G_LibClubLink_Request[_ClanNb] != Null) Http.Destroy(G_LibClubLink_Request[_ClanNb]);
		G_LibClubLink_Request[_ClanNb] = Null;
	}
	
	declare Team <=> Teams[_ClanNb - 1];
	Team.Name		= G_LibClubLink_TeamDefaultName[_ClanNb - 1];
	Team.ZonePath	= "";
	Team.City		= "";
	Team.EmblemUrl	= "";
	Team.PresentationManialinkUrl = "";
	Team.ClubLinkUrl = "";
	Team.ColorPrimary	= G_LibClubLink_TeamDefaultColor[_ClanNb - 1];
	Team.ColorSecondary	= G_LibClubLink_TeamDefaultColor[_ClanNb - 1];
	
	declare netwrite Text[] LibClubLink_TeamSponsors for Team;
	declare Text[] LibClubLink_TeamPlayerLogins for Team;
	declare Text[] LibClubLink_TeamPlayerNicknames for Team;
	declare Text[] LibClubLink_TeamPlayerAvatars for Team;
	LibClubLink_TeamSponsors.clear();
	LibClubLink_TeamPlayerLogins.clear();
	LibClubLink_TeamPlayerNicknames.clear();
	LibClubLink_TeamPlayerAvatars.clear();
	
	//Private_SwitchForceClan();
}

// ---------------------------------- //
/** Set the team info from XML
 *
 *	@param	_ClanNb			The clan to set
 *	@param	_Xml			The XML document containing the info
 *	@param	_ClubLinkUrl	The url of the clublink to use
 */
Void Private_SetClan(Integer _ClanNb, CXmlDocument _Xml, Text _ClubLinkUrl) {
	declare CXmlNode Root;
	if (_Xml != Null && _Xml.Root != Null && _Xml.Root.Name == "club") Root <=> _Xml.Root;
	else return;
	
	if (UseClans && !Private_CheckPlayersList(_ClanNb, _Xml, _ClubLinkUrl)) {
		Private_ResetClan(_ClanNb);
		return;
	}
	
	declare Team <=> Teams[_ClanNb - 1];
	Team.Name		= Private_ReadXmlText(Root, "name");
	Team.ZonePath	= Private_ReadXmlText(Root, "zone");
	Team.City		= Private_ReadXmlText(Root, "city");
	Team.EmblemUrl	= Private_ReadXmlText(Root, "emblem");
	Team.ClubLinkUrl= _ClubLinkUrl;
	
	declare TextColor = Private_ReadXmlText(Root, "color", "primary");
	if (TextColor != "")	Team.ColorPrimary = TL::ToColor(TextColor);
	else					Team.ColorPrimary = G_LibClubLink_TeamDefaultColor[_ClanNb - 1];
	
	declare netwrite Text[] LibClubLink_TeamSponsors for Team;
	LibClubLink_TeamSponsors.clear();
	declare Sponsors = Root.GetFirstChild("sponsors");
	if (Sponsors != Null) {
		foreach (Sponsor in Sponsors.Children) {
			if (Sponsor.Name != "sponsor") continue;
			declare Url = Private_ReadXmlText(Sponsor, "image_2x1");
			if (Http.IsValidUrl(Url)) LibClubLink_TeamSponsors.add(Url);
		}
	}
	
	declare Text[] LibClubLink_TeamPlayerLogins for Team;
	declare Text[] LibClubLink_TeamPlayerNicknames for Team;
	declare Text[] LibClubLink_TeamPlayerAvatars for Team;
	LibClubLink_TeamPlayerLogins.clear();
	LibClubLink_TeamPlayerNicknames.clear();
	LibClubLink_TeamPlayerAvatars.clear();
	declare TeamPlayers = Root.GetFirstChild("players");
	if (TeamPlayers != Null) {
		foreach (Player in TeamPlayers.Children) {
			if (Player.Name != "player") continue;
			declare Login = Player.GetAttributeText("login", "");
			if (Login == "") continue;
			declare Nickname = Private_ReadXmlText(Player, "nickname");
			declare Avatar =  Private_ReadXmlText(Player, "avatar");
			if (!Http.IsValidUrl(Avatar)) Avatar = "";
			
			LibClubLink_TeamPlayerLogins.add(Login);
			LibClubLink_TeamPlayerNicknames.add(Nickname);
			LibClubLink_TeamPlayerAvatars.add(Avatar);
		}
	}
	
	//Private_SwitchForceClan();
}

// ---------------------------------- //
/// Automatically set player clan based on the clublinks players list
Void Private_EnforcePlayerPolicy() {
	declare Text[][Integer] TeamPlayers;
	for (I, 0, 1) {
		declare Text[] LibClubLink_TeamPlayerLogins for Teams[I];
		TeamPlayers[I + 1] = LibClubLink_TeamPlayerLogins;
	}
	foreach (Player in Players) {
		declare NewClan = 0;
		if (TeamPlayers[1].count > 0 && TeamPlayers[1].exists(Player.Login)) {
			if (Player.CurrentClan != 1) {
				UnspawnPlayer(Player);
				SetPlayerClan(Player, 1);
			}
			continue;
		}
		if (TeamPlayers[2].count > 0 && TeamPlayers[2].exists(Player.Login)) {
			if (Player.CurrentClan != 2) {
				UnspawnPlayer(Player);
				SetPlayerClan(Player, 2);
			}
			continue;
		}
		if (TeamPlayers[1].count <= 0 && TeamPlayers[1].count <= TeamPlayers[2].count) {
			if (Player.CurrentClan != 1) {
				UnspawnPlayer(Player);
				SetPlayerClan(Player, 1);
			}
			continue;
		}
		if (TeamPlayers[2].count <= 0 && TeamPlayers[2].count <= TeamPlayers[1].count) {
			if (Player.CurrentClan != 2) {
				UnspawnPlayer(Player);
				SetPlayerClan(Player, 2);
			}
			continue;
		}
		declare LibClubLink_LastWarning for Player = 0;
		/*if (LibClubLink_LastWarning + 1000 < Now) {
			LibClubLink_LastWarning = Now;
			declare UI <=> UIManager.GetUI(Player);
			if (UI != Null) UI.SendChat("$fffYou're not in the list of allowed players. You've been sent into spectator mode.");
			log(Now^"> "^Player.Login^" > Go to spectator > CurrentClan: "^Player.CurrentClan);
		}*/
		Users_RequestSwitchToSpectator(Player.User);
	}
}

// ---------------------------------- //
// 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;
}

// ---------------------------------- //
// Unload the library
Void Unload() {
	Private_ResetClan(1);
	Private_ResetClan(2);
	
	foreach (Req in G_LibClubLink_Request) {
		if (Req != Null) Http.Destroy(Req);
	}
	
	G_LibClubLink_SourcePlayerId.clear();
	G_LibClubLink_LoadingPlayerId.clear();
	G_LibClubLink_LoadedPlayerId.clear();
	G_LibClubLink_RequestStartTime.clear();
	G_LibClubLink_Request.clear();
	
	declare Removed = UIManager.UIAll.UILayers.removekey(G_LibClubLink_LayerTeamsId);
	if (UIManager.UILayers.existskey(G_LibClubLink_LayerTeamsId)) UIManager.UILayerDestroy(UIManager.UILayers[G_LibClubLink_LayerTeamsId]);
	G_LibClubLink_LayerTeamsId = NullId;
	
	UseForcedClans					= False;
	G_LibClubLink_SettingForceClans	= False;
	G_LibClubLink_ForceClans		= False;
	G_LibClubLink_Loaded			= False;
}

// ---------------------------------- //
/** Load the library
 *
 *	@param	_UsePlayerClubLinks			Active the use of the players clublinks
 *	@param	_ForceClansFromClublink		Force the player's clan based on the clublink players list
 */
Void Load(Boolean _UsePlayerClubLinks/*, Boolean _ForceClansFromClubLinks*/) {
	G_LibClubLink_TeamDefaultName = C_LibClubLink_TeamDefaultName;
	G_LibClubLink_TeamDefaultColor = C_LibClubLink_TeamDefaultColor;
	
	Unload();
	
	if (!_UsePlayerClubLinks) return;
	
	G_LibClubLink_SourcePlayerId	= [1 => NullId, 2 => NullId];
	G_LibClubLink_LoadingPlayerId	= [1 => NullId, 2 => NullId];
	G_LibClubLink_LoadedPlayerId	= [1 => NullId, 2 => NullId];
	G_LibClubLink_RequestStartTime	= [1 => -1, 2 => -1];
	G_LibClubLink_Request			= [1 => Null, 2 => Null];
	
	declare LayerTeams <=> UIManager.UILayerCreate();
	declare G_LibClubLink_LayerTeamsId = LayerTeams.Id;
	LayerTeams.ManialinkPage = Private_CreateLayerTeams();
	UIManager.UIAll.UILayers.add(LayerTeams);
	
	UseForcedClans					= False;
	//G_LibClubLink_SettingForceClans	= _ForceClansFromClubLinks;
	G_LibClubLink_SettingForceClans	= False;	///< Not fully implemented
	G_LibClubLink_ForceClans		= False;
	G_LibClubLink_Loaded			= True;
}

// ---------------------------------- //
/** Set the default name of a team
 *
 *	@param	_Team		The team to update
 *	@param	_Name		The new default name of the team
 */
Void SetTeamDefaultName(Integer _Team, Text _Name) {
	if (!G_LibClubLink_Loaded) return;
	if (!Teams.existskey(_Team)) return;
	
	G_LibClubLink_TeamDefaultName[_Team] = _Name;
	if (G_LibClubLink_LoadedPlayerId[_Team + 1] == NullId) Teams[_Team].Name = _Name;
}

// ---------------------------------- //
/** Set the default color of a team
 *
 *	@param	_Team		The team to update
 *	@param	_Color		The new default color of the team
 */
Void SetTeamDefaultColor(Integer _Team, Vec3 _Color) {
	if (!G_LibClubLink_Loaded) return;
	if (!Teams.existskey(_Team)) return;
	
	G_LibClubLink_TeamDefaultColor[_Team] = _Color;
	if (G_LibClubLink_LoadedPlayerId[_Team + 1] == NullId) Teams[_Team].ColorPrimary = _Color;
}

// ---------------------------------- //
/// Manage HTTP requests (asynchrone)
Void Update() {
	if (!G_LibClubLink_Loaded) return;
	
	//if (G_LibClubLink_ForceClans) Private_EnforcePlayerPolicy();
	
	for (I, 1, 2) {
		// Stop the previous request if a new source player is set
		if (G_LibClubLink_RequestStartTime[I] >= 0 && G_LibClubLink_SourcePlayerId[I] != G_LibClubLink_LoadingPlayerId[I]) {
			G_LibClubLink_LoadingPlayerId[I]	= NullId;
			G_LibClubLink_RequestStartTime[I]	= -1;
			Http.Destroy(G_LibClubLink_Request[I]);
			G_LibClubLink_Request[I]			= Null;
		}
		
		// Skip the update if no source player is set
		if (G_LibClubLink_SourcePlayerId[I] == NullId) continue;
		
		// If the source player doesn't exist anymore
		if (!Players.existskey(G_LibClubLink_SourcePlayerId[I])) {
			Private_ResetClan(I);
			continue;
		}
		// If the source player is not in the same team anymore
		else if (Players[G_LibClubLink_SourcePlayerId[I]].CurrentClan != I) {
			Private_ResetClan(I);
			continue;
		}
		
		// Receive the request
		if (G_LibClubLink_RequestStartTime[I] >= 0) {
			if (G_LibClubLink_Request[I] != Null && G_LibClubLink_Request[I].IsCompleted) {
				G_LibClubLink_LoadingPlayerId[I]	= NullId;
				G_LibClubLink_LoadedPlayerId[I]		= G_LibClubLink_SourcePlayerId[I];
				G_LibClubLink_RequestStartTime[I]	= -1;
				
				declare ClubLinkXml <=> Xml.Create(G_LibClubLink_Request[I].Result);
				if (ClubLinkXml != Null) {
					Private_SetClan(I, ClubLinkXml, G_LibClubLink_Request[I].Url);
					Xml.Destroy(ClubLinkXml);
					ClubLinkXml <=> Null;
				}
				
				Http.Destroy(G_LibClubLink_Request[I]);
				G_LibClubLink_Request[I] = Null;
			}
		} 
		// Send the request
		else if (G_LibClubLink_SourcePlayerId[I] != NullId && G_LibClubLink_SourcePlayerId[I] != G_LibClubLink_LoadedPlayerId[I]) {
			declare Req <=> Http.CreateGet(Players[G_LibClubLink_SourcePlayerId[I]].User.ClubLink);
			G_LibClubLink_LoadingPlayerId[I] = G_LibClubLink_SourcePlayerId[I];
			G_LibClubLink_RequestStartTime[I] = Now;
			G_LibClubLink_Request[I] = Req;
		}
	}
}

// ---------------------------------- //
/// Load the selected Clublink (synchronous) 
Void SyncUpdate() {
	if (!G_LibClubLink_Loaded) return;
	
	for (I, 1, 2) {
		// Stop the previous request if a new source player is set
		if (G_LibClubLink_RequestStartTime[I] >= 0 && G_LibClubLink_SourcePlayerId[I] != G_LibClubLink_LoadingPlayerId[I]) {
			G_LibClubLink_LoadingPlayerId[I]	= NullId;
			G_LibClubLink_RequestStartTime[I]	= -1;
			Http.Destroy(G_LibClubLink_Request[I]);
			G_LibClubLink_Request[I]			= Null;
		}
		
		// Skip the update if no source player is set
		if (G_LibClubLink_SourcePlayerId[I] == NullId) continue;
		
		// If the source player doesn't exist anymore
		if (!Players.existskey(G_LibClubLink_SourcePlayerId[I])) {
			Private_ResetClan(I);
			continue;
		}
		// If the source player is not in the same team anymore
		else if (Players[G_LibClubLink_SourcePlayerId[I]].CurrentClan != I) {
			Private_ResetClan(I);
			continue;
		}
		
		// Send the request
		if (G_LibClubLink_SourcePlayerId[I] != NullId && G_LibClubLink_SourcePlayerId[I] != G_LibClubLink_LoadedPlayerId[I]) {
			declare Req <=> Http.CreateGet(Players[G_LibClubLink_SourcePlayerId[I]].User.ClubLink);
			G_LibClubLink_LoadingPlayerId[I] = G_LibClubLink_SourcePlayerId[I];
			G_LibClubLink_RequestStartTime[I] = Now;
			G_LibClubLink_Request[I] = Req;
		}
		
		// Receive the request
		if (G_LibClubLink_RequestStartTime[I] >= 0) {
		
			declare RequestTimeout = Now + C_LibClubLink_RequestTimeout;
			if (G_LibClubLink_Request[I] != Null) {
				declare Req <=> G_LibClubLink_Request[I];
				while (!Req.IsCompleted && RequestTimeout > Now) yield;
			}
		
			if (G_LibClubLink_Request[I] != Null && G_LibClubLink_Request[I].IsCompleted) {
				G_LibClubLink_LoadingPlayerId[I]	= NullId;
				G_LibClubLink_LoadedPlayerId[I]		= G_LibClubLink_SourcePlayerId[I];
				G_LibClubLink_RequestStartTime[I]	= -1;
				
				declare ClubLinkXml <=> Xml.Create(G_LibClubLink_Request[I].Result);
				if (ClubLinkXml != Null) {
					Private_SetClan(I, ClubLinkXml, G_LibClubLink_Request[I].Url);
					Xml.Destroy(ClubLinkXml);
					ClubLinkXml <=> Null;
				}
				
				Http.Destroy(G_LibClubLink_Request[I]);
				G_LibClubLink_Request[I] = Null;
			}
		} 
	}
}

// ---------------------------------- //
/** Manually define the players that'll be used as sources for the teams info
 *
 *	@param	_Player1	Source for team 1
 *	@param	_Player2	Source for team 2
 */
Void DefineTeamFromPlayers(CSmPlayer _Player1, CSmPlayer _Player2) {
	if (!G_LibClubLink_Loaded) return;
	
	if (_Player1 != Null) G_LibClubLink_SourcePlayerId[1] = _Player1.Id;
	else G_LibClubLink_SourcePlayerId[1] = NullId;
	if (_Player2 != Null) G_LibClubLink_SourcePlayerId[2] = _Player2.Id;
	else G_LibClubLink_SourcePlayerId[2] = NullId;
}

// ---------------------------------- //
/** /!\ WARNING: this function is synchronous
 *	The script will wait until it receives a response or timeout
 *
 *	Manually define the clublinks that'll be used as sources for the teams info
 *
 *	@param	_Url1	Url of the team 1 clublink
 *	@param	_Url2	Url of the team 2 clublink
 */
Void DefineTeamFromUrl(Text _Url1, Text _Url2) {
	if (!G_LibClubLink_Loaded) return;
	
	Private_ResetClan(1);
	Private_ResetClan(2);
	
	declare ClubLinkUrl = [_Url1, _Url2];
	for(I, 0, 1) {
		declare Url = ClubLinkUrl[I];
		if (!Http.IsValidUrl(Url)) continue;
		declare Req <=> Http.CreateGet(Url);
		declare RequestTimeout = Now + C_LibClubLink_RequestTimeout;
		
		while (!Req.IsCompleted && RequestTimeout > Now) yield;
		
		if (Req.IsCompleted) {
			declare ClubLinkXml <=> Xml.Create(Req.Result);
			if (ClubLinkXml != Null) {
				Private_SetClan(I + 1, ClubLinkXml, Req.Url);
				Xml.Destroy(ClubLinkXml);
				ClubLinkXml <=> Null;
			}
			Http.Destroy(Req);
			Req <=> Null;
		} else {
			log(Now^"> Error > couldn't load the clublink: "^Url);
		}
	}
}

// ---------------------------------- //
/** Automatically find the players that'll be used as source for the teams info
 *	If the update is not forced, then we check if the current
 *	source players are still here beforehand
 *
 *	@param	_Forced		Force the update
 */
Void DefineTeamAuto(Boolean _Forced) {
	if (!G_LibClubLink_Loaded) return;
	
	declare Clan1Id = G_LibClubLink_SourcePlayerId[1];
	declare Clan2Id = G_LibClubLink_SourcePlayerId[2];	
	declare UpdateClan1 = False;
	declare UpdateClan2 = False;
	
	if (_Forced) {
		UpdateClan1 = True;
		UpdateClan2 = True;
	} else {
		if (Clan1Id == NullId) UpdateClan1 = True;
		else if (!Players.existskey(Clan1Id)) UpdateClan1 = True;
		else if (Players[Clan1Id].CurrentClan != 1) UpdateClan1 = True;
		
		if (Clan2Id == NullId) UpdateClan2 = True;
		else if (!Players.existskey(Clan2Id)) UpdateClan2 = True;
		else if (Players[Clan2Id].CurrentClan != 2) UpdateClan2 = True;
	}
	
	if (!UpdateClan1 && !UpdateClan2) return;
	
	foreach (Player in Players) {
		if (Player.User.ClubLink == "") continue;
		if (!Http.IsValidUrl(Player.User.ClubLink)) continue;
		
		if (UpdateClan1 && Player.CurrentClan == 1) {
			if (Players.existskey(G_LibClubLink_SourcePlayerId[2])) {
				if (Player.User.ClubLink == Players[G_LibClubLink_SourcePlayerId[2]].User.ClubLink) continue;
			}
			G_LibClubLink_SourcePlayerId[1] = Player.Id;
		} else if (UpdateClan2 && Player.CurrentClan == 2) {
			if (Players.existskey(G_LibClubLink_SourcePlayerId[1])) {
				if (Player.User.ClubLink == Players[G_LibClubLink_SourcePlayerId[1]].User.ClubLink) continue;
			}
			G_LibClubLink_SourcePlayerId[2] = Player.Id;
		}
	}
	
	if (G_LibClubLink_SourcePlayerId[1] == NullId && G_LibClubLink_LoadedPlayerId[1] != NullId) Private_ResetClan(1);
	if (G_LibClubLink_SourcePlayerId[2] == NullId && G_LibClubLink_LoadedPlayerId[2] != NullId) Private_ResetClan(2);
}

// ---------------------------------- //
/// Overload of Void DefineTeamAuto(Boolean _Forced)
Void DefineTeamAuto() {
	if (!G_LibClubLink_Loaded) return;
	
	if (ForcedClubLinkUrl1 != "" && ForcedClubLinkUrl2 != "" )
		DefineTeamFromUrl(ForcedClubLinkUrl1, ForcedClubLinkUrl2);
	else
		DefineTeamAuto(False);
}

// ---------------------------------- //
/** Get the player used as the source for team info
 *
 *	@param	_ClanNb		The clan of the source player
 *
 *	@param		The source player if found, Null otherwise
 */
CSmPlayer GetSourcePlayer(Integer _ClanNb) {
	if (!G_LibClubLink_Loaded) return Null;
	
	if (Players.existskey(G_LibClubLink_SourcePlayerId[_ClanNb])) return Players[G_LibClubLink_SourcePlayerId[_ClanNb]];
	return Null;
}

// ---------------------------------- //
/** Get the clan selection status
 *
 *	@return		True if the clans are forced, false otherwise
 */
Boolean ClansAreForced() {
	return G_LibClubLink_ForceClans;
}

// ---------------------------------- //
/** Get the right requested clan for a player
 *	based on the clan selection status
 *
 *	@param	_Player		The player to search for
 *
 *	@return		The requested clan of the player
 */
Integer GetPlayerRequestedClan(CSmPlayer _Player) {
	if (_Player == Null) return 0;
	if (!G_LibClubLink_ForceClans) return _Player.RequestedClan;
	else {
		
	}
	
	return 0;
}

CSmPlayer[] GetAllowedPlayers(Integer _ClanNb) {
	declare CSmPlayer[] AllowedPlayers;
	if (!G_LibClubLink_Loaded) AllowedPlayers;
	
	return AllowedPlayers;
}

Void UpdateSetup() {
	if (!G_LibClubLink_Loaded) return;
	
}