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

namespace Alphaleonis.Win32
{
   /// <summary>Static class providing access to information about the operating system under which the assembly is executing.</summary>
   public static class OperatingSystem
   {
      #region OperatingSystem Name Enum

      /// <summary>A set of flags that describe the named Windows versions.</summary>
      /// <remarks>The values of the enumeration are ordered. A later released operating system version has a higher number, so comparisons between named versions are meaningful.</remarks>
      [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Os")]
      [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Os")]
      public enum EnumOsName
      {
         /// <summary>A Windows version earlier than Windows 2000.</summary>
         Earlier = -1,

         /// <summary>Windows 2000 (Server or Professional).</summary>
         Windows2000 = 0,

         /// <summary>Windows XP.</summary>
         WindowsXP = 1,

         /// <summary>Windows Server 2003.</summary>
         WindowsServer2003 = 2,

         /// <summary>Windows Vista.</summary>
         WindowsVista = 3,

         /// <summary>Windows Server 2008.</summary>
         WindowsServer2008 = 4,

         /// <summary>Windows 7.</summary>
         Windows7 = 5,

         /// <summary>Windows Server 2008 R2.</summary>
         WindowsServer2008R2 = 6,

         /// <summary>Windows 8.</summary>
         Windows8 = 7,

         /// <summary>Windows Server 2012.</summary>
         WindowsServer2012 = 8,

         /// <summary>Windows 8.1.</summary>
         Windows81 = 9,

         /// <summary>Windows Server 2012 R2</summary>
         WindowsServer2012R2 = 10,

         /// <summary>Windows 10</summary>
         Windows10 = 11,

         /// <summary>Windows Server</summary>
         WindowsServer = 12,

         /// <summary>A later version of Windows than currently installed.</summary>
         Later = 65535
      }

      #endregion // OperatingSystem Name Enum

      #region ProcessorArchitecture Name enum

      /// <summary>A set of flags to indicate the current processor architecture for which the operating system is targeted and running.</summary>
      [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Pa")]
      [SuppressMessage("Microsoft.Design", "CA1028:EnumStorageShouldBeInt32")]      
      public enum EnumProcessorArchitecture 
      {
         /// <summary>PROCESSOR_ARCHITECTURE_INTEL
         /// <para>The system is running a 32-bit version of Windows.</para>
         /// </summary>
         X86 = 0,

         /// <summary>PROCESSOR_ARCHITECTURE_IA64
         /// <para>The system is running on a Itanium processor.</para>
         /// </summary>
         [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ia")]
         [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Ia")]
         IA64 = 6,

         /// <summary>PROCESSOR_ARCHITECTURE_AMD64
         /// <para>The system is running a 64-bit version of Windows.</para>
         /// </summary>
         X64 = 9,

         /// <summary>PROCESSOR_ARCHITECTURE_UNKNOWN
         /// <para>Unknown architecture.</para>
         /// </summary>
         Unknown = 65535
      }

      #endregion // ProcessorArchitecture Name enum


      #region Properties

      #region IsServer

      private static bool _isServer;

      /// <summary>Gets a value indicating whether the operating system is a server operating system.</summary>
      /// <value><see langword="true"/> if the current operating system is a server operating system; otherwise, <see langword="false"/>.</value>
      public static bool IsServer
      {
         get
         {
            if (_servicePackVersion == null)
               UpdateData();
            return _isServer;
         }
      }

      #endregion // IsServer

      #region IsWow64Process

      private static bool? _isWow64Process;

      /// <summary>Gets a value indicating whether the current process is running under WOW64.</summary>
      /// <value><see langword="true"/> if the current process is running under WOW64; otherwise, <see langword="false"/>.</value>
      public static bool IsWow64Process
      {
         get
         {
            if (_isWow64Process == null)
            {
               bool value;
               IntPtr processHandle = Process.GetCurrentProcess().Handle;

               if (!NativeMethods.IsWow64Process(processHandle, out value))
                  Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());

               // A pointer to a value that is set to TRUE if the process is running under WOW64.
               // If the process is running under 32-bit Windows, the value is set to FALSE.
               // If the process is a 64-bit application running under 64-bit Windows, the value is also set to FALSE.

               _isWow64Process = value;
            }

            return (bool) _isWow64Process;
         }
      }

      #endregion // IsWow64Process

      #region OSVersion

      private static Version _osVersion;

      /// <summary>Gets the numeric version of the operating system.</summary>            
      /// <value>The numeric version of the operating system.</value>
      public static Version OSVersion
      {
         get
         {
            if (_osVersion == null)
               UpdateData();

            return _osVersion;
         }
      }

      #endregion // OSVersion

      #region VersionName

      private static EnumOsName _enumOsName = EnumOsName.Later;

      /// <summary>Gets the named version of the operating system.</summary>
      /// <value>The named version of the operating system.</value>
      public static EnumOsName VersionName
      {
         get
         {
            if (_servicePackVersion == null)
               UpdateData();

            return _enumOsName;
         }
      }

      #endregion // VersionName

      #region ProcessorArchitecture

      private static EnumProcessorArchitecture _processorArchitecture;

      /// <summary>Gets the processor architecture for which the operating system is targeted.</summary>
      /// <value>The processor architecture for which the operating system is targeted.</value>
      /// <remarks>If running under WOW64 this will return a 32-bit processor. Use <see cref="IsWow64Process"/> to determine if this is the case.</remarks>      
      public static EnumProcessorArchitecture ProcessorArchitecture
      {
         get
         {
            if (_servicePackVersion == null)
               UpdateData();

            return _processorArchitecture;
         }
      }

      #endregion // ProcessorArchitecture

      #region ServicePackVersion

      private static Version _servicePackVersion;

      /// <summary>Gets the version of the service pack currently installed on the operating system.</summary>
      /// <value>The version of the service pack currently installed on the operating system.</value>
      /// <remarks>Only the <see cref="System.Version.Major"/> and <see cref="System.Version.Minor"/> fields are used.</remarks>
      public static Version ServicePackVersion
      {
         get
         {
            if (_servicePackVersion == null)
               UpdateData();

            return _servicePackVersion;
         }
      }

      #endregion // ServicePackVersion

      #endregion // Properties

      #region Methods

      #region IsAtLeast

      /// <summary>Determines whether the operating system is of the specified version or later.</summary>
      /// <returns><see langword="true"/> if the operating system is of the specified <paramref name="version"/> or later; otherwise, <see langword="false"/>.</returns>      
      /// <param name="version">The lowest version for which to return true.</param>
      public static bool IsAtLeast(EnumOsName version)
      {
         return VersionName >= version;
      }

      /// <summary>Determines whether the operating system is of the specified version or later, allowing specification of a minimum service pack that must be installed on the lowest version.</summary>
      /// <returns><see langword="true"/> if the operating system matches the specified <paramref name="version"/> with the specified service pack, or if the operating system is of a later version; otherwise, <see langword="false"/>.</returns>      
      /// <param name="version">The minimum required version.</param>
      /// <param name="servicePackVersion">The major version of the service pack that must be installed on the minimum required version to return true. This can be 0 to indicate that no service pack is required.</param>
      public static bool IsAtLeast(EnumOsName version, int servicePackVersion)
      {
         return IsAtLeast(version) && ServicePackVersion.Major >= servicePackVersion;
      }

      #endregion // IsAtLeast
      
      #endregion // Methods


      #region Private

      [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RtlGetVersion")]
      private static void UpdateData()
      {
         var verInfo = new NativeMethods.RTL_OSVERSIONINFOEXW();

         // Needed to prevent: System.Runtime.InteropServices.COMException:
         // The data area passed to a system call is too small. (Exception from HRESULT: 0x8007007A)
         verInfo.dwOSVersionInfoSize = Marshal.SizeOf(verInfo);

         var sysInfo = new NativeMethods.SYSTEM_INFO();
         NativeMethods.GetNativeSystemInfo(ref sysInfo);


         // RtlGetVersion returns STATUS_SUCCESS (0).
         if (NativeMethods.RtlGetVersion(ref verInfo))
            throw new Win32Exception(Marshal.GetLastWin32Error(), "Function RtlGetVersion() failed to retrieve the operating system information.");


         _osVersion = new Version(verInfo.dwMajorVersion, verInfo.dwMinorVersion, verInfo.dwBuildNumber);

         _processorArchitecture = (EnumProcessorArchitecture) sysInfo.wProcessorArchitecture;
         _servicePackVersion = new Version(verInfo.wServicePackMajor, verInfo.wServicePackMinor);
         _isServer = verInfo.wProductType == NativeMethods.VER_NT_DOMAIN_CONTROLLER || verInfo.wProductType == NativeMethods.VER_NT_SERVER;


         // RtlGetVersion: https://msdn.microsoft.com/en-us/library/windows/hardware/ff561910%28v=vs.85%29.aspx

         // The following table summarizes the most recent operating system version numbers.
         //    Operating system	            Version number    Other
         // ================================================================================
         //    Windows 10                    10.0              OSVERSIONINFOEX.wProductType == VER_NT_WORKSTATION
         //    Windows Server                10.0              OSVERSIONINFOEX.wProductType != VER_NT_WORKSTATION
         //    Windows 8.1                   6.3               OSVERSIONINFOEX.wProductType == VER_NT_WORKSTATION
         //    Windows Server 2012 R2        6.3               OSVERSIONINFOEX.wProductType != VER_NT_WORKSTATION
         //    Windows 8	                  6.2               OSVERSIONINFOEX.wProductType == VER_NT_WORKSTATION
         //    Windows Server 2012	         6.2               OSVERSIONINFOEX.wProductType != VER_NT_WORKSTATION
         //    Windows 7	                  6.1               OSVERSIONINFOEX.wProductType == VER_NT_WORKSTATION
         //    Windows Server 2008 R2	      6.1               OSVERSIONINFOEX.wProductType != VER_NT_WORKSTATION
         //    Windows Server 2008	         6.0               OSVERSIONINFOEX.wProductType != VER_NT_WORKSTATION  
         //    Windows Vista	               6.0               OSVERSIONINFOEX.wProductType == VER_NT_WORKSTATION
         //    Windows Server 2003 R2	      5.2               GetSystemMetrics(SM_SERVERR2) != 0
         //    Windows Server 2003           5.2               GetSystemMetrics(SM_SERVERR2) == 0
         //    Windows XP 64-Bit Edition     5.2               (OSVERSIONINFOEX.wProductType == VER_NT_WORKSTATION) && (sysInfo.PaName == PaName.X64)
         //    Windows XP	                  5.1               Not applicable
         //    Windows 2000	               5.0               Not applicable


         // 10 == The lastest MajorVersion of Windows.
         if (verInfo.dwMajorVersion > 10)
            _enumOsName = EnumOsName.Later;

         else
            switch (verInfo.dwMajorVersion)
            {
               #region Version 10

               case 10:
                  switch (verInfo.dwMinorVersion)
                  {
                     // Windows 10 or Windows Server
                     case 0:
                        _enumOsName = (verInfo.wProductType == NativeMethods.VER_NT_WORKSTATION)
                           ? EnumOsName.Windows10
                           : EnumOsName.WindowsServer;
                        break;
                  }
                  break;

               #endregion // Version 10

               #region Version 6

               case 6:
                  switch (verInfo.dwMinorVersion)
                  {
                     // Windows Vista or Windows Server 2008
                     case 0:
                        _enumOsName = (verInfo.wProductType == NativeMethods.VER_NT_WORKSTATION)
                           ? EnumOsName.WindowsVista
                           : EnumOsName.WindowsServer2008;
                        break;

                     // Windows 7 or Windows Server 2008 R2
                     case 1:
                        _enumOsName = (verInfo.wProductType == NativeMethods.VER_NT_WORKSTATION)
                           ? EnumOsName.Windows7
                           : EnumOsName.WindowsServer2008R2;
                        break;

                     // Windows 8 or Windows Server 2012
                     case 2:
                        _enumOsName = (verInfo.wProductType == NativeMethods.VER_NT_WORKSTATION)
                           ? EnumOsName.Windows8
                           : EnumOsName.WindowsServer2012;
                        break;

                     // Windows 8.1 or Windows Server 2012 R2
                     case 3:
                        _enumOsName = (verInfo.wProductType == NativeMethods.VER_NT_WORKSTATION)
                           ? EnumOsName.Windows81
                           : EnumOsName.WindowsServer2012R2;
                        break;

                     default:
                        _enumOsName = EnumOsName.Later;
                        break;
                  }
                  break;

               #endregion // Version 6

               #region Version 5

               case 5:
                  switch (verInfo.dwMinorVersion)
                  {
                     case 0:
                        _enumOsName = EnumOsName.Windows2000;
                        break;

                     case 1:
                        _enumOsName = EnumOsName.WindowsXP;
                        break;

                     case 2:
                        _enumOsName = (verInfo.wProductType == NativeMethods.VER_NT_WORKSTATION && _processorArchitecture == EnumProcessorArchitecture.X64)
                           ? EnumOsName.WindowsXP
                           : (verInfo.wProductType != NativeMethods.VER_NT_WORKSTATION) ? EnumOsName.WindowsServer2003 : EnumOsName.Later;
                        break;

                     default:
                        _enumOsName = EnumOsName.Later;
                        break;
                  }
                  break;

               #endregion // Version 5

               default:
                  _enumOsName = EnumOsName.Earlier;
                  break;
            }
      }
      
      #region P/Invoke members / NativeMethods

      private static class NativeMethods
      {
         internal const short VER_NT_WORKSTATION = 1;
         internal const short VER_NT_DOMAIN_CONTROLLER = 2;
         internal const short VER_NT_SERVER = 3;


         [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
         internal struct RTL_OSVERSIONINFOEXW
         {
            public int dwOSVersionInfoSize;
            public readonly int dwMajorVersion;
            public readonly int dwMinorVersion;
            public readonly int dwBuildNumber;
            public readonly int dwPlatformId;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            public readonly string szCSDVersion;
            public readonly ushort wServicePackMajor;
            public readonly ushort wServicePackMinor;
            public readonly ushort wSuiteMask;
            public readonly byte wProductType;
            public readonly byte wReserved;
         }


         [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
         internal struct SYSTEM_INFO
         {
            public readonly ushort wProcessorArchitecture;
            private readonly ushort wReserved;
            public readonly uint dwPageSize;
            public readonly IntPtr lpMinimumApplicationAddress;
            public readonly IntPtr lpMaximumApplicationAddress;
            public readonly IntPtr dwActiveProcessorMask;
            public readonly uint dwNumberOfProcessors;
            public readonly uint dwProcessorType;
            public readonly uint dwAllocationGranularity;
            public readonly ushort wProcessorLevel;
            public readonly ushort wProcessorRevision;
         }


         /// <summary>The RtlGetVersion routine returns version information about the currently running operating system.</summary>
         /// <returns>RtlGetVersion returns STATUS_SUCCESS.</returns>
         /// <remarks>Available starting with Windows 2000.</remarks>
         [SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule"), DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Unicode)]
         [return: MarshalAs(UnmanagedType.Bool)]
         internal static extern bool RtlGetVersion([MarshalAs(UnmanagedType.Struct)] ref RTL_OSVERSIONINFOEXW lpVersionInformation);


         /// <summary>Retrieves information about the current system to an application running under WOW64.
         /// If the function is called from a 64-bit application, it is equivalent to the GetSystemInfo function.
         /// </summary>
         /// <returns>This function does not return a value.</returns>
         /// <remarks>To determine whether a Win32-based application is running under WOW64, call the <see cref="IsWow64Process"/> function.</remarks>
         /// <remarks>Minimum supported client: Windows XP [desktop apps | Windows Store apps]</remarks>
         /// <remarks>Minimum supported server: Windows Server 2003 [desktop apps | Windows Store apps]</remarks>
         [SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule")]
         [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode), SuppressUnmanagedCodeSecurity]
         internal static extern void GetNativeSystemInfo([MarshalAs(UnmanagedType.Struct)] ref SYSTEM_INFO lpSystemInfo);


         /// <summary>Determines whether the specified process is running under WOW64.</summary>
         /// <returns>
         /// If the function succeeds, the return value is a nonzero value.
         /// If the function fails, the return value is zero. To get extended error information, call GetLastError.
         /// </returns>
         /// <remarks>Minimum supported client: Windows Vista, Windows XP with SP2 [desktop apps only]</remarks>
         /// <remarks>Minimum supported server: Windows Server 2008, Windows Server 2003 with SP1 [desktop apps only]</remarks>
         [SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule")]
         [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode), SuppressUnmanagedCodeSecurity]
         [return: MarshalAs(UnmanagedType.Bool)]
         internal static extern bool IsWow64Process([In] IntPtr hProcess, [Out, MarshalAs(UnmanagedType.Bool)] out bool lpSystemInfo);
      }

      #endregion // P/Invoke members / NativeMethods

      #endregion // Private
   }
}
