/**
 * Used to create player entities prior to reading the rest of a map,
 * all other initialization must be done after loading map (terrain/entities).
 * Be VERY careful in using other components here, as they may not be properly initialised yet.
 * settings is the object containing settings for this map.
 * newPlayers if true will remove old player entities or add new ones until
 * the new number of player entities is obtained
 * (used when loading a map or when Atlas changes the number of players).
 */
function LoadPlayerSettings(settings, newPlayers)
{
	const playerDefaults = Engine.ReadJSONFile("simulation/data/settings/player_defaults.json").PlayerData;
	const playerData = settings.PlayerData;
	if (!playerData)
		warn("Player.js: Setup has no player data - using defaults.");

	const getPlayerSetting = (idx, property) => {
		if (playerData && playerData[idx] && (property in playerData[idx]))
			return playerData[idx][property];

		if (playerDefaults && playerDefaults[idx] && (property in playerDefaults[idx]))
			return playerDefaults[idx][property];

		return undefined;
	};

	// Add gaia to simplify iteration
	// (if gaia is not already the first civ such as when called from Atlas' ActorViewer)
	if (playerData && playerData[0] && (!playerData[0].Civ || playerData[0].Civ != "gaia"))
		playerData.unshift(null);

	if (playerData && !playerData.some(v => v && !!v.AI))
		Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface).Disable();

	const cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
	let numPlayers = cmpPlayerManager.GetNumPlayers();

	// Remove existing players or add new ones
	if (newPlayers)
	{
		const settingsNumPlayers = playerData?.length ?? playerDefaults.length;

		while (numPlayers < settingsNumPlayers)
			cmpPlayerManager.AddPlayer(GetPlayerTemplateName(getPlayerSetting(numPlayers++, "Civ")));

		for (; numPlayers > settingsNumPlayers; numPlayers--)
			cmpPlayerManager.RemoveLastPlayer();
	}

	// Even when no new player, we must check the template compatibility as player templates are civ dependent.
	const cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
	for (let i = 0; i < numPlayers; ++i)
	{
		const template = GetPlayerTemplateName(getPlayerSetting(i, "Civ"));
		const entID = cmpPlayerManager.GetPlayerByID(i);
		if (cmpTemplateManager.GetCurrentTemplateName(entID) !== template)
			cmpPlayerManager.ReplacePlayerTemplate(i, template);
	}

	for (let i = 0; i < numPlayers; ++i)
	{
		QueryPlayerIDInterface(i, IID_Identity).SetName(getPlayerSetting(i, "Name"));

		const color = getPlayerSetting(i, "Color");
		const cmpPlayer = QueryPlayerIDInterface(i);
		cmpPlayer.SetColor(color.r, color.g, color.b);

		// Special case for gaia
		if (i === 0)
			continue;

		// Atlas has no information about player removal.
		cmpPlayer.SetRemoved(getPlayerSetting(i, "Removed") === true);

		// StartingResources
		if (settings.PlayerData[i].Resources !== undefined)
			cmpPlayer.SetResourceCounts(settings.PlayerData[i].Resources);
		else if (settings.StartingResources)
		{
			const resourceCounts = cmpPlayer.GetResourceCounts();
			const newResourceCounts = {};
			for (const resources in resourceCounts)
				newResourceCounts[resources] = settings.StartingResources;
			cmpPlayer.SetResourceCounts(newResourceCounts);
		}
		else if (playerDefaults[i].Resources !== undefined)
			cmpPlayer.SetResourceCounts(playerDefaults[i].Resources);

		if (settings.DisableSpies)
		{
			cmpPlayer.AddDisabledTechnology("unlock_spies");
			cmpPlayer.AddDisabledTemplate("special/spy");
		}

		// If diplomacy explicitly defined, use that; otherwise use teams.
		const diplomacy = getPlayerSetting(i, "Diplomacy");
		if (diplomacy !== undefined)
			QueryPlayerIDInterface(i, IID_Diplomacy).SetDiplomacy(diplomacy);
		else
			QueryPlayerIDInterface(i, IID_Diplomacy).ChangeTeam(getPlayerSetting(i, "Team") ?? -1);

		const formations = getPlayerSetting(i, "Formations");
		if (formations)
			cmpPlayer.SetFormations(formations);

		const startCam = getPlayerSetting(i, "StartingCamera");
		if (startCam)
			cmpPlayer.SetStartingCamera(startCam.Position, startCam.Rotation);
	}

	// NOTE: We need to do the team locking here, as
	// otherwise we can't ally the players above.
	if (settings.LockTeams)
		for (let i = 0; i < numPlayers; ++i)
			QueryPlayerIDInterface(i, IID_Diplomacy).LockTeam();
}

function GetPlayerTemplateName(civ)
{
	return "special/players/" + civ;
}

/**
 * @param id An entity's ID
 * @returns The entity ID of the owner player (not his player ID) or ent if ent is a player entity.
 */
function QueryOwnerEntityID(ent)
{
	const cmpPlayer = Engine.QueryInterface(ent, IID_Player);
	if (cmpPlayer)
		return ent;

	const cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
	if (!cmpOwnership)
		return null;

	const owner = cmpOwnership.GetOwner();
	if (owner == INVALID_PLAYER)
		return null;

	const cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
	if (!cmpPlayerManager)
		return null;

	return cmpPlayerManager.GetPlayerByID(owner);
}

