#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.Drawing;
using System.IO;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;

namespace OpenRA.Mods.Common.Traits
{
	public class EditorActorPreview
	{
		public readonly string Tooltip;
		public readonly string ID;
		public readonly ActorInfo Info;
		public readonly PlayerReference Owner;
		public readonly WPos CenterPosition;
		public readonly IReadOnlyDictionary<CPos, SubCell> Footprint;
		public readonly Rectangle Bounds;

		public SubCell SubCell { get; private set; }

		readonly ActorReference actor;
		readonly WorldRenderer worldRenderer;
		IActorPreview[] previews;

		public EditorActorPreview(WorldRenderer worldRenderer, string id, ActorReference actor, PlayerReference owner)
		{
			ID = id;
			this.actor = actor;
			this.Owner = owner;
			this.worldRenderer = worldRenderer;

			if (!actor.InitDict.Contains<FactionInit>())
				actor.InitDict.Add(new FactionInit(owner.Faction));

			if (!actor.InitDict.Contains<OwnerInit>())
				actor.InitDict.Add(new OwnerInit(owner.Name));

			var world = worldRenderer.World;
			if (!world.Map.Rules.Actors.TryGetValue(actor.Type.ToLowerInvariant(), out Info))
				throw new InvalidDataException("Actor {0} of unknown type {1}".F(id, actor.Type.ToLowerInvariant()));

			CenterPosition = PreviewPosition(world, actor.InitDict);

			var location = actor.InitDict.Get<LocationInit>().Value(worldRenderer.World);
			var ios = Info.Traits.GetOrDefault<IOccupySpaceInfo>();

			var subCellInit = actor.InitDict.GetOrDefault<SubCellInit>();
			var subCell = subCellInit != null ? subCellInit.Value(worldRenderer.World) : SubCell.Any;

			if (ios != null)
				Footprint = ios.OccupiedCells(Info, location, subCell);
			else
			{
				var footprint = new Dictionary<CPos, SubCell>() { { location, SubCell.FullCell } };
				Footprint = new ReadOnlyDictionary<CPos, SubCell>(footprint);
			}

			var tooltip = Info.Traits.GetOrDefault<TooltipInfo>();
			Tooltip = tooltip == null ? Info.Name + " - " + ID : tooltip.Name + " (" + Info.Name + ") - " + ID;

			GeneratePreviews();

			// Bounds are fixed from the initial render.
			// If this is a problem, then we may need to fetch the area from somewhere else
			var r = previews
				.SelectMany(p => p.Render(worldRenderer, CenterPosition))
				.Select(rr => rr.PrepareRender(worldRenderer));

			if (r.Any())
			{
				Bounds = r.First().ScreenBounds(worldRenderer);
				foreach (var rr in r.Skip(1))
					Bounds = Rectangle.Union(Bounds, rr.ScreenBounds(worldRenderer));
			}
		}

		public void Tick()
		{
			foreach (var p in previews)
				p.Tick();
		}

		public IEnumerable<IRenderable> Render()
		{
			return previews.SelectMany(p => p.Render(worldRenderer, CenterPosition));
		}

		public void ReplaceInit<T>(T init)
		{
			var original = actor.InitDict.GetOrDefault<T>();
			if (original != null)
				actor.InitDict.Remove(original);

			actor.InitDict.Add(init);
			GeneratePreviews();
		}

		public T Init<T>()
		{
			return actor.InitDict.GetOrDefault<T>();
		}

		public MiniYaml Save()
		{
			Func<object, bool> saveInit = init =>
			{
				var factionInit = init as FactionInit;
				if (factionInit != null && factionInit.Faction == Owner.Faction)
					return false;

				// TODO: Other default values will need to be filtered
				// here after we have built a properties panel
				return true;
			};

			return actor.Save(saveInit);
		}

		WPos PreviewPosition(World world, TypeDictionary init)
		{
			if (init.Contains<CenterPositionInit>())
				return init.Get<CenterPositionInit>().Value(world);

			if (init.Contains<LocationInit>())
			{
				var cell = init.Get<LocationInit>().Value(world);
				var offset = WVec.Zero;

				var subCellInit = actor.InitDict.GetOrDefault<SubCellInit>();
				var subCell = subCellInit != null ? subCellInit.Value(worldRenderer.World) : SubCell.Any;

				var buildingInfo = Info.Traits.GetOrDefault<BuildingInfo>();
				if (buildingInfo != null)
					offset = FootprintUtils.CenterOffset(world, buildingInfo);

				return world.Map.CenterOfSubCell(cell, subCell) + offset;
			}
			else
				throw new InvalidDataException("Actor {0} must define Location or CenterPosition".F(ID));
		}

		void GeneratePreviews()
		{
			var init = new ActorPreviewInitializer(Info, worldRenderer, actor.InitDict);
			previews = Info.Traits.WithInterface<IRenderActorPreviewInfo>()
				.SelectMany(rpi => rpi.RenderPreview(init))
				.ToArray();
		}
	}
}
