using System.Collections.Generic;
using System.Threading;

namespace StackExchange.Redis
{
    /// <summary>
    /// <para>A collection of IProfiledCommands.</para>
    /// <para>This is a very light weight data structure, only supporting enumeration.</para>
    /// <para>
    /// While it implements IEnumerable, it there are fewer allocations if one uses
    /// it's explicit GetEnumerator() method.  Using `foreach` does this automatically.
    /// </para>
    /// <para>This type is not threadsafe.</para>
    /// </summary>
    public readonly struct ProfiledCommandEnumerable : IEnumerable<IProfiledCommand>
    {
        /// <summary>
        /// <para>
        /// Implements IEnumerator for ProfiledCommandEnumerable.
        /// This implementation is comparable to List.Enumerator and Dictionary.Enumerator,
        /// and is provided to reduce allocations in the common (ie. foreach) case.
        /// </para>
        /// <para>This type is not threadsafe.</para>
        /// </summary>
        public struct Enumerator : IEnumerator<IProfiledCommand>
        {
            private ProfileStorage Head;
            private ProfileStorage CurrentBacker;

            private bool IsEmpty => Head == null;
            private bool IsUnstartedOrFinished => CurrentBacker == null;

            internal Enumerator(ProfileStorage head)
            {
                Head = head;
                CurrentBacker = null;
            }

            /// <summary>
            /// The current element.
            /// </summary>
            public IProfiledCommand Current => CurrentBacker;

            object System.Collections.IEnumerator.Current => CurrentBacker;

            /// <summary>
            /// Advances the enumeration, returning true if there is a new element to consume and false
            /// if enumeration is complete.
            /// </summary>
            public bool MoveNext()
            {
                if (IsEmpty) return false;

                if (IsUnstartedOrFinished)
                {
                    CurrentBacker = Head;
                }
                else
                {
                    CurrentBacker = CurrentBacker.NextElement;
                }

                return CurrentBacker != null;
            }

            /// <summary>
            /// Resets the enumeration.
            /// </summary>
            public void Reset()
            {
                CurrentBacker = null;
            }

            /// <summary>
            /// Disposes the enumeration.
            /// subsequent attempts to enumerate results in undefined behavior.
            /// </summary>
            public void Dispose()
            {
                CurrentBacker = Head = null;
            }
        }

        private readonly ProfileStorage Head;

        internal ProfiledCommandEnumerable(ProfileStorage head)
        {
            Head = head;
        }

        /// <summary>
        /// <para>
        /// Returns an implementor of IEnumerator that, provided it isn't accessed
        /// though an interface, avoids allocations.
        /// </para>
        /// <para>`foreach` will automatically use this method.</para>
        /// </summary>
        public Enumerator GetEnumerator() => new Enumerator(Head);

        IEnumerator<IProfiledCommand> IEnumerable<IProfiledCommand>.GetEnumerator() => GetEnumerator();

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
    }

    /// <summary>
    /// <para>
    /// A thread-safe collection tailored to the "always append, with high contention, then enumerate once with no contention"
    /// behavior of our profiling.
    /// </para>
    /// <para>Performs better than ConcurrentBag, which is important since profiling code shouldn't impact timings.</para>
    /// </summary>
    internal sealed class ConcurrentProfileStorageCollection
    {
        // internal for test purposes
        internal static int AllocationCount = 0;

        // It is, by definition, impossible for an element to be in 2 intrusive collections
        //   and we force Enumeration to release any reference to the collection object
        //   so we can **always** pool these.
        private const int PoolSize = 64;
        private static readonly ConcurrentProfileStorageCollection[] Pool = new ConcurrentProfileStorageCollection[PoolSize];

        private volatile ProfileStorage Head;

        private ConcurrentProfileStorageCollection() { }

        // for testing purposes only
        internal static int CountInPool()
        {
            var ret = 0;

            for (var i = 0; i < PoolSize; i++)
            {
                var inPool = Pool[i];
                if (inPool != null) ret++;
            }

            return ret;
        }

        /// <summary>
        /// <para>This method is thread-safe.</para>
        /// <para>Adds an element to the bag.</para>
        /// <para>Order is not preserved.</para>
        /// <para>The element can only be a member of *one* bag.</para>
        /// </summary>
        /// <param name="command">The command to add.</param>
        public void Add(ProfileStorage command)
        {
            while (true)
            {
                var cur = Head;
                command.NextElement = cur;

                // Interlocked references to volatile fields are perfectly cromulent
#pragma warning disable 420
                var got = Interlocked.CompareExchange(ref Head, command, cur);
#pragma warning restore 420

                if (object.ReferenceEquals(got, cur)) break;
            }
        }

        /// <summary>
        /// <para>
        /// This method returns an enumerable view of the bag, and returns it to 
        /// an internal pool for reuse by GetOrCreate().
        /// </para>
        /// <para>It is not thread safe.</para>
        /// <para>It should only be called once the bag is finished being mutated.</para>
        /// </summary>
        public ProfiledCommandEnumerable EnumerateAndReturnForReuse()
        {
            var ret = new ProfiledCommandEnumerable(Head);

            ReturnForReuse();

            return ret;
        }

        /// <summary>
        /// This returns the ConcurrentProfileStorageCollection to an internal pool for reuse by GetOrCreate().
        /// </summary>
        public void ReturnForReuse()
        {
            // no need for interlocking, this isn't a thread safe method
            Head = null;

            for (var i = 0; i < PoolSize; i++)
            {
                if (Interlocked.CompareExchange(ref Pool[i], this, null) == null) break;
            }
        }

        /// <summary>
        /// <para>Returns a ConcurrentProfileStorageCollection to use.</para>
        /// <para>
        /// It *may* have allocated a new one, or it may return one that has previously been released.
        /// To return the collection, call EnumerateAndReturnForReuse()
        /// </para>
        /// </summary>
        public static ConcurrentProfileStorageCollection GetOrCreate()
        {
            ConcurrentProfileStorageCollection found;
            for (int i = 0; i < PoolSize; i++)
            {
                if ((found = Interlocked.Exchange(ref Pool[i], null)) != null)
                {
                    return found;
                }
            }

            Interlocked.Increment(ref AllocationCount);
            return new ConcurrentProfileStorageCollection();
        }
    }
}
