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

[Serializable]
public struct InstantiationPrefabSettings
{
    public GameObject prefab;
    public int minCount;
    public int maxCount;
}

public class Pool
{
    private InstantiationPrefabSettings settings;

    private Queue<GameObject> remainingGameObjects = new Queue<GameObject>();
    private HashSet<GameObject> instancedGameObjects = new HashSet<GameObject>();

    public Pool(InstantiationPrefabSettings settings)
    {
        this.settings = settings;
    }

    public void Init()
    {
        remainingGameObjects = new Queue<GameObject>();
        instancedGameObjects = new HashSet<GameObject>();

        for(int n=0;n<settings.minCount;n++)
        {
            GameObject g = GameObject.Instantiate(settings.prefab, Vector3.zero, Quaternion.identity);
            g.SetActive(false);
            g.transform.parent = PoolManager.Instance.transform;
            remainingGameObjects.Enqueue(g);
        }
    }

    public GameObject GetInstance()
    {
        int remainingInstanceCount = remainingGameObjects.Count;
        int remainingInstantiableCount = settings.maxCount - (remainingGameObjects.Count + instancedGameObjects.Count);

        GameObject instance = null;

        if(remainingInstanceCount > 0)
        {
            instance = remainingGameObjects.Dequeue();
            instance.SetActive(true);
            instancedGameObjects.Add(instance);
        }
        else if(remainingInstantiableCount > 0)
        {
            instance = GameObject.Instantiate(settings.prefab, Vector3.zero, Quaternion.identity);
            instancedGameObjects.Add(instance);
        }
        
        if(instance != null)
        {
            instance.transform.parent = null;
            PoolableMonoBehaviour poolableMonoBehaviour = instance.GetComponent<PoolableMonoBehaviour>();
            if(poolableMonoBehaviour != null)
            {
                poolableMonoBehaviour.OnRetrieve();
            }
        }

        return instance;
    }

    public void ReturnInstance(GameObject gameObj)
    {
        int remainingInstanceCount = remainingGameObjects.Count;
        if(remainingInstanceCount < settings.minCount)
        {
            PoolableMonoBehaviour poolableMonoBehaviour = gameObj.GetComponent<PoolableMonoBehaviour>();
            if(poolableMonoBehaviour != null)
            {
                poolableMonoBehaviour.OnReturn();
            }
            gameObj.SetActive(false);
            gameObj.transform.parent = PoolManager.Instance.transform;
            remainingGameObjects.Enqueue(gameObj);
        }
        else
        {
            GameObject.Destroy(gameObj);
        }
    }
}

public class PoolManager : MonoBehaviour
{
    public InstantiationPrefabSettings[] InstantiableGameObjects = new InstantiationPrefabSettings[0];

    private Dictionary<GameObject, Pool> pools = new Dictionary<GameObject, Pool>(); 
    private Dictionary<GameObject, GameObject> instanceToPrefabDict = new Dictionary<GameObject, GameObject>();

    private static PoolManager instance;

    public static PoolManager Instance
    {
        get
        {
            return instance;
        }
    }

    protected void Awake()
    {
        instance = this;
    }

    protected void Start()
    {
        foreach(InstantiationPrefabSettings poolInstantiationSettings in InstantiableGameObjects)
        {
            Pool pool = new Pool(poolInstantiationSettings);
            pools[poolInstantiationSettings.prefab] = pool;
            pool.Init();
        }
    }

    public GameObject GetInstance(GameObject prefab)
    {
        GameObject instance = pools[prefab].GetInstance();
        if(instance != null)
        {
            instanceToPrefabDict[instance] = prefab;
        }
        return instance;
    }

    public void ReturnInstance(GameObject gameObj)
    {
        GameObject prefab = instanceToPrefabDict[gameObj];
        pools[prefab].ReturnInstance(gameObj);
    }
}
