using System;
using System.Collections.Concurrent;
using GameKit.Dependencies.Utilities.Types;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

// ReSharper disable ThreadStaticFieldHasInitializesr
namespace GameKit.Dependencies.Utilities
{
    /// <summary>
    /// Implement to use type with Caches.
    /// </summary>
    public interface IResettable
    {
        /// <summary>
        /// Resets values when being placed in a cache.
        /// </summary>
        void ResetState();

        /// <summary>
        /// Initializes values after being retrieved from a cache.
        /// </summary>
        void InitializeState();
    }

    #region Resettable caches.
    /// <summary>
    /// Caches collections of multiple generics.
    /// </summary>
    public static class ResettableCollectionCaches<T1, T2> where T1 : IResettable, new() where T2 : IResettable, new()
    {
        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        /// <returns></returns>
        public static Dictionary<T1, T2> RetrieveDictionary() => CollectionCaches<T1, T2>.RetrieveDictionary();

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref Dictionary<T1, T2> value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        public static void Store(Dictionary<T1, T2> value)
        {
            if (value == null)
                return;

            foreach (KeyValuePair<T1, T2> kvp in value)
            {
                ResettableObjectCaches<T1>.Store(kvp.Key);
                ResettableObjectCaches<T2>.Store(kvp.Value);
            }

            value.Clear();

            CollectionCaches<T1, T2>.Store(value);
        }
    }

    /// <summary>
    /// Caches collections of multiple generics.
    /// </summary>
    public static class ResettableT1CollectionCaches<T1, T2> where T1 : IResettable, new()
    {
        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        /// <returns></returns>
        public static Dictionary<T1, T2> RetrieveDictionary() => CollectionCaches<T1, T2>.RetrieveDictionary();

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref Dictionary<T1, T2> value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        public static void Store(Dictionary<T1, T2> value)
        {
            if (value == null)
                return;

            foreach (T1 item in value.Keys)
                ResettableObjectCaches<T1>.Store(item);

            value.Clear();
            CollectionCaches<T1, T2>.Store(value);
        }
    }

    /// <summary>
    /// Caches collections of multiple generics.
    /// </summary>
    public static class ResettableT2CollectionCaches<T1, T2> where T2 : IResettable, new()
    {
        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        /// <returns></returns>
        public static Dictionary<T1, T2> RetrieveDictionary() => CollectionCaches<T1, T2>.RetrieveDictionary();

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref Dictionary<T1, T2> value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        public static void Store(Dictionary<T1, T2> value)
        {
            if (value == null)
                return;

            foreach (T2 item in value.Values)
                ResettableObjectCaches<T2>.Store(item);

            value.Clear();
            CollectionCaches<T1, T2>.Store(value);
        }
    }

    /// <summary>
    /// Caches collections of a single generic.
    /// </summary>
    public static class ResettableCollectionCaches<T> where T : IResettable, new()
    {
        /// <summary>
        /// Cache for ResettableRingBuffer.
        /// </summary>
        private static readonly ConcurrentStack<ResettableRingBuffer<T>> _resettableRingBufferCache = new();
        /// <summary>
        /// Maximum number of entries allowed for the cache.
        /// </summary>
        private const int MAXIMUM_CACHE_COUNT = 50;
        
        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        public static ResettableRingBuffer<T> RetrieveRingBuffer()
        {
            ResettableRingBuffer<T> result;
            if (!_resettableRingBufferCache.TryPop(out result))
                result = new();

            return result;
        }

        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        /// <returns></returns>
        public static T[] RetrieveArray() => CollectionCaches<T>.RetrieveArray();

        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        /// <returns></returns>
        public static List<T> RetrieveList() => CollectionCaches<T>.RetrieveList();

        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        /// <returns></returns>
        public static SortedSet<T> RetrieveSortedSet() => CollectionCaches<T>.RetrieveSortedSet();

        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        /// <returns></returns>
        public static HashSet<T> RetrieveHashSet() => CollectionCaches<T>.RetrieveHashSet();

        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        /// <returns></returns>
        public static Queue<T> RetrieveQueue() => CollectionCaches<T>.RetrieveQueue();

        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        /// <returns></returns>
        public static BasicQueue<T> RetrieveBasicQueue() => CollectionCaches<T>.RetrieveBasicQueue();

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        /// <param name = "count">Number of entries in the array from the beginning.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref ResettableRingBuffer<T> value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        /// <param name = "count">Number of entries in the array from the beginning.</param>
        public static void Store(ResettableRingBuffer<T> value)
        {
            if (value == null)
                return;

            value.ResetState();

            if (_resettableRingBufferCache.Count < MAXIMUM_CACHE_COUNT)
                _resettableRingBufferCache.Push(value);
        }

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        /// <param name = "count">Number of entries in the array from the beginning.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref T[] value, int count)
        {
            Store(value, count);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        /// <param name = "count">Number of entries in the array from the beginning.</param>
        public static void Store(T[] value, int count)
        {
            if (value == null)
                return;

            for (int i = 0; i < count; i++)
                ResettableObjectCaches<T>.Store(value[i]);

            CollectionCaches<T>.Store(value, count);
        }

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref List<T> value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        public static void Store(List<T> value)
        {
            if (value == null)
                return;

            for (int i = 0; i < value.Count; i++)
                ResettableObjectCaches<T>.Store(value[i]);

            value.Clear();
            CollectionCaches<T>.Store(value);
        }

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref SortedSet<T> value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        public static void Store(SortedSet<T> value)
        {
            if (value == null)
                return;

            foreach (T item in value)
                ResettableObjectCaches<T>.Store(item);

            value.Clear();
            CollectionCaches<T>.Store(value);
        }

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref HashSet<T> value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        public static void Store(HashSet<T> value)
        {
            if (value == null)
                return;

            foreach (T item in value)
                ResettableObjectCaches<T>.Store(item);

            value.Clear();
            CollectionCaches<T>.Store(value);
        }

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref Queue<T> value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        public static void Store(Queue<T> value)
        {
            if (value == null)
                return;

            foreach (T item in value)
                ResettableObjectCaches<T>.Store(item);

            value.Clear();
            CollectionCaches<T>.Store(value);
        }

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref BasicQueue<T> value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        public static void Store(BasicQueue<T> value)
        {
            if (value == null)
                return;

            while (value.TryDequeue(out T result))
                ResettableObjectCaches<T>.Store(result);

            value.Clear();
            CollectionCaches<T>.Store(value);
        }
    }

