using UnityEngine;
using System;
using System.Collections.Generic;

namespace mgaia.Utils
{
    /// <summary>
    /// Classe de pool, gerenciada unicamente pelo PoolManager. Guarda referências a uma quantidade determinada de PoolElements, que mantém desativados e guardados.
    /// </summary>
    public class Pool
    {
		private GameObject mPoolObject;
        private string mPoolName;
        private string mResourceName;
        private List<PoolElement> mPool_In = new List<PoolElement>(); // Guarda os PoolElements que estão na Pool, desativados.
        private List<PoolElement> mPool_Out = new List<PoolElement>(); // Guarda os PoolElements que não estão na Pool. Provavelmente estão sendo usados por alguém, ativos.
        private int mInitialSize;

        /// <summary>
        /// Instancia uma nova pool.
        /// </summary>
        /// <param name="resourcePath">Path completo da resource, relativo à sua pasta raiz Resources.</param>
        /// <param name="poolName">O nome da pool.</param>
        /// <param name="initialSize">Quantidade de PoolElements que será criada durante a criação da pool. Idealmente a pool nunca deve aumentar de tamanho durante o jogo.</param>
        public Pool(string resourcePath, string poolName, int initialSize)
        {
            mPoolName = poolName;
            mPoolObject = new GameObject(mPoolName);
            GameObject.DontDestroyOnLoad(mPoolObject);
            mResourceName = resourcePath;
            mInitialSize = initialSize;
            for (int i = 0; i < mInitialSize; i++)
            {
                CreateElementAndIncPool();
            }
        }

        /// <summary>
        /// Cria um novo PoolElement, do tipo conhecido mRosourcePath, e o adiciona à pool.
        /// </summary>
        private void CreateElementAndIncPool()
        {
            UnityEngine.Object loadedResource = Resources.Load(mResourceName);
            Assert.AssertTrue(loadedResource != null, "Resource could not be loaded: " + mResourceName);
            UnityEngine.Object instantiatedResource = MonoBehaviour.Instantiate(loadedResource);
            Assert.AssertTrue(instantiatedResource != null, "Loaded resource could not be instantiated: " + loadedResource.name);
            GameObject gameObj = instantiatedResource as GameObject;
            Assert.AssertTrue(gameObj != null, "Instantiated resource is n ot a GameObject: " + instantiatedResource.name);
            PoolElement poolElement = gameObj.GetComponent<PoolElement>();
            Assert.AssertTrue(poolElement != null, "The created gameobject does not have an IPoolElement behaviour");
            Push(poolElement, true);
			AddInQueue(poolElement);
        }

        /// <summary>
        /// Insere um PoolElement que estava fora da pool à pool de volta, desativando-o.
        /// </summary>
        /// <param name="poolElement">O elemento que será adicionado à pool.</param>
        /// <param name="justCreated">Indica se o PoolElement acabou de ser criado ou se está retornando à Pool.</param>
        private void Push(PoolElement poolElement, bool justCreated)
        {
            Assert.AssertTrue(!mPool_In.Contains(poolElement), "Pushed element was already at the pool_in: " + poolElement.name);
            Assert.AssertTrue(justCreated ? !mPool_Out.Contains(poolElement) : true, "Pushed element was just created and should not be at the pool_out: " + poolElement.name);
            Assert.AssertTrue(!justCreated ? mPool_Out.Contains(poolElement) : true, "Pushed element not found at the pool_out: " + poolElement.name);
            poolElement.PoolElementActionFinished -= HandlePoolElementActionFinished;
            poolElement.transform.parent = mPoolObject.transform;
            poolElement.transform.localPosition = Vector3.zero;
            poolElement.transform.localRotation = Quaternion.identity;
            poolElement.transform.localScale = Vector3.one;
        }

		/// <summary>
		/// Adiciona o elemento na fila de elementos que estão disponíveis na pool
		/// </summary>
		/// <param name="poolElement">Elemento que vai ficar disponível</param>
		private void AddInQueue(PoolElement poolElement)
		{
			mPool_In.Add(poolElement);
			mPool_Out.Remove(poolElement);

            poolElement.gameObject.SetActive(false);
		}

        /// <summary>
        /// Ativa e retorna um PoolElement da Pool. Caso nenhum esteja disponível, um novo é criado e retornado.
        /// </summary>
        public PoolElement Pop()
        {
            if (mPool_In.Count == 0)
            {
                Debug.LogWarning("Pool (" + mPoolName + ") had no ready pre-instantiated element and had to create one. Consider increasing the pool's initial size.");
                CreateElementAndIncPool();
                mInitialSize += 1;
            }
            PoolElement poolElement = mPool_In[0];
            mPool_In.RemoveAt(0);
            mPool_Out.Add(poolElement);
            poolElement.PoolElementActionFinished += HandlePoolElementActionFinished;
            poolElement.gameObject.SetActive(true);
            return poolElement;
        }

        /// <summary>
        /// Lida com o evento disparado quando um PoolElement que está fora da Pool terminou sua ação e precisa voltar à Pool.
        /// </summary>
        private void HandlePoolElementActionFinished(PoolElement poolElement)
        {
			Push(poolElement, false);
			AddInQueue(poolElement);
        }

        /// <summary>
        /// Recarrega a pool para o seu limite máximo
        /// </summary>
        public void Recharge()
        {
            for (; mPool_In.Count < mInitialSize; )
            {
                CreateElementAndIncPool();
            }
        }
    }
}