/*  Copyright (C) 2008-2016 Peter Palotas, Jeffrey Jangli, Alexandr Normuradov
 *  
 *  Permission is hereby granted, free of charge, to any person obtaining a copy 
 *  of this software and associated documentation files (the "Software"), to deal 
 *  in the Software without restriction, including without limitation the rights 
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 *  copies of the Software, and to permit persons to whom the Software is 
 *  furnished to do so, subject to the following conditions:
 *  
 *  The above copyright notice and this permission notice shall be included in 
 *  all copies or substantial portions of the Software.
 *  
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
 *  THE SOFTWARE. 
 */

using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.AccessControl;
using System.Security.Permissions;
using System.Transactions;

namespace Alphaleonis.Win32.Filesystem
{
   [ComImport]
   [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
   [Guid("79427A2B-F895-40e0-BE79-B57DC82ED231")]
   [SuppressUnmanagedCodeSecurity]
   internal interface IKernelTransaction
   {
      void GetHandle([Out] out SafeKernelTransactionHandle handle);
   }

   /// <summary>A KTM transaction object for use with the transacted operations in <see cref="Filesystem"/></summary>
   public sealed class KernelTransaction : MarshalByRefObject, IDisposable
   {
      /// <summary>Initializes a new instance of the <see cref="KernelTransaction"/> class, internally using the specified <see cref="Transaction"/>.
      /// This method allows the usage of methods accepting a <see cref="KernelTransaction"/> with an instance of <see cref="System.Transactions.Transaction"/>.
      /// </summary>
      /// <param name="transaction">The transaction to use for any transactional operations.</param>
      [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
      [SecurityCritical]
      public KernelTransaction(Transaction transaction)
      {
         ((IKernelTransaction) TransactionInterop.GetDtcTransaction(transaction)).GetHandle(out _hTrans);
      }

      /// <summary>Initializes a new instance of the <see cref="KernelTransaction"/> class with a default security descriptor, infinite timeout and no description.</summary>
      [SecurityCritical]
      public KernelTransaction()
         : this(0, null)
      {
      }

      /// <summary>Initializes a new instance of the <see cref="KernelTransaction"/> class with a default security descriptor.</summary>
      /// <param name="timeout"><para>The time, in milliseconds, when the transaction will be aborted if it has not already reached the prepared state.</para></param>
      /// <param name="description">A user-readable description of the transaction. This parameter may be <see langword="null"/>.</param>
      [SecurityCritical]      
      public KernelTransaction(int timeout, string description)
         : this(null, timeout, description)
      {
      }

      /// <summary>Initializes a new instance of the <see cref="KernelTransaction"/> class.</summary>
      /// <param name="securityDescriptor">The <see cref="ObjectSecurity"/> security descriptor.</param>
      /// <param name="timeout"><para>The time, in milliseconds, when the transaction will be aborted if it has not already reached the prepared state.</para>
      /// <para>Specify 0 to provide an infinite timeout.</para></param>
      /// <param name="description">A user-readable description of the transaction. This parameter may be <see langword="null"/>.</param>
      [SecurityCritical]
      public KernelTransaction(ObjectSecurity securityDescriptor, int timeout, string description)
      {
         if (!NativeMethods.IsAtLeastWindowsVista)
            throw new PlatformNotSupportedException(Resources.Requires_Windows_Vista_Or_Higher);

         using (var securityAttributes = new Security.NativeMethods.SecurityAttributes(securityDescriptor))
         {

            _hTrans = NativeMethods.CreateTransaction(securityAttributes, IntPtr.Zero, 0, 0, 0, timeout, description);
            int lastError = Marshal.GetLastWin32Error();            

            NativeMethods.IsValidHandle(_hTrans, lastError);
         }
      }

      /// <summary>Requests that the specified transaction be committed.</summary>
      /// <exception cref="TransactionAlreadyCommittedException"/>
      /// <exception cref="TransactionAlreadyAbortedException"/>
      /// <exception cref="Win32Exception"/>
      [SecurityCritical]
      public void Commit()
      {
         if (!NativeMethods.IsAtLeastWindowsVista)
            throw new PlatformNotSupportedException(Resources.Requires_Windows_Vista_Or_Higher);

         if (!NativeMethods.CommitTransaction(_hTrans))
            CheckTransaction();
      }

      /// <summary>Requests that the specified transaction be rolled back. This function is synchronous.</summary>
      /// <exception cref="TransactionAlreadyCommittedException"/>
      /// <exception cref="Win32Exception"/>
      [SecurityCritical]
      public void Rollback()
      {
         if (!NativeMethods.IsAtLeastWindowsVista)
            throw new PlatformNotSupportedException(Resources.Requires_Windows_Vista_Or_Higher);

         if (!NativeMethods.RollbackTransaction(_hTrans))
            CheckTransaction();
      }

      private static void CheckTransaction()
      {
         uint error = (uint) Marshal.GetLastWin32Error();
         int hr = Marshal.GetHRForLastWin32Error();

         switch (error)
         {
            case Win32Errors.ERROR_TRANSACTION_ALREADY_ABORTED:
               throw new TransactionAlreadyAbortedException("Transaction was already aborted", Marshal.GetExceptionForHR(hr));

            case Win32Errors.ERROR_TRANSACTION_ALREADY_COMMITTED:
               throw new TransactionAlreadyAbortedException("Transaction was already committed", Marshal.GetExceptionForHR(hr));

            default:
               Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
               break;
         }
      }

      /// <summary>Gets the safe handle.</summary>
      /// <value>The safe handle.</value>
      public SafeHandle SafeHandle
      {
         get { return _hTrans; }
      }

      private readonly SafeKernelTransactionHandle _hTrans;

      #region IDisposable Members

      /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
      [SecurityPermissionAttribute(SecurityAction.Demand, UnmanagedCode = true)]
      public void Dispose()
      {
         _hTrans.Close();
      }

      #endregion // IDisposable Members
   }
}
