using FishNet.Managing.Object;
using FishNet.Object;
using FishNet.Utility.Extension;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Utility.Performance
{
    public class DefaultObjectPool : ObjectPool
    {
        #region Public.
        /// 
        /// Cache for pooled NetworkObjects.
        /// 
        public IReadOnlyCollection>> Cache => _cache;
        private List>> _cache = new List>>();
        #endregion
        #region Serialized.
        /// 
        /// True if to use object pooling.
        /// 
        [Tooltip("True if to use object pooling.")]
        [SerializeField]
        private bool _enabled = true;
        #endregion
        #region Private.
        /// 
        /// Current count of the cache collection.
        /// 
        private int _cacheCount = 0;
        #endregion
        /// 
        /// Returns an object that has been stored with a collectionId of 0. A new object will be created if no stored objects are available.
        /// 
        /// PrefabId of the object to return.
        /// True if being called on the server side.
        /// 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public override NetworkObject RetrieveObject(int prefabId, bool asServer)
        {
            return RetrieveObject(prefabId, 0, asServer);
        }
        /// 
        /// Returns an object that has been stored. A new object will be created if no stored objects are available.
        /// 
        /// PrefabId of the object to return.
        /// CollectionId of the prefab.
        /// True if being called on the server side.
        /// 
        public override NetworkObject RetrieveObject(int prefabId, ushort collectionId, bool asServer)
        {
            PrefabObjects po = base.NetworkManager.GetPrefabObjects(collectionId, false);
            //Quick exit/normal retrieval when not using pooling.
            if (!_enabled)
            {
                NetworkObject prefab = po.GetObject(asServer, prefabId);
                return Instantiate(prefab);
            }
            Stack cache = GetOrCreateCache(collectionId, prefabId);
            NetworkObject nob;
            //Iterate until nob is populated just in case cache entries have been destroyed.
            do
            {
                if (cache.Count == 0)
                {
                    NetworkObject prefab = po.GetObject(asServer, prefabId);
                    /* A null nob should never be returned from spawnables. This means something
                     * else broke, likely unrelated to the object pool. */
                    nob = Instantiate(prefab);
                    //Can break instantly since we know nob is not null.
                    break;
                }
                else
                {
                    nob = cache.Pop();
                }
            } while (nob == null);
            nob.gameObject.SetActive(true);
            return nob;
        }
        /// 
        /// Stores an object into the pool.
        /// 
        /// Object to store.
        /// True if being called on the server side.
        /// 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public override void StoreObject(NetworkObject instantiated, bool asServer)
        {
            //Pooling is not enabled.
            if (!_enabled)
            {
                Destroy(instantiated.gameObject);
                return;
            }
            instantiated.gameObject.SetActive(false);
            instantiated.ResetForObjectPool();
            Stack cache = GetOrCreateCache(instantiated.SpawnableCollectionId, instantiated.PrefabId);
            cache.Push(instantiated);
        }
        /// 
        /// Instantiates a number of objects and adds them to the pool.
        /// 
        /// Prefab to cache.
        /// Quantity to spawn.
        /// True if storing prefabs for the server collection. This is only applicable when using DualPrefabObjects.
        public void CacheObjects(NetworkObject prefab, int count, bool asServer)
        {
            if (!_enabled)
                return;
            if (count <= 0)
                return;
            if (prefab == null)
                return;
            if (prefab.PrefabId == NetworkObject.UNSET_PREFABID_VALUE)
            {
                InstanceFinder.NetworkManager.LogError($"Pefab {prefab.name} has an invalid prefabId and cannot be cached.");
                return;
            }
            Stack cache = GetOrCreateCache(prefab.SpawnableCollectionId, prefab.PrefabId);
            for (int i = 0; i < count; i++)
            {
                NetworkObject nob = Instantiate(prefab);
                nob.gameObject.SetActive(false);
                cache.Push(nob);
            }
        }
        /// 
        /// Clears pools for all collectionIds
        /// 
        public void ClearPool()
        {
            int count = _cache.Count;
            for (int i = 0; i < count; i++)
                ClearPool(i);
        }
        /// 
        /// Clears a pool for collectionId.
        /// 
        /// CollectionId to clear for.
        public void ClearPool(int collectionId)
        {
            if (collectionId >= _cacheCount)
                return;
            Dictionary> dict = _cache[collectionId];
            //Convert to a list from the stack so we do not modify the stack directly.
            ListCache nobCache = ListCaches.GetNetworkObjectCache();
            foreach (Stack item in dict.Values)
            {
                while (item.Count > 0)
                    nobCache.AddValue(item.Pop());
            }
        }
        /// 
        /// Gets a cache for an id or creates one if does not exist.
        /// 
        /// 
        /// 
        private Stack GetOrCreateCache(int collectionId, int prefabId)
        {
            if (collectionId >= _cacheCount)
            {
                //Add more to the cache.
                while (_cache.Count <= collectionId)
                {
                    Dictionary> dict = new Dictionary>();
                    _cache.Add(dict);
                }
                _cacheCount = collectionId;
            }
            Dictionary> dictionary = _cache[collectionId];
            Stack cache;
            //No cache for prefabId yet, make one.
            if (!dictionary.TryGetValueIL2CPP(prefabId, out cache))
            {
                cache = new Stack();
                dictionary[prefabId] = cache;
            }
            return cache;
        }
    }
}