    /// <summary>
    /// Caches objects of a single generic.
    /// </summary>
    public static class ResettableObjectCaches<T> where T : IResettable, new()
    {
        /// <summary>
        /// Retrieves an instance of T.
        /// </summary>
        public static T Retrieve()
        {
            T result = ObjectCaches<T>.Retrieve();
            result.InitializeState();
            return result;
        }

        /// <summary>
        /// Stores an instance of T and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref T value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores an instance of T.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        public static void Store(T value)
        {
            if (value == null)
                return;

            value.ResetState();
            ObjectCaches<T>.Store(value);
        }
    }
    #endregion

    #region NonResettable caches.
    /// <summary>
    /// Caches collections of multiple generics.
    /// </summary>
    public static class CollectionCaches<T1, T2>
    {
        /// <summary>
        /// Cache for dictionaries.
        /// </summary>
        private static readonly ConcurrentStack<Dictionary<T1, T2>> _dictionaryCache = new();
        /// <summary>
        /// Maximum number of entries allowed for the cache.
        /// </summary>
        private const int MAXIMUM_CACHE_COUNT = 50;

        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        /// <returns></returns>
        public static Dictionary<T1, T2> RetrieveDictionary()
        {
            Dictionary<T1, T2> result;
            if (!_dictionaryCache.TryPop(out result))
                result = new();

            return result;
        }

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref Dictionary<T1, T2> value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        public static void Store(Dictionary<T1, T2> value)
        {
            if (value == null)
                return;

            value.Clear();
            if (_dictionaryCache.Count < MAXIMUM_CACHE_COUNT)
                _dictionaryCache.Push(value);
        }
    }

    /// <summary>
    /// Caches collections of a single generic.
    /// </summary>
    public static partial class CollectionCaches<T>
    {
        /// <summary>
        /// Cache for arrays.
        /// </summary>
        private static readonly ConcurrentStack<T[]> _arrayCache;
        /// <summary>
        /// Cache for lists.
        /// </summary>
        private static readonly ConcurrentStack<List<T>> _listCache;
        /// <summary>
        /// Cache for sortedset.
        /// </summary>
        private static readonly ConcurrentStack<SortedSet<T>> _sortedSetCache;
        /// <summary>
        /// Cache for queues.
        /// </summary>
        private static readonly ConcurrentStack<Queue<T>> _queueCache;
        /// <summary>
        /// Cache for queues.
        /// </summary>
        private static readonly ConcurrentStack<BasicQueue<T>> _basicQueueCache;
        /// <summary>
        /// Cache for hashset.
        /// </summary>
        private static readonly ConcurrentStack<HashSet<T>> _hashSetCache;
        /// <summary>
        /// Maximum number of entries allowed for the cache.
        /// </summary>
        private const int MAXIMUM_CACHE_COUNT = 50;

        static CollectionCaches()
        {
            _arrayCache = new();
            _listCache = new();
            _sortedSetCache = new();
            _queueCache = new();
            _basicQueueCache = new();
            _hashSetCache = new();
        }

        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        /// <returns></returns>
        public static T[] RetrieveArray()
        {
            T[] result;
            if (!_arrayCache.TryPop(out result))
                result = new T[0];

            return result;
        }

        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        /// <returns></returns>
        public static List<T> RetrieveList()
        {
            List<T> result;
            if (!_listCache.TryPop(out result))
                result = new();

            return result;
        }

        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        /// <returns></returns>
        public static SortedSet<T> RetrieveSortedSet()
        {
            SortedSet<T> result;
            if (!_sortedSetCache.TryPop(out result))
                result = new();

            return result;
        }

        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        /// <returns></returns>
        public static Queue<T> RetrieveQueue()
        {
            Queue<T> result;
            if (!_queueCache.TryPop(out result))
                result = new();

            return result;
        }

