/**
 * This class provides a property independent interface to g_PlayerAssignment events and actions.
 */
class PlayerAssignmentsController
{
	constructor(setupWindow, netMessages)
	{
		this.clientJoinHandlers = new Set();
		this.clientLeaveHandlers = new Set();
		this.playerAssignmentsChangeHandlers = new Set();

		if (!g_IsNetworked)
		{
			let name = singleplayerName();

			// Replace empty player name when entering a single-player match for the first time.
			Engine.ConfigDB_CreateAndWriteValueToFile("user", this.ConfigNameSingleplayer, name, "config/user.cfg");

			// By default, assign the player to the first slot.
			g_PlayerAssignments = {
				"local": {
					"name": name,
					"player": 1
				}
			};
		}

		// Keep a list of last assigned slot for each player, so we can try to re-assign them
		// if they disconnect/rejoin.
		this.lastAssigned = {};

		g_GameSettings.playerCount.watch(() => this.unassignInvalidPlayers(), ["nbPlayers"]);

		setupWindow.registerLoadHandler(this.onLoad.bind(this));
		setupWindow.registerGetHotloadDataHandler(this.onGetHotloadData.bind(this));
		netMessages.registerNetMessageHandler("players", this.onPlayerAssignmentMessage.bind(this));

		this.registerClientJoinHandler(this.onClientJoin.bind(this));
	}

	registerPlayerAssignmentsChangeHandler(handler)
	{
		this.playerAssignmentsChangeHandlers.add(handler);
	}

	unregisterPlayerAssignmentsChangeHandler(handler)
	{
		this.playerAssignmentsChangeHandlers.delete(handler);
	}

	registerClientJoinHandler(handler)
	{
		this.clientJoinHandlers.add(handler);
	}

	unregisterClientJoinHandler(handler)
	{
		this.clientJoinHandlers.delete(handler);
	}

	registerClientLeaveHandler(handler)
	{
		this.clientLeaveHandlers.add(handler);
	}

	unregisterClientLeaveHandler(handler)
	{
		this.clientLeaveHandlers.delete(handler);
	}

	onLoad(initData, hotloadData)
	{
		if (hotloadData)
		{
			g_PlayerAssignments = hotloadData.playerAssignments;
			this.updatePlayerAssignments();
		}
		else if (!g_IsNetworked)
		{
			// Simulate a net message for the local player to keep a common path.
			this.onPlayerAssignmentMessage({
				"newAssignments": g_PlayerAssignments
			});
		}
	}

	onGetHotloadData(object)
	{
		object.playerAssignments = g_PlayerAssignments;
	}

	/**
	 * On client join, try to assign them to a free slot.
	 * (This is called before g_PlayerAssignments is updated).
	 */
	onClientJoin(newGUID, newAssignments)
	{
		if (!g_IsController || newAssignments[newGUID].player != -1)
			return;
			
		//HostEnhanced -->START
		let playersBanned = botManagerHostEnhanced.get("ban").instance.getIds();		
		let joinedNameFull = newAssignments[newGUID].name
		let joinedName = splitRatingFromNick(newAssignments[newGUID].name).nick;
		log("debug:PlayerAssignmentsController.js:joinedNameFull:"+joinedNameFull);		
		if ( playersBanned.some(elem => elem.toLowerCase() === joinedName.toLowerCase())) {
				log("debug:player found in PERMABAN LIST");
				kickPlayer(joinedNameFull, true);
		}
		//HostEnhanced -->END

		// Assign the client (or only buddies if prefered) to a free slot
		if (newGUID != Engine.GetPlayerGUID())
		{
			const assignOption = Engine.ConfigDB_GetValue("user", this.ConfigAssignPlayers);
			if (assignOption == "disabled" ||
				assignOption == "buddies" && g_Buddies.indexOf(splitRatingFromNick(newAssignments[newGUID].name).nick) == -1)
				return;
		}

		// Find a player slot that no other player is assigned to.
		const possibleSlots = [...Array(g_GameSettings.playerCount.nbPlayers).keys()].map(i => i + 1);

		let slot;
		const newName = newAssignments[newGUID].name;
		// First check if we know them and try to give them their old assignment back.
		if (this.lastAssigned[newName] > 0 && this.lastAssigned[newName] <= g_GameSettings.playerCount.nbPlayers)
		{
			let free = true;
			for (const guid in newAssignments)
				if (newAssignments[guid].player === this.lastAssigned[newName])
				{
					free = false;
					break;
				}
			if (free)
				slot = this.lastAssigned[newName];
		}
		if (!slot)
			slot = possibleSlots.find(i => {
				for (const guid in newAssignments)
					if (newAssignments[guid].player == i)
						return false;
				return true;
			});
		if (slot === undefined)
			return;

		this.assignClient(newGUID, slot);
	}

	/**
	 * To be called when g_PlayerAssignments is modified.
	 */
	updatePlayerAssignments()
	{
		Engine.ProfileStart("updatePlayerAssignments");
		for (const guid in g_PlayerAssignments)
			this.lastAssigned[g_PlayerAssignments[guid].name] = g_PlayerAssignments[guid].player;
		for (const handler of this.playerAssignmentsChangeHandlers)
			handler();
		Engine.ProfileStop();
	}

	/**
	 * Called whenever a client joins or leaves or any game setting is changed.
	 */
	onPlayerAssignmentMessage(message)
	{
		let newAssignments = message.newAssignments;
		for (let guid in newAssignments)
			if (!g_PlayerAssignments[guid])
				for (let handler of this.clientJoinHandlers)
					handler(guid, message.newAssignments);

		for (let guid in g_PlayerAssignments)
			if (!newAssignments[guid])
				for (let handler of this.clientLeaveHandlers)
					handler(guid);

		g_PlayerAssignments = newAssignments;
		this.updatePlayerAssignments();
	}

	assignClient(guid, playerIndex)
	{
		if (g_IsNetworked)
			Engine.AssignNetworkPlayer(playerIndex, guid);
		else
		{
			g_PlayerAssignments[guid].player = playerIndex;
			this.updatePlayerAssignments();
		}
	}

	/**
	 * If both clients are assigned players, this will swap their assignments.
	 */
	assignPlayer(guidToAssign, playerIndex)
	{
		if (g_PlayerAssignments[guidToAssign].player != -1)
		{
			for (let guid in g_PlayerAssignments)
				if (g_PlayerAssignments[guid].player == playerIndex + 1)
				{
					this.assignClient(guid, g_PlayerAssignments[guidToAssign].player);
					break;
				}
		}
		this.assignClient(guidToAssign, playerIndex + 1);
	}

	unassignClient(playerID)
	{
		if (g_IsNetworked)
			Engine.AssignNetworkPlayer(playerID, "");
		else if (g_PlayerAssignments.local.player == playerID)
		{
			g_PlayerAssignments.local.player = -1;
			this.updatePlayerAssignments();
		}
	}

	unassignInvalidPlayers()
	{
		if (g_IsNetworked)
		{
			for (let guid in g_PlayerAssignments)
				if (g_PlayerAssignments[guid].player > g_GameSettings.playerCount.nbPlayers)
					Engine.AssignNetworkPlayer(g_PlayerAssignments[guid].player, "");
		}
		else if (g_PlayerAssignments.local.player > g_GameSettings.playerCount.nbPlayers)
		{
			g_PlayerAssignments.local.player = -1;
			this.updatePlayerAssignments();
		}
	}
}

PlayerAssignmentsController.prototype.ConfigNameSingleplayer =
	"playername.singleplayer";

PlayerAssignmentsController.prototype.ConfigAssignPlayers =
	"gui.gamesetup.assignplayers";
