//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------

namespace System.IdentityModel
{
    using System;
    using System.Diagnostics;
    using System.Threading;
    using System.Runtime;

    /// <summary>
    /// Base class for common AsyncResult programming scenarios.
    /// </summary>
    public abstract class AsyncResult : IAsyncResult, IDisposable
    {
        /// <summary>
        /// End should be called when the End function for the asynchronous operation is complete.  It
        /// ensures the asynchronous operation is complete, and does some common validation.
        /// </summary>
        /// <param name="result">The <see cref="IAsyncResult"/> representing the status of an asynchronous operation.</param>
        public static void End(IAsyncResult result)
        {
            if (result == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("result");

            AsyncResult asyncResult = result as AsyncResult;

            if (asyncResult == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.ID4001), "result"));

            if (asyncResult.endCalled)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4002)));

            asyncResult.endCalled = true;

            if (!asyncResult.completed)
                asyncResult.AsyncWaitHandle.WaitOne();

            if (asyncResult.resetEvent != null)
                ((IDisposable)asyncResult.resetEvent).Dispose();

            if (asyncResult.exception != null)
                throw asyncResult.exception;
        }

        AsyncCallback callback;
        bool completed;
        bool completedSync;
        bool disposed;
        bool endCalled;
        Exception exception;
        ManualResetEvent resetEvent;
        object state;

        object thisLock;

        /// <summary>
        /// Constructor for async results that do not need a callback or state.
        /// </summary>
        protected AsyncResult()
            : this(null, null)
        {
        }

        /// <summary>
        /// Constructor for async results that do not need a callback.
        /// </summary>
        /// <param name="state">A user-defined object that qualifies or contains information about an asynchronous operation.</param>
        protected AsyncResult(object state)
            : this(null, state)
        {
        }

        /// <summary>
        /// Constructor for async results that need a callback and a state.
        /// </summary>
        /// <param name="callback">The method to be called when the async operation completes.</param>
        /// <param name="state">A user-defined object that qualifies or contains information about an asynchronous operation.</param>
        protected AsyncResult(AsyncCallback callback, object state)
        {
            this.thisLock = new object();
            this.callback = callback;
            this.state = state;
        }

        /// <summary>
        /// Finalizer for AsyncResult.
        /// </summary>
        ~AsyncResult()
        {
            Dispose(false);
        }

        /// <summary>
        /// Call this version of complete when your asynchronous operation is complete.  This will update the state
        /// of the operation and notify the callback.
        /// </summary>
        /// <param name="completedSynchronously">True if the asynchronous operation completed synchronously.</param>
        protected void Complete(bool completedSynchronously)
        {
            Complete(completedSynchronously, null);
        }

        /// <summary>
        /// Call this version of complete if you raise an exception during processing.  In addition to notifying
        /// the callback, it will capture the exception and store it to be thrown during AsyncResult.End.
        /// </summary>
        /// <param name="completedSynchronously">True if the asynchronous operation completed synchronously.</param>
        /// <param name="exception">The exception during the processing of the asynchronous operation.</param>
        protected void Complete(bool completedSynchronously, Exception exception)
        {
            if (completed == true)
            {
                // it is a bug to call complete twice
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new AsynchronousOperationException(SR.GetString(SR.ID4005)));
            }

            completedSync = completedSynchronously;
            this.exception = exception;

            if (completedSynchronously)
            {
                //
                // No event should be set for synchronous completion
                //
                completed = true;
                Fx.Assert(resetEvent == null, SR.GetString(SR.ID8025));
            }
            else
            {
                //
                // Complete asynchronously
                //
                lock (thisLock)
                {
                    completed = true;
                    if (resetEvent != null)
                        resetEvent.Set();
                }
            }

            //
            // finally call the call back. Note, we are expecting user's callback to handle the exception
            // so, if the callback throw exception, all we can do is burn and crash.
            //
            try
            {
                if (callback != null)
                    callback(this);
            }
            catch (ThreadAbortException)
            {
                //
                // The thread running the callback is being aborted. We ignore this case.
                //
            }
            catch (AsynchronousOperationException)
            {
                throw;
            }
#pragma warning suppress 56500
            catch (Exception unhandledException)
            {
                //
                // The callback raising an exception is equivalent to Main raising an exception w/out a catch.
                // We should just throw it back. We should log the exception somewhere.
                //
                // Because the stack trace gets lost on a rethrow, we're wrapping it in a generic exception
                // so the stack trace is preserved.
                //
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new AsynchronousOperationException(SR.GetString(SR.ID4003), unhandledException));
            }
        }

        /// <summary>
        /// Disposes of unmanaged resources held by the AsyncResult.
        /// </summary>
        /// <param name="isExplicitDispose">True if this is an explicit call to Dispose.</param>
        protected virtual void Dispose(bool isExplicitDispose)
        {
            if (!disposed)
            {
                if (isExplicitDispose)
                {
                    lock (thisLock)
                    {
                        if (!disposed)
                        {
                            //
                            // Mark disposed
                            //
                            disposed = true;

                            //
                            // Called explicitly to close the object
                            //
                            if (resetEvent != null)
                                resetEvent.Close();
                        }
                    }
                }
                else
                {
                    //
                    // Called for finalization
                    //
                }
            }
        }

        #region IAsyncResult implementation

        /// <summary>
        /// Gets the user-defined state information that was passed to the Begin method.
        /// </summary>
        public object AsyncState
        {
            get
            {
                return state;
            }
        }

        /// <summary>
        /// Gets the wait handle of the async event.
        /// </summary>
        public virtual WaitHandle AsyncWaitHandle
        {
            get
            {
                if (resetEvent == null)
                {
                    bool savedCompleted = completed;

                    lock (thisLock)
                    {
                        if (resetEvent == null)
                            resetEvent = new ManualResetEvent(completed);
                    }

                    if (!savedCompleted && completed)
                        resetEvent.Set();
                }

                return resetEvent;
            }
        }

        /// <summary>
        /// Gets a value that indicates whether the asynchronous operation completed synchronously.
        /// </summary>
        public bool CompletedSynchronously
        {
            get
            {
                return completedSync;
            }
        }

        /// <summary>
        /// Gets a value that indicates whether the asynchronous operation has completed.
        /// </summary>
        public bool IsCompleted
        {
            get
            {
                return completed;
            }
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Disposes this object
        /// </summary>
        public void Dispose()
        {
            Dispose(true);

            GC.SuppressFinalize(this);
        }

        #endregion
    }
}

