using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Capnp.Util
{
    /// <summary>
    /// A task-like object which enforces that all await operations from the same thread leave in the exact order they were issued.
    /// Note that an ordinary .NET Task does not fulfill this requirement if completed by a thread which is different from the
    /// awaiting thread.
    /// </summary>
    public class StrictlyOrderedAwaitTask: INotifyCompletion
    {
        class Cover { }
        class Seal { }

        static readonly Cover s_cover = new Cover();
        static readonly Seal s_seal = new Seal();

        readonly Task _awaitedTask;
        object? _state;

        /// <summary>
        /// Constructs an instance
        /// </summary>
        /// <param name="awaitedTask">Task on which the order shall be enforced</param>
        public StrictlyOrderedAwaitTask(Task awaitedTask)
        {
            _awaitedTask = awaitedTask;
            _state = s_cover;
        }

        /// <summary>
        /// await pattern implementation
        /// </summary>
        /// <returns>An object suitable for the await pattern</returns>
        public StrictlyOrderedAwaitTask GetAwaiter()
        {
            return this;
        }

        async void AwaitInternal()
        {
            try
            {
                await _awaitedTask;
            }
            catch
            {
            }
            finally
            {
#if SOTASK_PERF
                long outerCount = 0, innerCount = 0;
#endif

                SpinWait.SpinUntil(() =>
                {
                    Action? continuations;

                    while (true)
                    {
#if SOTASK_PERF
                        ++innerCount;
#endif

                        continuations = (Action?)Interlocked.Exchange(ref _state, null);

                        if (continuations != null)
                            continuations();
                        else
                            break;
                    }

#if SOTASK_PERF
                    ++outerCount;
#endif
                    return Interlocked.CompareExchange(ref _state, s_seal, null) == null;
                });

#if SOTASK_PERF
                StrictlyOrderedTaskExtensions.Stats.UpdateAwaitInternal(outerCount, innerCount);
#endif
            }
        }

        /// <summary>
        /// Part of await pattern implementation. Do not use directly.
        /// </summary>
        public void OnCompleted(Action continuation)
        {
            bool first = false;

#if SOTASK_PERF
            long spinCount = 0;
#endif

            SpinWait.SpinUntil(() => {

#if SOTASK_PERF
                ++spinCount;
#endif

                object? cur, next;
                cur = Volatile.Read(ref _state);
                first = false;
                switch (cur)
                {
                    case Cover cover:
                        next = continuation;
                        first = true;
                        break;

                    case null:
                        next = continuation;
                        break;

                    case Action action:
                        next = action + continuation;
                        break;

                    default:
                        continuation();
                        return true;
                }

                return Interlocked.CompareExchange(ref _state, next, cur) == cur;
            });

#if SOTASK_PERF
            StrictlyOrderedTaskExtensions.Stats.UpdateOnCompleted(spinCount);
#endif

            if (first)
            {
                AwaitInternal();
            }
        }

        /// <summary>
        /// Whether the underlying task did complete and it is safe to skip continuation registration.
        /// </summary>
        public bool IsCompleted => _awaitedTask.IsCompleted && (_state == s_cover || _state == s_seal);

        /// <summary>
        /// Part of await pattern implementation. Do not use directly.
        /// </summary>
        public void GetResult() => _awaitedTask.GetAwaiter().GetResult();

        /// <summary>
        /// Task on which the order shall be enforced.
        /// </summary>
        public Task WrappedTask => _awaitedTask;
    }

    /// <summary>
    /// A task-like object which enforces that all await operations from the same thread leave in the exact order they were issued.
    /// Note that an ordinary .NET Task does not fulfill this requirement if completed by a thread which is different from the
    /// awaiting thread.
    /// </summary>
    public class StrictlyOrderedAwaitTask<T> : StrictlyOrderedAwaitTask
    {

        /// <summary>
        /// Constructs an instance
        /// </summary>
        /// <param name="awaitedTask">Task on which the order shall be enforced</param>
        public StrictlyOrderedAwaitTask(Task<T> awaitedTask): base(awaitedTask)
        {
        }

        /// <summary>
        /// Task on which the order shall be enforced.
        /// </summary>
        public new Task<T> WrappedTask => (Task<T>)base.WrappedTask;

        /// <summary>
        /// await pattern implementation
        /// </summary>
        /// <returns>An object suitable for the await pattern</returns>
        public new StrictlyOrderedAwaitTask<T> GetAwaiter() => this;

        /// <summary>
        /// Part of await pattern implementation. Do not use directly.
        /// </summary>
        public new T GetResult() => WrappedTask.GetAwaiter().GetResult();

        /// <summary>
        /// Redirects to the wrapped Task's result.
        /// </summary>
        public T Result => WrappedTask.Result;

    }

    /// <summary>
    /// Extension methods to simplify the use of <see cref="StrictlyOrderedAwaitTask"/>
    /// </summary>
    public static class StrictlyOrderedTaskExtensions
    {
#if SOTASK_PERF
        public class Statistics
        {
            internal long _awaitInternalMaxOuterIterations;
            internal long _awaitInternalMaxInnerIterations;
            internal long _onCompletedMaxSpins;

            public long AwaitInternalMaxOuterIterations => Volatile.Read(ref _awaitInternalMaxOuterIterations);
            public long AwaitInternalMaxInnerIterations => Volatile.Read(ref _awaitInternalMaxInnerIterations);
            public long OnCompletedMaxSpins => Volatile.Read(ref _onCompletedMaxSpins);

            public void Reset()
            {
                Volatile.Write(ref _awaitInternalMaxOuterIterations, 0);
                Volatile.Write(ref _awaitInternalMaxInnerIterations, 0);
                Volatile.Write(ref _onCompletedMaxSpins, 0);
            }

            internal static void InterlockedMax(ref long current, long value)
            {
                long existing;
                do
                {
                    existing = Volatile.Read(ref current);
                    if (value <= existing) return;
                } while (Interlocked.CompareExchange(ref current, value, existing) != existing);
            }

            internal void UpdateAwaitInternal(long outerCount, long innerCount)
            {
                InterlockedMax(ref _awaitInternalMaxOuterIterations, outerCount);
                InterlockedMax(ref _awaitInternalMaxInnerIterations, innerCount);
            }

            internal void UpdateOnCompleted(long spinCount)
            {
                InterlockedMax(ref _onCompletedMaxSpins, spinCount);
            }
        }

        public static readonly Statistics Stats = new Statistics();
#endif
        /// <summary>
        /// Converts the task to a task-like object which enforces that all await operations from the same thread leave in the exact order they were issued.
        /// </summary>
        /// <typeparam name="T">The type of the result produced by the Task</typeparam>
        /// <param name="task">Task to wrap</param>
        /// <returns>awaitable object</returns>
        public static StrictlyOrderedAwaitTask<T> EnforceAwaitOrder<T>(this Task<T> task)
        {
            return new StrictlyOrderedAwaitTask<T>(task);
        }

        /// <summary>
        /// Converts the task to a task-like object which enforces that all await operations from the same thread leave in the exact order they were issued.
        /// </summary>
        /// <param name="task">Task to wrap</param>
        /// <returns>awaitable object</returns>
        public static StrictlyOrderedAwaitTask EnforceAwaitOrder(this Task task)
        {
            return new StrictlyOrderedAwaitTask(task);
        }
    }
}