/*  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.Diagnostics.CodeAnalysis;
using System.IO;
using System.Security;
using System.Text;

namespace Alphaleonis.Win32.Filesystem
{
   /// <summary>Contains Shell32 information about a file.</summary>
   [SerializableAttribute]
   [SecurityCritical]
   public sealed class Shell32Info
   {
      #region Constructors

      /// <summary>Initializes a Shell32Info instance.
      /// <remarks>Shell32 is limited to MAX_PATH length.</remarks>
      /// <remarks>This constructor does not check if a file exists. This constructor is a placeholder for a string that is used to access the file in subsequent operations.</remarks>
      /// </summary>
      /// <param name="fileName">The fully qualified name of the new file, or the relative file name. Do not end the path with the directory separator character.</param>
      public Shell32Info(string fileName) : this(fileName, PathFormat.RelativePath)
      {
      }

      /// <summary>Initializes a Shell32Info instance.
      /// <remarks>Shell32 is limited to MAX_PATH length.</remarks>
      /// <remarks>This constructor does not check if a file exists. This constructor is a placeholder for a string that is used to access the file in subsequent operations.</remarks>
      /// </summary>
      /// <param name="fileName">The fully qualified name of the new file, or the relative file name. Do not end the path with the directory separator character.</param>
      /// <param name="pathFormat">Indicates the format of the path parameter(s).</param>
      public Shell32Info(string fileName, PathFormat pathFormat)
      {
         if (Utils.IsNullOrWhiteSpace(fileName))
            throw new ArgumentNullException("fileName");

         // Shell32 is limited to MAX_PATH length.
         // Get a full path of regular format.

         FullPath = Path.GetExtendedLengthPathCore(null, fileName, pathFormat, GetFullPathOptions.RemoveTrailingDirectorySeparator | GetFullPathOptions.FullCheck);

         Initialize();
      }

      #endregion // Constructors

      
      #region Methods

      /// <summary>Gets an <see cref="IntPtr"/> handle to the Shell icon that represents the file.</summary>
      /// <param name="iconAttributes">Icon size <see cref="Shell32.FileAttributes.SmallIcon"/> or <see cref="Shell32.FileAttributes.LargeIcon"/>. Can also be combined with <see cref="Shell32.FileAttributes.AddOverlays"/> and others.</param>
      /// <returns>An <see cref="IntPtr"/> handle to the Shell icon that represents the file.</returns>
      /// <remarks>Caller is responsible for destroying this handle with DestroyIcon() when no longer needed.</remarks>
      [SecurityCritical]
      public IntPtr GetIcon(Shell32.FileAttributes iconAttributes)
      {
         return Shell32.GetFileIcon(FullPath, iconAttributes);
      }


      /// <summary>Gets the Shell command association from the registry.</summary>
      /// <param name="shellVerb">The shell verb.</param>
      /// <returns>
      ///   Returns the associated file- or protocol-related Shell command from the registry or <c>string.Empty</c> if no association can be
      ///   found.
      /// </returns>
      [SecurityCritical]
      public string GetVerbCommand(string shellVerb)
      {
         return GetString(_iQaNone, Shell32.AssociationString.Command, shellVerb);
      }


      [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
      [SecurityCritical]
      private static string GetString(NativeMethods.IQueryAssociations iQa, Shell32.AssociationString assocString, string shellVerb)
      {
         // GetString() throws Exceptions.
         try
         {
            // Use a large buffer to prevent calling this function twice.
            var size = NativeMethods.DefaultFileBufferSize;
            var buffer = new StringBuilder(size);

            iQa.GetString(Shell32.AssociationAttributes.NoTruncate | Shell32.AssociationAttributes.RemapRunDll, assocString, shellVerb, buffer, out size);

            return buffer.ToString();
         }
         catch
         {
            return string.Empty;
         }
      }


      private NativeMethods.IQueryAssociations _iQaNone;    // Retrieve info from Shell.
      private NativeMethods.IQueryAssociations _iQaByExe;   // Retrieve info from exe file.

      [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
      [SecurityCritical]
      private void Initialize()
      {
         if (Initialized)
            return;

         var iidIQueryAssociations = new Guid(NativeMethods.QueryAssociationsGuid);

         if (NativeMethods.AssocCreate(NativeMethods.ClsidQueryAssociations, ref iidIQueryAssociations, out _iQaNone) == Win32Errors.S_OK)
         {
            try
            {
               _iQaNone.Init(Shell32.AssociationAttributes.None, FullPath, IntPtr.Zero, IntPtr.Zero);

               if (NativeMethods.AssocCreate(NativeMethods.ClsidQueryAssociations, ref iidIQueryAssociations, out _iQaByExe) == Win32Errors.S_OK)
               {
                  _iQaByExe.Init(Shell32.AssociationAttributes.InitByExeName, FullPath, IntPtr.Zero, IntPtr.Zero);

                  Initialized = true;
               }
            }
            catch
            {
            }
         }
      }


      /// <summary>Refreshes the state of the object.</summary>
      [SecurityCritical]
      public void Refresh()
      {
         Association = Command = ContentType = DdeApplication = DefaultIcon = FriendlyAppName = FriendlyDocName = OpenWithAppName = null;
         Attributes = Shell32.GetAttributesOf.None;
         Initialized = false;
         Initialize();
      }


      /// <summary>Returns the path as a string.</summary>
      /// <returns>The path.</returns>      
      public override string ToString()
      {
         return FullPath;
      }

      #endregion // Methods


      #region Properties

      private string _association;

      /// <summary>Gets the Shell file or protocol association from the registry.</summary>
      public string Association
      {
         get
         {
            if (_association == null)
               _association = GetString(_iQaNone, Shell32.AssociationString.Executable, null);

            return _association;
         }

         private set { _association = value; }
      }

      
      private Shell32.GetAttributesOf _attributes;

      /// <summary>The attributes of the file object.</summary>
      public Shell32.GetAttributesOf Attributes
      {
         get
         {
            if (_attributes == Shell32.GetAttributesOf.None)
            {
               var fileInfo = Shell32.GetFileInfoCore(FullPath, FileAttributes.Normal, Shell32.FileAttributes.Attributes, false, true);
               _attributes = fileInfo.Attributes;
            }

            return _attributes;
         }

         private set { _attributes = value; }
      }


      private string _command;

      /// <summary>Gets the Shell command association from the registry.</summary>
      public string Command
      {
         get
         {
            if (_command == null)
               _command = GetString(_iQaNone, Shell32.AssociationString.Command, null);

            return _command;
         }

         private set { _command = value; }
      }


      private string _contentType;

      /// <summary>Gets the Shell command association from the registry.</summary>
      public string ContentType
      {
         get
         {
            if (_contentType == null)
               _contentType = GetString(_iQaNone, Shell32.AssociationString.ContentType, null);

            return _contentType;
         }

         private set { _contentType = value; }
      }


      private string _ddeApplication;

      /// <summary>Gets the Shell DDE association from the registry.</summary>
      [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Dde")]
      public string DdeApplication
      {
         get
         {
            if (_ddeApplication == null)
               _ddeApplication = GetString(_iQaNone, Shell32.AssociationString.DdeApplication, null);

            return _ddeApplication;
         }

         private set { _ddeApplication = value; }
      }


      private string _defaultIcon;

      /// <summary>Gets the Shell default icon association from the registry.</summary>
      public string DefaultIcon
      {
         get
         {
            if (_defaultIcon == null)
               _defaultIcon = GetString(_iQaNone, Shell32.AssociationString.DefaultIcon, null);

            return _defaultIcon;
         }

         private set { _defaultIcon = value; }
      }


      /// <summary>Represents the fully qualified path of the file.</summary>
      public string FullPath { get; private set; }


      private string _friendlyAppName;

      /// <summary>Gets the Shell friendly application name association from the registry.</summary>
      public string FriendlyAppName
      {
         get
         {
            if (_friendlyAppName == null)
               _friendlyAppName = GetString(_iQaByExe, Shell32.AssociationString.FriendlyAppName, null);

            return _friendlyAppName;
         }

         private set { _friendlyAppName = value; }
      }


      private string _friendlyDocName;

      /// <summary>Gets the Shell friendly document name association from the registry.</summary>
      public string FriendlyDocName
      {
         get
         {
            if (_friendlyDocName == null)
               _friendlyDocName = GetString(_iQaNone, Shell32.AssociationString.FriendlyDocName, null);

            return _friendlyDocName;
         }

         private set { _friendlyDocName = value; }
      }


      /// <summary>Reflects the initialization state of the instance.</summary>
      internal bool Initialized { get; set; }


      private string _openWithAppName;

      /// <summary>Gets the Shell "Open With" command association from the registry.</summary>
      public string OpenWithAppName
      {
         get
         {
            if (_openWithAppName == null)
               _openWithAppName = GetString(_iQaNone, Shell32.AssociationString.FriendlyAppName, null);

            return _openWithAppName;
         }

         private set { _openWithAppName = value; }
      }

      #endregion // Properties
   }
}
