//-----------------------------------------------------------------------
// 
//     Copyright (c) Microsoft Corporation.
// 
//-----------------------------------------------------------------------
namespace Microsoft.Isam.Esent.Interop
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Runtime.CompilerServices;
    using System.Security.Permissions;
    using Microsoft.Isam.Esent.Interop.Vista;
    using Microsoft.Win32.SafeHandles;
    /// 
    /// A class that encapsulates a  in a disposable object. The
    /// instance must be closed last and closing the instance releases all other
    /// resources for the instance.
    /// 
    public class Instance : SafeHandleZeroOrMinusOneIsInvalid
    {
        /// 
        /// Parameters for the instance.
        /// 
        private readonly InstanceParameters parameters;
        /// 
        /// The name of the instance.
        /// 
        private readonly string name;
        /// 
        /// The display name of the instance.
        /// 
        private readonly string displayName;
        /// 
        /// The TermGrbit to be used at JetTerm time.
        /// 
        private TermGrbit termGrbit;
        /// 
        /// Initializes a new instance of the Instance class. The underlying
        /// JET_INSTANCE is allocated, but not initialized.
        /// 
        /// 
        /// The name of the instance. This string must be unique within a
        /// given process hosting the database engine.
        /// 
        [SecurityPermissionAttribute(SecurityAction.LinkDemand)]
        public Instance(string name) : this(name, name, TermGrbit.None)
        {
        }
        /// 
        /// Initializes a new instance of the Instance class. The underlying
        /// JET_INSTANCE is allocated, but not initialized.
        /// 
        /// 
        /// The name of the instance. This string must be unique within a
        /// given process hosting the database engine.
        /// 
        /// 
        /// A display name for the instance. This will be used in eventlog
        /// entries.
        /// 
        [SecurityPermissionAttribute(SecurityAction.LinkDemand)]
        public Instance(string name, string displayName) : this(name, displayName, TermGrbit.None)
        {
        }
        /// 
        /// Initializes a new instance of the Instance class. The underlying
        /// JET_INSTANCE is allocated, but not initialized.
        /// 
        /// 
        /// The name of the instance. This string must be unique within a
        /// given process hosting the database engine.
        /// 
        /// 
        /// A display name for the instance. This will be used in eventlog
        /// entries.
        /// 
        /// 
        /// The TermGrbit to be used at JetTerm time.
        /// 
        [SecurityPermissionAttribute(SecurityAction.LinkDemand)]
        public Instance(string name, string displayName, TermGrbit termGrbit) : base(true)
        {
            this.name = name;
            this.displayName = displayName;
            this.termGrbit = termGrbit;
            JET_INSTANCE instance;
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                this.SetHandle(JET_INSTANCE.Nil.Value);
            }
            finally
            {
                // This is the code that we want in a constrained execution region.
                // We need to avoid the situation where JetCreateInstance2 is called
                // but the handle isn't set, so the instance is never terminated.
                // This would happen, for example, if there was a ThreadAbortException
                // between the call to JetCreateInstance2 and the call to SetHandle.
                //
                // If an Esent exception is generated we do not want to call SetHandle
                // because the instance isn't valid. On the other hand if a different 
                // exception (out of memory or thread abort) is generated we still need
                // to set the handle to avoid losing track of the instance. The call to
                // JetCreateInstance2 is in the CER to make sure that the only exceptions
                // which can be generated are from ESENT failures.
                Api.JetCreateInstance2(out instance, this.name, this.displayName, CreateInstanceGrbit.None);
                this.SetHandle(instance.Value);
            }
            this.parameters = new InstanceParameters(instance);
        }
        /// 
        /// Gets the JET_INSTANCE that this instance contains.
        /// 
        public JET_INSTANCE JetInstance
        {
            [SecurityPermissionAttribute(SecurityAction.LinkDemand)]
            get
            {
                this.CheckObjectIsNotDisposed();
                return this.CreateInstanceFromHandle();
            }
        }
        /// 
        /// Gets the InstanceParameters for this instance. 
        /// 
        public InstanceParameters Parameters
        {
            [SecurityPermissionAttribute(SecurityAction.LinkDemand)]
            get
            {
                this.CheckObjectIsNotDisposed();
                return this.parameters;
            }
        }
        /// 
        /// Gets or sets the TermGrbit for this instance. 
        /// 
        public TermGrbit TermGrbit
        {
            [SecurityPermissionAttribute(SecurityAction.LinkDemand)]
            get
            {
                this.CheckObjectIsNotDisposed();
                return this.termGrbit;
            }
            [SecurityPermissionAttribute(SecurityAction.LinkDemand)]
            set
            {
                this.CheckObjectIsNotDisposed();
                this.termGrbit = value;
            }
        }
        /// 
        /// Provide implicit conversion of an Instance object to a JET_INSTANCE
        /// structure. This is done so that an Instance can be used anywhere a
        /// JET_INSTANCE is required.
        /// 
        /// The instance to convert.
        /// The JET_INSTANCE wrapped by the instance.
        [SecurityPermissionAttribute(SecurityAction.LinkDemand)]
        public static implicit operator JET_INSTANCE(Instance instance)
        {
            return instance.JetInstance;
        }
        /// 
        /// Returns a  that represents the current .
        /// 
        /// 
        /// A  that represents the current .
        /// 
        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture, "{0} ({1})", this.displayName, this.name);
        }
        /// 
        /// Initialize the JET_INSTANCE.
        /// 
        [SecurityPermissionAttribute(SecurityAction.LinkDemand)]
        public void Init()
        {
            this.Init(InitGrbit.None);
        }
        /// 
        /// Initialize the JET_INSTANCE.
        /// 
        /// 
        /// Initialization options.
        /// 
        [SecurityPermissionAttribute(SecurityAction.LinkDemand)]
        public void Init(InitGrbit grbit)
        {
            this.CheckObjectIsNotDisposed();
            JET_INSTANCE instance = this.JetInstance;
            // Use a constrained region so that the handle is
            // always set after JetInit2 is called.
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                // Remember that a failure in JetInit can zero the handle
                // and that JetTerm should not be called in that case.
                Api.JetInit2(ref instance, grbit);
            }
            finally
            {
                this.SetHandle(instance.Value);
            }
        }
        /// 
        /// Initialize the JET_INSTANCE. This API requires at least the
        /// Vista version of ESENT.
        /// 
        /// 
        /// Additional recovery parameters for remapping databases during
        /// recovery, position where to stop recovery at, or recovery status.
        /// 
        /// 
        /// Initialization options.
        /// 
        [SecurityPermissionAttribute(SecurityAction.LinkDemand)]
        public void Init(JET_RSTINFO recoveryOptions, InitGrbit grbit)
        {
            this.CheckObjectIsNotDisposed();
            JET_INSTANCE instance = this.JetInstance;
            // Use a constrained region so that the handle is
            // always set after JetInit3 is called.
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                // Remember that a failure in JetInit can zero the handle
                // and that JetTerm should not be called in that case.
                VistaApi.JetInit3(ref instance, recoveryOptions, grbit);
            }
            finally
            {
                this.SetHandle(instance.Value);
            }
        }
        /// 
        /// Terminate the JET_INSTANCE.
        /// 
        [SuppressMessage(
            "Microsoft.StyleCop.CSharp.MaintainabilityRules",
            "SA1409:RemoveUnnecessaryCode",
            Justification = "CER code belongs in the finally block, so the try clause is empty")]
        [SecurityPermissionAttribute(SecurityAction.LinkDemand)]
        public void Term()
        {
            // Use a constrained region so that the handle is
            // always set as invalid after JetTerm is called.
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                // This try block deliberately left blank.
            }
            finally
            {
                // This is the code that we want in a constrained execution region.
                // We need to avoid the situation where JetTerm is called
                // but the handle isn't invalidated, so the instance is terminated again.
                // This would happen, for example, if there was a ThreadAbortException
                // between the call to JetTerm and the call to SetHandle.
                //
                // If an Esent exception is generated we do not want to invalidate the handle
                // because the instance isn't necessarily terminated. On the other hand if a 
                // different exception (out of memory or thread abort) is generated we still need
                // to invalidate the handle.
                try
                {
                    Api.JetTerm2(this.JetInstance, this.termGrbit);
                }
                catch (EsentDirtyShutdownException)
                {
                    this.SetHandleAsInvalid();                
                    throw;
                }
                this.SetHandleAsInvalid();                
            }
        }
        /// 
        /// Release the handle for this instance.
        /// 
        /// True if the handle could be released.
        protected override bool ReleaseHandle()
        {
            // The object is already marked as invalid so don't check
            var instance = this.CreateInstanceFromHandle();
            return (int)JET_err.Success == Api.Impl.JetTerm2(instance, this.termGrbit);
        }
        /// 
        /// Create a JET_INSTANCE from the internal handle value.
        /// 
        /// A JET_INSTANCE containing the internal handle.
        private JET_INSTANCE CreateInstanceFromHandle()
        {
            return new JET_INSTANCE { Value = this.handle };
        }
        /// 
        /// Check to see if this instance is invalid or closed.
        /// 
        [SecurityPermissionAttribute(SecurityAction.LinkDemand)]
        private void CheckObjectIsNotDisposed()
        {
            if (this.IsInvalid || this.IsClosed)
            {
                throw new ObjectDisposedException("Instance");
            }
        }
    }
}