﻿using UnityEngine;
using FastCollections;
using Lockstep.Data;
using System.Collections.Generic;

namespace Lockstep
{
	public static class EffectManager
	{
		const int MaxEffects = ProjectileManager.MaxProjectiles * 2;
		private static Dictionary<string, FastStack<LSEffect>> EffectPool;
		private static Dictionary<string, IEffectData> CodeDataMap;

		public static void Setup()
		{
			IEffectDataProvider database;
			if (LSDatabaseManager.TryGetDatabase<IEffectDataProvider>(out database))
			{
				IEffectData[] effectData = database.EffectData;
				EffectPool = new Dictionary<string, FastStack<LSEffect>>(effectData.Length);
				CodeDataMap = new Dictionary<string, IEffectData>(effectData.Length);
				for (int i = 0; i < effectData.Length; i++)
				{
					IEffectData dataItem = effectData[i];
					string code = (string)dataItem.Name;
					EffectPool.Add(code, new FastStack<LSEffect>());
					CodeDataMap.Add(code, dataItem);
				}
			}
		}

		public static void Visualize()
		{
			for (int i = 0; i < PeakCount; i++)
			{
				if (EffectActive[i])
				{
					Effects[i].Visualize();
				}
			}
		}

		public static void Deactivate()
		{
			for (int i = 0; i < PeakCount; i++)
			{
				if (EffectActive[i])
				{
					EndEffect(Effects[i]);
				}
			}
		}

		public static bool IsValid(string effectCode)
		{
			return !string.IsNullOrEmpty(effectCode) && effectCode != "None" && CodeDataMap.ContainsKey(effectCode);
		}
		public static LSEffect CreateCollisionEffect(string effectCode, LSProjectile projectile, LSBody hitBody)
		{
			Vector3 collisionDirection = -(projectile.Forward.ToVector3());
			Vector3 collisionPosition = collisionDirection * hitBody.Radius.ToFloat() + hitBody.PositionalTransform.position;
			return CreateEffect(effectCode, collisionPosition, projectile.transform.rotation);
		}
		public static LSEffect CreateEffect(string effectCode, Vector3 position)
		{
			if (!IsValid(effectCode))
				return null;
			LSEffect effect = CreateEffect(effectCode);
			effect.CachedTransform.position = position;
			Fire(effect);
			return effect;
		}

		public static LSEffect CreateEffect(string effectCode, Vector3 position, Quaternion rotation)
		{
			if (!IsValid(effectCode))
				return null;
			LSEffect effect = CreateEffect(effectCode);
			effect.CachedTransform.position = position;
			effect.CachedTransform.rotation = rotation;
			Fire(effect);
			return effect;

		}

		public static LSEffect CreateEffect(string effectCode, Transform spawnParent)
		{
			if (!IsValid(effectCode))
				return null;
			LSEffect effect = CreateEffect(effectCode);
			effect.CachedTransform.parent = spawnParent;
			Fire(effect);
			return effect;

		}

		public static LSEffect CreateEffect(string effectCode)
		{
			if (!IsValid(effectCode))
				return null;

			LSEffect effect = GenEffect(effectCode, -1);
			EffectActive[effect.ID] = true;
			Effects[effect.ID] = effect;
			return effect;
		}

		public static void Fire(LSEffect effect)
		{
			effect.Initialize();
		}
		#region Allocation

		private static bool[] EffectActive = new bool[MaxEffects];
		private static LSEffect[] Effects = new LSEffect[MaxEffects];

		private static FastStack<int> OpenSlots = new FastStack<int>();
		private static int PeakCount;

		private static int GenerateID()
		{
			if (OpenSlots.Count > 0)
				return OpenSlots.Pop();
			else
				return PeakCount++;
		}

		private static LSEffect GenEffect(string effectCode, int id = -1)
		{
			FastStack<LSEffect> pool;

			if (!EffectPool.TryGetValue(effectCode, out pool))
			{
				return null;
			}
			LSEffect effect = null;
			if (pool.Count > 0)
			{
				effect = pool.Pop();
			}
			else
			{
				IEffectData dataItem = CodeDataMap[effectCode];
				effect = GameObject.Instantiate<GameObject>(dataItem.GetEffect().gameObject).GetComponent<LSEffect>();
				effect.Setup(effectCode);
			}

			if (id == -1)
				id = GenerateID();
			if (effect.Create(id))
			{
				return effect;
			}
			else
			{
				return GenEffect(effectCode, id);
			}
		}

		public static void EndEffect(LSEffect effect)
		{
			if (EffectActive[effect.ID] == false)
				return;
			effect.Deactivate();
			EffectPool[effect.MyEffectCode].Add(effect);
			EffectActive[effect.ID] = false;
			Effects[effect.ID] = null;
			OpenSlots.Add(effect.ID);

		}

		/// <summary>
		/// Eliminates an effect and allows the Garbage Collector to clean it up.
		/// </summary>
		/// <param name="effect">Effect.</param>
		public static void DestroyEffect(LSEffect effect)
		{
			if (EffectActive[effect.ID] == false)
				return;
			EffectActive[effect.ID] = false;
			OpenSlots.Add(effect.ID);
		}

		#endregion
	}
}