//-----------------------------------------------------------------------
// 
//     Copyright (c) Microsoft Corporation.
// 
//-----------------------------------------------------------------------
namespace Microsoft.Isam.Esent.Interop
{
    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Threading;
    /// 
    /// Receives information about the progress of long-running operations,
    /// such as defragmentation, backup, or restore operations. During such
    /// operations, the database engine calls this callback function to give
    ///  an update on the progress of the operation.
    /// 
    /// 
    /// The session with which the long running operation was called.
    /// 
    /// The type of operation.
    /// The status of the operation.
    /// Optional data. May be a .
    /// An error code.
    public delegate JET_err JET_PFNSTATUS(JET_SESID sesid, JET_SNP snp, JET_SNT snt, object data);
    /// 
    /// Receives information about the progress of long-running operations,
    /// such as defragmentation, backup, or restore operations. During such
    /// operations, the database engine calls this callback function to give
    ///  an update on the progress of the operation.
    /// 
    /// 
    /// This is the internal version of the callback. The final parameter is
    /// a void* pointer, which may point to a NATIVE_SNPROG.
    /// 
    /// 
    /// The session with which the long running operation was called.
    /// 
    /// The type of operation.
    /// The status of the operation.
    /// Optional .
    /// An error code.
    internal delegate JET_err NATIVE_PFNSTATUS(IntPtr nativeSesid, uint snp, uint snt, IntPtr snprog);
    /// 
    /// Wraps a NATIVE_PFNSTATUS callback around a JET_PFNSTATUS. This is
    /// used to convert the snprog argument to a managed snprog.
    /// 
    internal sealed class StatusCallbackWrapper
    {
        /// 
        /// API call tracing.
        /// 
        private static readonly TraceSwitch TraceSwitch = new TraceSwitch("ESENT StatusCallbackWrapper", "Wrapper around unmanaged ESENT status callback");
        /// 
        /// The wrapped status callback.
        /// 
        private readonly JET_PFNSTATUS wrappedCallback;
        /// 
        /// The native version of the callback. This will be a closure (because we are wrapping
        /// a non-static method) so keep track of it here to make sure it isn't garbage collected.
        /// 
        private readonly NATIVE_PFNSTATUS nativeCallback;
#if !MANAGEDESENT_ON_WSA
        /// 
        /// Initializes static members of the  class. 
        /// 
        static StatusCallbackWrapper()
        {
            // We don't want a JIT failure when trying to execute the callback
            // because that would throw an exception through ESENT, corrupting it.
            // It is fine for the wrapped callback to fail because CallbackImpl
            // will catch the exception and deal with it.
            RuntimeHelpers.PrepareMethod(typeof(StatusCallbackWrapper).GetMethod(
                "CallbackImpl",
                BindingFlags.NonPublic | BindingFlags.Instance).MethodHandle);
        }
#endif
        /// 
        /// Initializes a new instance of the StatusCallbackWrapper class.
        /// 
        /// 
        /// The managed callback to use.
        /// 
        public StatusCallbackWrapper(JET_PFNSTATUS wrappedCallback)
        {
            this.wrappedCallback = wrappedCallback;
            this.nativeCallback = wrappedCallback != null ? this.CallbackImpl : (NATIVE_PFNSTATUS)null;
        }
        /// 
        /// Gets a NATIVE_PFNSTATUS callback that wraps the managed callback.
        /// 
        public NATIVE_PFNSTATUS NativeCallback
        {
            get
            {
                return this.nativeCallback;
            }
        }
        /// 
        /// Gets or sets the saved exception. If the callback throws an exception
        /// it is saved here and should be rethrown when the API call finishes.
        /// 
        private Exception SavedException { get; set; }
        /// 
        /// Gets or sets a value indicating whether the thread was aborted during
        /// the callback.
        /// 
        private bool ThreadWasAborted { get; set; }
        /// 
        /// If an exception was generated during a callback throw it.
        /// 
        public void ThrowSavedException()
        {
#if !MANAGEDESENT_ON_WSA // Thread model has changed in Windows store apps.
            if (this.ThreadWasAborted)
            {
                Thread.CurrentThread.Abort();
            }
#endif
            if (null != this.SavedException)
            {
                throw this.SavedException;
            }
        }
        /// 
        /// Callback function for native code. We don't want to throw an exception through
        /// unmanaged ESENT because that will corrupt ESENT's internal state. Instead we
        /// catch all exceptions and return an error instead. We use a CER to make catching
        /// the exceptions as reliable as possible.
        /// 
        /// 
        /// The session with which the long running operation was called.
        /// 
        /// The type of operation.
        /// The status of the operation.
        /// Optional .
        /// An error code.
        private JET_err CallbackImpl(IntPtr nativeSesid, uint nativeSnp, uint nativeSnt, IntPtr nativeData)
        {
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                var sesid = new JET_SESID { Value = nativeSesid };
                JET_SNP snp = (JET_SNP)nativeSnp;
                JET_SNT snt = (JET_SNT)nativeSnt;
                object data = CallbackDataConverter.GetManagedData(nativeData, snp, snt);
                return this.wrappedCallback(sesid, snp, snt, data);
            }
#if !MANAGEDESENT_ON_WSA // Thread model has changed in windows store apps.
            catch (ThreadAbortException)
            {
                Trace.WriteLineIf(TraceSwitch.TraceWarning, "Caught ThreadAbortException");
                // Stop the thread abort and let the unmanaged ESENT code finish.
                // ThrowSavedException will call Thread.Abort() again.
                this.ThreadWasAborted = true;
                LibraryHelpers.ThreadResetAbort();
                return JET_err.CallbackFailed;
            }
#endif
            catch (Exception ex)
            {
                Trace.WriteLineIf(
                    TraceSwitch.TraceWarning,
                    string.Format(CultureInfo.InvariantCulture, "Caught Exception {0}", ex));
                this.SavedException = ex;
                return JET_err.CallbackFailed;
            }
            // What happens if the thread is aborted here, outside of the CER?
            // We probably throw the exception through ESENT, which isn't good.
        }
    }
}