using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.FlowAnalyze
{
    static class TaskToApm
    {
        /// 
        /// Marshals the Task as an IAsyncResult, using the supplied callback and state
        /// to implement the APM pattern.
        /// 
        /// The Task to be marshaled.
        /// The callback to be invoked upon completion.
        /// The state to be stored in the IAsyncResult.
        /// An IAsyncResult to represent the task's asynchronous operation.
        public static IAsyncResult Begin(Task task, AsyncCallback? callback, object? state) =>
            new TaskAsyncResult(task, state, callback);
        /// Processes an IAsyncResult returned by Begin.
        /// The IAsyncResult to unwrap.
        public static void End(IAsyncResult asyncResult)
        {
            if (asyncResult is TaskAsyncResult twar)
            {
                twar._task.GetAwaiter().GetResult();
                return;
            }
            throw new ArgumentNullException();
        }
        /// Processes an IAsyncResult returned by Begin.
        /// The IAsyncResult to unwrap.
        public static TResult End(IAsyncResult asyncResult)
        {
            if (asyncResult is TaskAsyncResult twar && twar._task is Task task)
            {
                return task.GetAwaiter().GetResult();
            }
            throw new ArgumentNullException();
        }
        /// Provides a simple IAsyncResult that wraps a Task.
        /// 
        /// We could use the Task as the IAsyncResult if the Task's AsyncState is the same as the object state,
        /// but that's very rare, in particular in a situation where someone cares about allocation, and always
        /// using TaskAsyncResult simplifies things and enables additional optimizations.
        /// 
        internal sealed class TaskAsyncResult : IAsyncResult
        {
            /// The wrapped Task.
            internal readonly Task _task;
            /// Callback to invoke when the wrapped task completes.
            private readonly AsyncCallback? _callback;
            /// Initializes the IAsyncResult with the Task to wrap and the associated object state.
            /// The Task to wrap.
            /// The new AsyncState value.
            /// Callback to invoke when the wrapped task completes.
            internal TaskAsyncResult(Task task, object? state, AsyncCallback? callback)
            {
                Debug.Assert(task != null);
                _task = task;
                AsyncState = state;
                if (task.IsCompleted)
                {
                    // Synchronous completion.  Invoke the callback.  No need to store it.
                    CompletedSynchronously = true;
                    callback?.Invoke(this);
                }
                else if (callback != null)
                {
                    // Asynchronous completion, and we have a callback; schedule it. We use OnCompleted rather than ContinueWith in
                    // order to avoid running synchronously if the task has already completed by the time we get here but still run
                    // synchronously as part of the task's completion if the task completes after (the more common case).
                    _callback = callback;
                    _task.ConfigureAwait(continueOnCapturedContext: false)
                         .GetAwaiter()
                         .OnCompleted(InvokeCallback); // allocates a delegate, but avoids a closure
                }
            }
            /// Invokes the callback.
            private void InvokeCallback()
            {
                Debug.Assert(!CompletedSynchronously);
                Debug.Assert(_callback != null);
                _callback.Invoke(this);
            }
            /// Gets a user-defined object that qualifies or contains information about an asynchronous operation.
            public object? AsyncState { get; }
            /// Gets a value that indicates whether the asynchronous operation completed synchronously.
            /// This is set lazily based on whether the  has completed by the time this object is created.
            public bool CompletedSynchronously { get; }
            /// Gets a value that indicates whether the asynchronous operation has completed.
            public bool IsCompleted => _task.IsCompleted;
            /// Gets a  that is used to wait for an asynchronous operation to complete.
            public WaitHandle AsyncWaitHandle => ((IAsyncResult)_task).AsyncWaitHandle;
        }
    }
}