        /// <summary>
        /// Retrieves a collection.
        /// </summary>
        /// <returns></returns>
        public static BasicQueue<T> RetrieveBasicQueue()
        {
            BasicQueue<T> result;
            if (!_basicQueueCache.TryPop(out result))
                result = new();

            return result;
        }

        /// <summary>
        /// Retrieves a collection adding one entry.
        /// </summary>
        /// <returns></returns>
        public static Queue<T> RetrieveQueue(T entry)
        {
            Queue<T> result;
            if (!_queueCache.TryPop(out result))
                result = new();

            result.Enqueue(entry);
            return result;
        }

        /// <summary>
        /// Retrieves a collection adding one entry.
        /// </summary>
        /// <returns></returns>
        public static List<T> RetrieveList(T entry)
        {
            List<T> result;
            if (!_listCache.TryPop(out result))
                result = new();

            result.Add(entry);
            return result;
        }

        /// <summary>
        /// Retrieves a HashSet<T>.
        /// </summary>
        /// <returns></returns>
        public static HashSet<T> RetrieveHashSet()
        {
            HashSet<T> result;
            if (!_hashSetCache.TryPop(out result))
                result = new();

            return result;
        }

        /// <summary>
        /// Retrieves a collection adding one entry.
        /// </summary>
        /// <returns></returns>
        public static HashSet<T> RetrieveHashSet(T entry)
        {
            HashSet<T> result;
            if (!_hashSetCache.TryPop(out result))
                return new();

            result.Add(entry);
            return result;
        }

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        /// <param name = "count">Number of entries in the array set default, from the beginning.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref T[] value, int count)
        {
            Store(value, count);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        /// <param name = "count">Number of entries in the array from the beginning.</param>
        public static void Store(T[] value, int count)
        {
            if (value == null)
                return;

            for (int i = 0; i < count; i++)
                value[i] = default;

            if (_arrayCache.Count < MAXIMUM_CACHE_COUNT)
                _arrayCache.Push(value);
        }

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref List<T> value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        public static void Store(List<T> value)
        {
            if (value == null)
                return;

            value.Clear();

            if (_listCache.Count < MAXIMUM_CACHE_COUNT)
                _listCache.Push(value);
        }

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref SortedSet<T> value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        public static void Store(SortedSet<T> value)
        {
            if (value == null)
                return;

            value.Clear();

            if (_sortedSetCache.Count < MAXIMUM_CACHE_COUNT)
                _sortedSetCache.Push(value);
        }

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref Queue<T> value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        public static void Store(Queue<T> value)
        {
            if (value == null)
                return;

            value.Clear();

            if (_queueCache.Count < MAXIMUM_CACHE_COUNT)
                _queueCache.Push(value);
        }

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref BasicQueue<T> value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        public static void Store(BasicQueue<T> value)
        {
            if (value == null)
                return;

            value.Clear();

            if (_basicQueueCache.Count < MAXIMUM_CACHE_COUNT)
                _basicQueueCache.Push(value);
        }

        /// <summary>
        /// Stores a collection and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref HashSet<T> value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a collection.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        public static void Store(HashSet<T> value)
        {
            if (value == null)
                return;

            value.Clear();

            if (_hashSetCache.Count < MAXIMUM_CACHE_COUNT)
                _hashSetCache.Push(value);
        }
    }

    /// <summary>
    /// Caches objects of a single generic.
    /// </summary>
    public static class ObjectCaches<T> where T : new()
    {
        /// <summary>
        /// Stack to use.
        /// </summary>
        private static readonly ConcurrentStack<T> _stack = new();
        /// <summary>
        /// Maximum number of entries allowed for the cache.
        /// </summary>
        private const int MAXIMUM_CACHE_COUNT = 50;

        /// <summary>
        /// Returns a value from the stack or creates an instance when the stack is empty.
        /// </summary>
        /// <returns></returns>
        public static T Retrieve()
        {
            T result;
            if (!_stack.TryPop(out result))
                result = new(); // Activator.CreateInstance<T>();

            return result;
        }

        /// <summary>
        /// Stores an instance of T and sets the original reference to default.
        /// Method will not execute if value is null.
        /// </summary>
        /// <param name = "value">Value to store.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void StoreAndDefault(ref T value)
        {
            Store(value);
            value = default;
        }

        /// <summary>
        /// Stores a value to the stack.
        /// </summary>
        /// <param name = "value"></param>
        public static void Store(T value)
        {
            if (value == null)
                return;

            if (_stack.Count < MAXIMUM_CACHE_COUNT)
                _stack.Push(value);
        }
    }
    #endregion
}