const g_NaturalColor = "255 255 255 255"; // pure white

function StatusBars() {}

StatusBars.prototype.Schema =
	"<element name='BarWidth'>" +
		"<data type='decimal'/>" +
	"</element>" +
	"<element name='BarHeight' a:help='Height of a normal sized bar. Some bars are scaled accordingly.'>" +
		"<data type='decimal'/>" +
	"</element>" +
	"<element name='HeightOffset'>" +
		"<data type='decimal'/>" +
	"</element>";

/**
 * For every sprite, the code will call their "Add" method when regenerating
 * the sprites. Every sprite adder should return the height it needs.
 *
 * Modders who need extra sprites can just modify this array, and
 * provide the right methods.
 */
StatusBars.prototype.Sprites = [
	"ExperienceBar",
	"PackBar",
	"UpgradeBar",
	"ResourceSupplyBar",
	"CaptureBar",
	"HealthBar",
	"AuraIcons",
	"RankIcon"
];

StatusBars.prototype.Init = function()
{
	this.enabled = false;
	this.showRank = false;
	this.showExperience = false;

	// Whether the status bars used the player colors anywhere (e.g. in the capture bar)
	this.usedPlayerColors = false;

	this.auraSources = new Map();
};

/**
 * Don't serialise this.enabled since it's modified by the GUI.
 */
StatusBars.prototype.Serialize = function()
{
	return { "auraSources": this.auraSources };
};

StatusBars.prototype.Deserialize = function(data)
{
	this.Init();
	this.auraSources = data.auraSources;
};

StatusBars.prototype.SetEnabled = function(enabled, showRank, showExperience)
{
	// Quick return if no change
	if (enabled == this.enabled && showRank == this.showRank && showExperience == this.showExperience)
		return;

	this.enabled = enabled;
	this.showRank = showRank;
	this.showExperience = showExperience;

	// Update the displayed sprites
	this.RegenerateSprites();
};

StatusBars.prototype.AddAuraSource = function(source, auraName)
{
	if (this.auraSources.has(source))
		this.auraSources.get(source).push(auraName);
	else
		this.auraSources.set(source, [auraName]);
	this.RegenerateSprites();
};

StatusBars.prototype.RemoveAuraSource = function(source, auraName)
{
	let names = this.auraSources.get(source);
	names.splice(names.indexOf(auraName), 1);
	this.RegenerateSprites();
};

StatusBars.prototype.OnHealthChanged = function(msg)
{
	if (this.enabled)
		this.RegenerateSprites();
};

StatusBars.prototype.OnCapturePointsChanged = function(msg)
{
	if (this.enabled)
		this.RegenerateSprites();
};

StatusBars.prototype.OnResourceSupplyChanged = function(msg)
{
	if (this.enabled)
		this.RegenerateSprites();
};

StatusBars.prototype.OnPackProgressUpdate = function(msg)
{
	if (this.enabled)
		this.RegenerateSprites();
};

StatusBars.prototype.OnUpgradeProgressUpdate = function(msg)
{
	if (this.enabled)
		this.RegenerateSprites();
};

StatusBars.prototype.OnExperienceChanged = function()
{
	if (this.enabled)
		this.RegenerateSprites();
};

StatusBars.prototype.UpdateColor = function()
{
	if (this.usedPlayerColors)
		this.RegenerateSprites();
};

StatusBars.prototype.OnPlayerColorChanged = function(msg)
{
	if (this.enabled)
		this.RegenerateSprites();
};

StatusBars.prototype.RegenerateSprites = function()
{
	let cmpOverlayRenderer = Engine.QueryInterface(this.entity, IID_OverlayRenderer);
	cmpOverlayRenderer.Reset();

	let yoffset = 0;
	for (let sprite of this.Sprites)
		yoffset += this["Add" + sprite](cmpOverlayRenderer, yoffset);
};

// Internal helper functions
/**
 * Generic piece of code to add a bar.
 */
StatusBars.prototype.AddBar = function(cmpOverlayRenderer, yoffset, type, amount, heightMultiplier = 1)
{
	// Size of health bar (in world-space units)
	let width = +this.template.BarWidth;
	let height = +this.template.BarHeight * heightMultiplier;

	// World-space offset from the unit's position
	let offset = { "x": 0, "y": +this.template.HeightOffset, "z": 0 };

	// background
	cmpOverlayRenderer.AddSprite(
		"art/textures/ui/session/icons/" + type + "_bg.png",
		{ "x": -width / 2, "y": yoffset },
		{ "x": width / 2, "y": height + yoffset },
		offset,
		g_NaturalColor
	);

	// foreground
	cmpOverlayRenderer.AddSprite(
		"art/textures/ui/session/icons/" + type + "_fg.png",
		{ "x": -width / 2, "y": yoffset },
		{ "x": width * (amount - 0.5), "y": height + yoffset },
		offset,
		g_NaturalColor
	);

	return height * 1.2;
};

StatusBars.prototype.AddExperienceBar = function(cmpOverlayRenderer, yoffset)
{
	if (!this.enabled || !this.showExperience)
		return 0;

	let cmpPromotion = Engine.QueryInterface(this.entity, IID_Promotion);
	if (!cmpPromotion || !cmpPromotion.GetCurrentXp() || !cmpPromotion.GetRequiredXp())
		return 0;

	return this.AddBar(cmpOverlayRenderer, yoffset, "pack", cmpPromotion.GetCurrentXp() / cmpPromotion.GetRequiredXp(), 2/3);
};

