using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace SuperSocket.Common
{
    /// <summary>
    /// The pool information class
    /// </summary>
    public interface IPoolInfo
    {
        /// <summary>
        /// Gets the min size of the pool.
        /// </summary>
        /// <value>
        /// The min size of the pool.
        /// </value>
        int MinPoolSize { get; }

        /// <summary>
        /// Gets the max size of the pool.
        /// </summary>
        /// <value>
        /// The max size of the pool.
        /// </value>
        int MaxPoolSize { get; }


        /// <summary>
        /// Gets the avialable items count.
        /// </summary>
        /// <value>
        /// The avialable items count.
        /// </value>
        int AvialableItemsCount { get; }


        /// <summary>
        /// Gets the total items count, include items in the pool and outside the pool.
        /// </summary>
        /// <value>
        /// The total items count.
        /// </value>
        int TotalItemsCount { get; }
    }

    /// <summary>
    /// The basic interface of smart pool
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface ISmartPool<T> : IPoolInfo
    {
        /// <summary>
        /// Initializes the specified min pool size.
        /// </summary>
        /// <param name="minPoolSize">The min size of the pool.</param>
        /// <param name="maxPoolSize">The max size of the pool.</param>
        /// <param name="sourceCreator">The source creator.</param>
        /// <returns></returns>
        void Initialize(int minPoolSize, int maxPoolSize, ISmartPoolSourceCreator<T> sourceCreator);        

        /// <summary>
        /// Pushes the specified item into the pool.
        /// </summary>
        /// <param name="item">The item.</param>
        void Push(T item);

        /// <summary>
        /// Tries to get one item from the pool.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        bool TryGet(out T item);
    }

    /// <summary>
    /// ISmartPoolSource
    /// </summary>
    public interface ISmartPoolSource
    {
        /// <summary>
        /// Gets the count.
        /// </summary>
        /// <value>
        /// The count.
        /// </value>
        int Count { get; }
    }

    /// <summary>
    /// SmartPoolSource
    /// </summary>
    public class SmartPoolSource : ISmartPoolSource
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="SmartPoolSource" /> class.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="itemsCount">The items count.</param>
        public SmartPoolSource(object source, int itemsCount)
        {
            Source = source;
            Count = itemsCount;
        }

        /// <summary>
        /// Gets the source.
        /// </summary>
        /// <value>
        /// The source.
        /// </value>
        public object Source { get; private set; }

        /// <summary>
        /// Gets the count.
        /// </summary>
        /// <value>
        /// The count.
        /// </value>
        public int Count { get; private set; }
    }



    /// <summary>
    /// ISmartPoolSourceCreator
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface ISmartPoolSourceCreator<T>
    {
        /// <summary>
        /// Creates the specified size.
        /// </summary>
        /// <param name="size">The size.</param>
        /// <param name="poolItems">The pool items.</param>
        /// <returns></returns>
        ISmartPoolSource Create(int size, out T[] poolItems);
    }

    /// <summary>
    /// The smart pool
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class SmartPool<T> : ISmartPool<T>
    {
        private ConcurrentStack<T> m_GlobalStack;

        private ISmartPoolSource[] m_ItemsSource;

        private int m_CurrentSourceCount;

        private ISmartPoolSourceCreator<T> m_SourceCreator;

        private int m_MinPoolSize;

        /// <summary>
        /// Gets the size of the min pool.
        /// </summary>
        /// <value>
        /// The size of the min pool.
        /// </value>
        public int MinPoolSize
        {
            get
            {
                return m_MinPoolSize;
            }
        }

        private int m_MaxPoolSize;

        /// <summary>
        /// Gets the size of the max pool.
        /// </summary>
        /// <value>
        /// The size of the max pool.
        /// </value>
        public int MaxPoolSize
        {
            get
            {
                return m_MaxPoolSize;
            }
        }

        /// <summary>
        /// Gets the avialable items count.
        /// </summary>
        /// <value>
        /// The avialable items count.
        /// </value>
        public int AvialableItemsCount
        {
            get
            {
                return m_GlobalStack.Count;
            }
        }

        private int m_TotalItemsCount;

        /// <summary>
        /// Gets the total items count, include items in the pool and outside the pool.
        /// </summary>
        /// <value>
        /// The total items count.
        /// </value>
        public int TotalItemsCount
        {
            get { return m_TotalItemsCount; }
        }

        /// <summary>
        /// Initializes the specified min and max pool size.
        /// </summary>
        /// <param name="minPoolSize">The min size of the pool.</param>
        /// <param name="maxPoolSize">The max size of the pool.</param>
        /// <param name="sourceCreator">The source creator.</param>
        public void Initialize(int minPoolSize, int maxPoolSize, ISmartPoolSourceCreator<T> sourceCreator)
        {
            m_MinPoolSize = minPoolSize;
            m_MaxPoolSize = maxPoolSize;
            m_SourceCreator = sourceCreator;
            m_GlobalStack = new ConcurrentStack<T>();

            var n = 0;

            if (minPoolSize != maxPoolSize)
            {
                var currentValue = minPoolSize;

                while (true)
                {
                    n++;

                    var thisValue = currentValue * 2;

                    if (thisValue >= maxPoolSize)
                        break;

                    currentValue = thisValue;
                }
            }

            m_ItemsSource = new ISmartPoolSource[n + 1];

            T[] items;
            m_ItemsSource[0] = sourceCreator.Create(minPoolSize, out items);
            m_CurrentSourceCount = 1;

            for (var i = 0; i < items.Length; i++)
            {
                m_GlobalStack.Push(items[i]);
            }

            m_TotalItemsCount = m_MinPoolSize;
        }

        private int m_IsIncreasing = 0;

        /// <summary>
        /// Pushes the specified item into the pool.
        /// </summary>
        /// <param name="item">The item.</param>
        public void Push(T item)
        {
            m_GlobalStack.Push(item);
        }

        bool TryPopWithWait(out T item, int waitTicks)
        {
            var spinWait = new SpinWait();

            while (true)
            {
                spinWait.SpinOnce();

                if (m_GlobalStack.TryPop(out item))
                    return true;

                if (spinWait.Count >= waitTicks)
                {
                    return false;
                }
            }
        }

        /// <summary>
        /// Tries to get one item from the pool.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        /// <exception cref="System.NotImplementedException"></exception>
        public bool TryGet(out T item)
        {
            if (m_GlobalStack.TryPop(out item))
                return true;

            var currentSourceCount = m_CurrentSourceCount;

            if (currentSourceCount >= m_ItemsSource.Length)
            {
                return TryPopWithWait(out item, 100);
            }

            var isIncreasing = m_IsIncreasing;

            if (isIncreasing == 1)
                return TryPopWithWait(out item, 100);

            if (Interlocked.CompareExchange(ref m_IsIncreasing, 1, isIncreasing) != isIncreasing)
                return TryPopWithWait(out item, 100);

            IncreaseCapacity();

            m_IsIncreasing = 0;

            if (!m_GlobalStack.TryPop(out item))
            {
                return false;
            }

            return true;
        }

        private void IncreaseCapacity()
        {
            var newItemsCount = Math.Min(m_TotalItemsCount, m_MaxPoolSize - m_TotalItemsCount);

            T[] items;
            m_ItemsSource[m_CurrentSourceCount++] = m_SourceCreator.Create(newItemsCount, out items);

            m_TotalItemsCount += newItemsCount;

            for (var i = 0; i < items.Length; i++)
            {
                m_GlobalStack.Push(items[i]);
            }
        }
    }
}