/**
 * Similar to Engine.QueryInterface but applies to the player entity
 * that owns the given entity.
 * iid is typically IID_Player.
 */
function QueryOwnerInterface(ent, iid = IID_Player)
{
	var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
	if (!cmpOwnership)
		return null;

	var owner = cmpOwnership.GetOwner();
	if (owner == INVALID_PLAYER)
		return null;

	return QueryPlayerIDInterface(owner, iid);
}

/**
 * Similar to Engine.QueryInterface but applies to the player entity
 * with the given ID number.
 * iid is typically IID_Player.
 */
function QueryPlayerIDInterface(id, iid = IID_Player)
{
	var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);

	var playerEnt = cmpPlayerManager.GetPlayerByID(id);
	if (!playerEnt)
		return null;

	return Engine.QueryInterface(playerEnt, iid);
}

/**
 * Similar to Engine.QueryInterface but first checks if the entity
 * mirages the interface.
 */
function QueryMiragedInterface(ent, iid)
{
	const cmpMirage = Engine.QueryInterface(ent, IID_Mirage);
	if (cmpMirage && !cmpMirage.Mirages(iid))
		return null;
	else if (!cmpMirage)
		return Engine.QueryInterface(ent, iid);

	return cmpMirage.Get(iid);
}

/**
 * Similar to Engine.QueryInterface, but checks for all interfaces
 * implementing a builder list (currently Foundation and Repairable)
 * TODO Foundation and Repairable could both implement a BuilderList component
 */
function QueryBuilderListInterface(ent)
{
	return Engine.QueryInterface(ent, IID_Foundation) || Engine.QueryInterface(ent, IID_Repairable);
}

/**
 * Returns true if the entity 'target' is owned by an ally of
 * the owner of 'entity'.
 */
function IsOwnedByAllyOfEntity(entity, target)
{
	return IsOwnedByEntityHelper(entity, target, "IsAlly");
}

function IsOwnedByMutualAllyOfEntity(entity, target)
{
	return IsOwnedByEntityHelper(entity, target, "IsMutualAlly");
}

function IsOwnedByEntityHelper(entity, target, check)
{
	const owner = Engine.QueryInterface(entity, IID_Ownership)?.GetOwner() || 0;
	return IsOwnedByHelper(owner, target, check);
}

/**
 * Returns true if the entity 'target' is owned by player
 */
function IsOwnedByPlayer(player, target)
{
	var cmpOwnershipTarget = Engine.QueryInterface(target, IID_Ownership);
	return cmpOwnershipTarget && player == cmpOwnershipTarget.GetOwner();
}

function IsOwnedByGaia(target)
{
	return IsOwnedByPlayer(0, target);
}

/**
 * Returns true if the entity 'target' is owned by an ally of player
 */
function IsOwnedByAllyOfPlayer(player, target)
{
	return IsOwnedByHelper(player, target, "IsAlly");
}

function IsOwnedByMutualAllyOfPlayer(player, target)
{
	return IsOwnedByHelper(player, target, "IsMutualAlly");
}

function IsOwnedByNeutralOfPlayer(player, target)
{
	return IsOwnedByHelper(player, target, "IsNeutral");
}

function IsOwnedByEnemyOfPlayer(player, target)
{
	return IsOwnedByHelper(player, target, "IsEnemy");
}

function IsOwnedByHelper(player, target, check)
{
	const targetOwner = Engine.QueryInterface(target, IID_Ownership)?.GetOwner() || 0;
	const cmpDiplomacy = QueryPlayerIDInterface(player, IID_Diplomacy);
	return cmpDiplomacy && cmpDiplomacy[check](targetOwner);
}

Engine.RegisterGlobal("LoadPlayerSettings", LoadPlayerSettings);
Engine.RegisterGlobal("QueryOwnerEntityID", QueryOwnerEntityID);
Engine.RegisterGlobal("QueryOwnerInterface", QueryOwnerInterface);
Engine.RegisterGlobal("QueryPlayerIDInterface", QueryPlayerIDInterface);
Engine.RegisterGlobal("QueryMiragedInterface", QueryMiragedInterface);
Engine.RegisterGlobal("QueryBuilderListInterface", QueryBuilderListInterface);
Engine.RegisterGlobal("IsOwnedByAllyOfEntity", IsOwnedByAllyOfEntity);
Engine.RegisterGlobal("IsOwnedByMutualAllyOfEntity", IsOwnedByMutualAllyOfEntity);
Engine.RegisterGlobal("IsOwnedByPlayer", IsOwnedByPlayer);
Engine.RegisterGlobal("IsOwnedByGaia", IsOwnedByGaia);
Engine.RegisterGlobal("IsOwnedByAllyOfPlayer", IsOwnedByAllyOfPlayer);
Engine.RegisterGlobal("IsOwnedByMutualAllyOfPlayer", IsOwnedByMutualAllyOfPlayer);
Engine.RegisterGlobal("IsOwnedByNeutralOfPlayer", IsOwnedByNeutralOfPlayer);
Engine.RegisterGlobal("IsOwnedByEnemyOfPlayer", IsOwnedByEnemyOfPlayer);
