#region Copyright & License Information
/*
 * Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
 * This file is part of OpenRA, which is free software. It is made
 * available to you under the terms of the GNU General Public License
 * as published by the Free Software Foundation. For more information,
 * see COPYING.
 */
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
	[Desc("Attach this to an actor (usually a building) to let it produce units or construct buildings.",
		"If one builds another actor of this type, he will get a separate queue to create two actors",
		"at the same time. Will only work together with the Production: trait.")]
	public class ProductionQueueInfo : ITraitInfo
	{
		[FieldLoader.Require]
		[Desc("What kind of production will be added (e.g. Building, Infantry, Vehicle, ...)")]
		public readonly string Type = null;
		[Desc("Group queues from separate buildings together into the same tab.")]
		public readonly string Group = null;
		[Desc("Only enable this queue for certain factions.")]
		public readonly HashSet<string> Factions = new HashSet<string>();
		[Desc("Should the prerequisite remain enabled if the owner changes?")]
		public readonly bool Sticky = true;
		[Desc("This value is used to translate the unit cost into build time.")]
		public readonly float BuildSpeed = 0.4f;
		[Desc("The build time is multiplied with this value on low power.")]
		public readonly int LowPowerSlowdown = 3;
		[Desc("Notification played when production is complete.",
			"The filename of the audio is defined per faction in notifications.yaml.")]
		public readonly string ReadyAudio = "UnitReady";
		[Desc("Notification played when you can't train another unit",
			"when the build limit exceeded or the exit is jammed.",
			"The filename of the audio is defined per faction in notifications.yaml.")]
		public readonly string BlockedAudio = "NoBuild";
		[Desc("Notification played when user clicks on the build palette icon.",
			"The filename of the audio is defined per faction in notifications.yaml.")]
		public readonly string QueuedAudio = "Training";
		[Desc("Notification played when player right-clicks on the build palette icon.",
			"The filename of the audio is defined per faction in notifications.yaml.")]
		public readonly string OnHoldAudio = "OnHold";
		[Desc("Notification played when player right-clicks on a build palette icon that is already on hold.",
			"The filename of the audio is defined per faction in notifications.yaml.")]
		public readonly string CancelledAudio = "Cancelled";
		public virtual object Create(ActorInitializer init) { return new ProductionQueue(init, init.Self.Owner.PlayerActor, this); }
	}
	public class ProductionQueue : IResolveOrder, ITick, ITechTreeElement, INotifyOwnerChanged, INotifyKilled, INotifySold, ISync, INotifyTransform
	{
		public readonly ProductionQueueInfo Info;
		readonly Actor self;
		// A list of things we could possibly build
		readonly Dictionary<ActorInfo, ProductionState> produceable = new Dictionary<ActorInfo, ProductionState>();
		readonly List<ProductionItem> queue = new List<ProductionItem>();
		readonly IEnumerable<ActorInfo> allProduceables;
		readonly IEnumerable<ActorInfo> buildableProduceables;
		// Will change if the owner changes
		PowerManager playerPower;
		PlayerResources playerResources;
		protected DeveloperMode developerMode;
		public Actor Actor { get { return self; } }
		[Sync] public int QueueLength { get { return queue.Count; } }
		[Sync] public int CurrentRemainingCost { get { return QueueLength == 0 ? 0 : queue[0].RemainingCost; } }
		[Sync] public int CurrentRemainingTime { get { return QueueLength == 0 ? 0 : queue[0].RemainingTime; } }
		[Sync] public int CurrentSlowdown { get { return QueueLength == 0 ? 0 : queue[0].Slowdown; } }
		[Sync] public bool CurrentPaused { get { return QueueLength != 0 && queue[0].Paused; } }
		[Sync] public bool CurrentDone { get { return QueueLength != 0 && queue[0].Done; } }
		[Sync] public bool Enabled { get; private set; }
		public string Faction { get; private set; }
		public ProductionQueue(ActorInitializer init, Actor playerActor, ProductionQueueInfo info)
		{
			self = init.Self;
			Info = info;
			playerResources = playerActor.Trait<PlayerResources>();
			playerPower = playerActor.Trait<PowerManager>();
			developerMode = playerActor.Trait<DeveloperMode>();
			Faction = init.Contains<FactionInit>() ? init.Get<FactionInit, string>() : self.Owner.Faction.InternalName;
			Enabled = !info.Factions.Any() || info.Factions.Contains(Faction);
			CacheProduceables(playerActor);
			allProduceables = produceable.Where(a => a.Value.Buildable || a.Value.Visible).Select(a => a.Key);
			buildableProduceables = produceable.Where(a => a.Value.Buildable).Select(a => a.Key);
		}
		void ClearQueue()
		{
			if (queue.Count == 0)
				return;
			// Refund the current item
			playerResources.GiveCash(queue[0].TotalCost - queue[0].RemainingCost);
			queue.Clear();
		}
		public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
		{
			ClearQueue();
			playerPower = newOwner.PlayerActor.Trait<PowerManager>();
			playerResources = newOwner.PlayerActor.Trait<PlayerResources>();
			developerMode = newOwner.PlayerActor.Trait<DeveloperMode>();
			if (!Info.Sticky)
			{
				Faction = self.Owner.Faction.InternalName;
				Enabled = !Info.Factions.Any() || Info.Factions.Contains(Faction);
			}
			// Regenerate the produceables and tech tree state
			oldOwner.PlayerActor.Trait<TechTree>().Remove(this);
			CacheProduceables(newOwner.PlayerActor);
			newOwner.PlayerActor.Trait<TechTree>().Update();
		}
		public void Killed(Actor killed, AttackInfo e) { if (killed == self) { ClearQueue(); Enabled = false; } }
		public void Selling(Actor self) { ClearQueue(); Enabled = false; }
		public void Sold(Actor self) { }
		public void BeforeTransform(Actor self) { ClearQueue(); Enabled = false; }
		public void OnTransform(Actor self) { }
		public void AfterTransform(Actor self) { }
		void CacheProduceables(Actor playerActor)
		{
			produceable.Clear();
			if (!Enabled)
				return;
			var ttc = playerActor.Trait<TechTree>();
			foreach (var a in AllBuildables(Info.Type))
			{
				var bi = a.TraitInfo<BuildableInfo>();
				produceable.Add(a, new ProductionState());
				ttc.Add(a.Name, bi.Prerequisites, bi.BuildLimit, this);
			}
		}
		IEnumerable<ActorInfo> AllBuildables(string category)
		{
			return self.World.Map.Rules.Actors.Values
				.Where(x =>
					x.Name[0] != '^' &&
					x.HasTraitInfo<BuildableInfo>() &&
					x.TraitInfo<BuildableInfo>().Queue.Contains(category));
		}
		public void PrerequisitesAvailable(string key)
		{
			produceable[self.World.Map.Rules.Actors[key]].Buildable = true;
		}
		public void PrerequisitesUnavailable(string key)
		{
			produceable[self.World.Map.Rules.Actors[key]].Buildable = false;
		}
		public void PrerequisitesItemHidden(string key)
		{
			produceable[self.World.Map.Rules.Actors[key]].Visible = false;
		}
		public void PrerequisitesItemVisible(string key)
		{
			produceable[self.World.Map.Rules.Actors[key]].Visible = true;
		}
		public ProductionItem CurrentItem()
		{
			return queue.ElementAtOrDefault(0);
		}
		public IEnumerable<ProductionItem> AllQueued()
		{
			return queue;
		}
		public virtual IEnumerable<ActorInfo> AllItems()
		{
			if (self.World.AllowDevCommands && developerMode.AllTech)
				return produceable.Keys;
			return allProduceables;
		}
		public virtual IEnumerable<ActorInfo> BuildableItems()
		{
			if (!Enabled)
				return Enumerable.Empty<ActorInfo>();
			if (self.World.AllowDevCommands && developerMode.AllTech)
				return produceable.Keys;
			return buildableProduceables;
		}
		public bool CanBuild(ActorInfo actor)
		{
			ProductionState ps;
			if (!produceable.TryGetValue(actor, out ps))
				return false;
			return ps.Buildable || (self.World.AllowDevCommands && developerMode.AllTech);
		}
		public virtual void Tick(Actor self)
		{
			while (queue.Count > 0 && BuildableItems().All(b => b.Name != queue[0].Item))
			{
				playerResources.GiveCash(queue[0].TotalCost - queue[0].RemainingCost); // refund what's been paid so far.
				FinishProduction();
			}
			if (queue.Count > 0)
				queue[0].Tick(playerResources);
		}
		public void ResolveOrder(Actor self, Order order)
		{
			if (!Enabled)
				return;
			var rules = self.World.Map.Rules;
			switch (order.OrderString)
			{
				case "StartProduction":
					{
						var unit = rules.Actors[order.TargetString];
						var bi = unit.TraitInfo<BuildableInfo>();
						if (!bi.Queue.Contains(Info.Type))
							return; /* Not built by this queue */
						var cost = unit.HasTraitInfo<ValuedInfo>() ? unit.TraitInfo<ValuedInfo>().Cost : 0;
						var time = GetBuildTime(order.TargetString);
						if (BuildableItems().All(b => b.Name != order.TargetString))
							return;	/* you can't build that!! */
						// Check if the player is trying to build more units that they are allowed
						var fromLimit = int.MaxValue;
						if (!developerMode.AllTech && bi.BuildLimit > 0)
						{
							var inQueue = queue.Count(pi => pi.Item == order.TargetString);
							var owned = self.Owner.World.ActorsWithTrait<Buildable>().Count(a => a.Actor.Info.Name == order.TargetString && a.Actor.Owner == self.Owner);
							fromLimit = bi.BuildLimit - (inQueue + owned);
							if (fromLimit <= 0)
								return;
						}
						var amountToBuild = Math.Min(fromLimit, order.ExtraData);
						for (var n = 0; n < amountToBuild; n++)
						{
							var hasPlayedSound = false;
							BeginProduction(new ProductionItem(this, order.TargetString, cost, playerPower, () => self.World.AddFrameEndTask(_ =>
							{
								var isBuilding = unit.HasTraitInfo<BuildingInfo>();
								if (isBuilding && !hasPlayedSound)
									hasPlayedSound = Game.Sound.PlayNotification(rules, self.Owner, "Speech", Info.ReadyAudio, self.Owner.Faction.InternalName);
								else if (!isBuilding)
								{
									if (BuildUnit(order.TargetString))
										Game.Sound.PlayNotification(rules, self.Owner, "Speech", Info.ReadyAudio, self.Owner.Faction.InternalName);
									else if (!hasPlayedSound && time > 0)
										hasPlayedSound = Game.Sound.PlayNotification(rules, self.Owner, "Speech", Info.BlockedAudio, self.Owner.Faction.InternalName);
								}
							})));
						}
						break;
					}
				case "PauseProduction":
					{
						if (queue.Count > 0 && queue[0].Item == order.TargetString)
							queue[0].Pause(order.ExtraData != 0);
						break;
					}
				case "CancelProduction":
					{
						CancelProduction(order.TargetString, order.ExtraData);
						break;
					}
			}
		}
		public virtual int GetBuildTime(string unitString)
		{
			var unit = self.World.Map.Rules.Actors[unitString];
			if (unit == null || !unit.HasTraitInfo<BuildableInfo>())
				return 0;
			if (self.World.AllowDevCommands && self.Owner.PlayerActor.Trait<DeveloperMode>().FastBuild)
				return 0;
			var time = unit.GetBuildTime() * Info.BuildSpeed;
			return (int)time;
		}
		protected void CancelProduction(string itemName, uint numberToCancel)
		{


Answer: 			for (var i = 0; i < numberToCancel; i++)