StatusBars.prototype.AddPackBar = function(cmpOverlayRenderer, yoffset)
{
	if (!this.enabled)
		return 0;

	let cmpPack = Engine.QueryInterface(this.entity, IID_Pack);
	if (!cmpPack || !cmpPack.IsPacking())
		return 0;

	return this.AddBar(cmpOverlayRenderer, yoffset, "pack", cmpPack.GetProgress());
};

StatusBars.prototype.AddUpgradeBar = function(cmpOverlayRenderer, yoffset)
{
	if (!this.enabled)
		return 0;

	let cmpUpgrade = Engine.QueryInterface(this.entity, IID_Upgrade);
	if (!cmpUpgrade || !cmpUpgrade.IsUpgrading())
		return 0;

	return this.AddBar(cmpOverlayRenderer, yoffset, "upgrade", cmpUpgrade.GetProgress());
};

StatusBars.prototype.AddHealthBar = function(cmpOverlayRenderer, yoffset)
{
	if (!this.enabled)
		return 0;

	let cmpHealth = QueryMiragedInterface(this.entity, IID_Health);
	if (!cmpHealth || cmpHealth.GetHitpoints() <= 0)
		return 0;

	return this.AddBar(cmpOverlayRenderer, yoffset, "health", cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints());
};

StatusBars.prototype.AddResourceSupplyBar = function(cmpOverlayRenderer, yoffset)
{
	if (!this.enabled)
		return 0;

	let cmpResourceSupply = QueryMiragedInterface(this.entity, IID_ResourceSupply);
	if (!cmpResourceSupply)
		return 0;
	let value = cmpResourceSupply.IsInfinite() ? 1 : cmpResourceSupply.GetCurrentAmount() / cmpResourceSupply.GetMaxAmount();
	return this.AddBar(cmpOverlayRenderer, yoffset, "supply", value);
};

StatusBars.prototype.AddCaptureBar = function(cmpOverlayRenderer, yoffset)
{
	if (!this.enabled)
		return 0;

	let cmpCapturable = QueryMiragedInterface(this.entity, IID_Capturable);
	if (!cmpCapturable)
		return 0;

	let cmpOwnership = QueryMiragedInterface(this.entity, IID_Ownership);
	if (!cmpOwnership)
		return 0;

	let owner = cmpOwnership.GetOwner();
	if (owner == INVALID_PLAYER)
		return 0;

	this.usedPlayerColors = true;
	let capturePoints = cmpCapturable.GetCapturePoints();

	// Size of health bar (in world-space units)
	let width = +this.template.BarWidth;
	let height = +this.template.BarHeight;

	// World-space offset from the unit's position
	let offset = { "x": 0, "y": +this.template.HeightOffset, "z": 0 };

	let setCaptureBarPart = function(playerID, startSize)
	{
		let c = QueryPlayerIDInterface(playerID).GetDisplayedColor();
		let strColor = (c.r * 255) + " " + (c.g * 255) + " " + (c.b * 255) + " 255";
		let size = width * capturePoints[playerID] / cmpCapturable.GetMaxCapturePoints();

		cmpOverlayRenderer.AddSprite(
			"art/textures/ui/session/icons/capture_bar.png",
			{ "x": startSize, "y": yoffset },
			{ "x": startSize + size, "y": height + yoffset },
			offset,
			strColor
		);

		return size + startSize;
	};

	// First handle the owner's points, to keep those points on the left for clarity
	let size = setCaptureBarPart(owner, -width / 2);
	for (let i in capturePoints)
		if (i != owner && capturePoints[i] > 0)
			size = setCaptureBarPart(i, size);

	return height * 1.2;
};

StatusBars.prototype.AddAuraIcons = function(cmpOverlayRenderer, yoffset)
{
	let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
	let sources = cmpGuiInterface.GetEntitiesWithStatusBars().filter(e => this.auraSources.has(e) && this.auraSources.get(e).length);

	if (!sources.length)
		return 0;

	let iconSet = new Set();
	for (let ent of sources)
	{
		let cmpAuras = Engine.QueryInterface(ent, IID_Auras);
		if (!cmpAuras) // probably the ent just died
			continue;
		for (let name of this.auraSources.get(ent))
			iconSet.add(cmpAuras.GetOverlayIcon(name));
	}

	// World-space offset from the unit's position
	let offset = { "x": 0, "y": +this.template.HeightOffset + yoffset, "z": 0 };

	let iconSize = +this.template.BarWidth / 2;
	let xoffset = -iconSize * (iconSet.size - 1) * 0.6;
	for (let icon of iconSet)
	{
		cmpOverlayRenderer.AddSprite(
			icon,
			{ "x": xoffset - iconSize / 2, "y": yoffset },
			{ "x": xoffset + iconSize / 2, "y": iconSize + yoffset },
			offset,
			g_NaturalColor
		);
		xoffset += iconSize * 1.2;
	}

	return iconSize + this.template.BarHeight / 2;
};

StatusBars.prototype.AddRankIcon = function(cmpOverlayRenderer, yoffset)
{
	if (!this.enabled || !this.showRank)
		return 0;

	let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
	if (!cmpIdentity || !cmpIdentity.GetRank())
		return 0;

	let iconSize = +this.template.BarWidth / 2;
	cmpOverlayRenderer.AddSprite(
		"art/textures/ui/session/icons/ranks/" + cmpIdentity.GetRank() + ".png",
		{ "x": -iconSize / 2, "y": yoffset },
		{ "x": iconSize / 2, "y": iconSize + yoffset },
		{ "x": 0, "y": +this.template.HeightOffset + 0.1, "z": 0 },
		g_NaturalColor);

	return iconSize + this.template.BarHeight / 2;
};

Engine.RegisterComponentType(IID_StatusBars, "StatusBars", StatusBars);
