/*
 * PlatformOps.cs --
 *
 * Copyright (c) 2007-2012 by Joe Mistachkin.  All rights reserved.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: $
 */

using System;

#if NATIVE && WINDOWS
using System.Diagnostics;
#endif

using System.IO;
using System.Runtime.InteropServices;

#if NATIVE
using System.Security;
#endif

#if !NET_40
using System.Security.Permissions;
#endif

using Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Constants;
using Eagle._Containers.Public;

namespace Eagle._Components.Private
{
#if NATIVE
#if NET_40
    [SecurityCritical()]
#else
    [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
#endif
#endif
    [ObjectId("751e3aab-6f53-4a0d-bc13-f1ab217ef7dc")]
    internal static class PlatformOps
    {
        #region Private Constants
        private const uint defaultPageSize = 4096; /* COMPAT: x86. */

        ///////////////////////////////////////////////////////////////////////

        private static readonly StringList processorNames =
            new StringList(new string[] {
            "intel", "mips", "alpha", "ppc", "shx",
            "arm", "ia64", "alpha64", "msil", "amd64",
            "ia32_on_win64"
        });

        ///////////////////////////////////////////////////////////////////////

        private static readonly StringList platformNames =
            new StringList(new string[] {
            "windows", "windows", "windows",
            "windows", "unix", "windows",
            "unix"
        });

        ///////////////////////////////////////////////////////////////////////

        private static readonly StringList operatingSystemNames =
            new StringList(new string[] {
            "Win32s", "Windows 9x", "Windows NT",
            "Windows CE", "Unix", "Xbox",
            "Darwin"
        });

        ///////////////////////////////////////////////////////////////////////

#if NATIVE
        private static readonly StringDictionary platformProcessorNames =
            new StringDictionary(new StringPair[] {
            new StringPair("Win32", "x86"),
            new StringPair("x86", "x86"),
            new StringPair("ARM", "arm"),
            new StringPair("AMD64", "x64"),
            new StringPair("x64", "x64"),
            new StringPair("Itanium", "IA64"),
            new StringPair("IA64", "IA64")
        });
#endif
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Private Data
        private static readonly object syncRoot = new object();
        private static bool initialized = false;

        ///////////////////////////////////////////////////////////////////////

#if NATIVE && WINDOWS
        private static UnsafeNativeMethods.SYSTEM_INFO systemInfo;
        private static UnsafeNativeMethods.OSVERSIONINFOEX versionInfo;
#endif

        ///////////////////////////////////////////////////////////////////////

        private static ProcessorArchitecture processorArchitecture =
            ProcessorArchitecture.Unknown;

        private static OperatingSystemId operatingSystemId =
            OperatingSystemId.Unknown;

        private static uint pageSize = defaultPageSize;

        ///////////////////////////////////////////////////////////////////////

        private static OperatingSystem operatingSystem = null;

        ///////////////////////////////////////////////////////////////////////

        private static string processorName = null;
        private static string platformName = null;
        private static string operatingSystemName = null;

        ///////////////////////////////////////////////////////////////////////

        private static bool isWin32onWin64 = false;
        #endregion

        ///////////////////////////////////////////////////////////////////////
        // Required Native APIs used via P/Invoke
        ///////////////////////////////////////////////////////////////////////

#if NATIVE
        #region Private Unsafe Native Methods Class
        [SuppressUnmanagedCodeSecurity()]
        [ObjectId("259417ba-e318-4982-b2c0-9f6fd4196b74")]
        private static class UnsafeNativeMethods
        {
#if WINDOWS
            [StructLayout(LayoutKind.Sequential)]
            [ObjectId("a858d038-1313-43d2-a9b0-7a00b2975933")]
            internal struct SYSTEM_INFO
            {
                public ProcessorArchitecture wProcessorArchitecture;
                public ushort wReserved;
                public uint dwPageSize;
                public IntPtr lpMinimumApplicationAddress;
                public IntPtr lpMaximumApplicationAddress;
                public UIntPtr dwActiveProcessorMask;
                public uint dwNumberOfProcessors;
                public uint dwProcessorType;
                public uint dwAllocationGranularity;
                public ushort wProcessorLevel;
                public ushort wProcessorRevision;
            }

            ///////////////////////////////////////////////////////////////////

            //
            // NOTE: Yes, this has been tested and the size must be exactly
            //       148 bytes.
            //
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
            [ObjectId("f33f3aa7-8ddc-48a0-8ac9-e950dcbaaac1")]
            internal struct OSVERSIONINFOEX
            {
                public uint dwOSVersionInfoSize;
                public uint dwMajorVersion;
                public uint dwMinorVersion;
                public uint dwBuildNumber;
                public OperatingSystemId dwPlatformId;
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
                public string szCSDVersion;
                public ushort wServicePackMajor;
                public ushort wServicePackMinor;
                public short wSuiteMask;
                public byte wProductType;
                public byte wReserved;
            }

            ///////////////////////////////////////////////////////////////////

            [DllImport(DllName.Kernel32,
                CallingConvention = CallingConvention.Winapi)]
            internal static extern void GetSystemInfo(
                ref SYSTEM_INFO systemInfo
            );

            ///////////////////////////////////////////////////////////////////

            [DllImport(DllName.Kernel32,
                CallingConvention = CallingConvention.Winapi,
                CharSet = CharSet.Auto, BestFitMapping = false,
                ThrowOnUnmappableChar = true, SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool GetVersionEx(
                ref OSVERSIONINFOEX versionInfo
            );

            ///////////////////////////////////////////////////////////////////

            [DllImport(DllName.Kernel32,
                CallingConvention = CallingConvention.Winapi,
                SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool IsWow64Process(
                IntPtr hProcess,
                [MarshalAs(UnmanagedType.Bool)]
                ref bool wow64Process
            );
#endif
        }
        #endregion
#endif

        ///////////////////////////////////////////////////////////////////////

        #region Static Constructor
        static PlatformOps()
        {
            Initialize();
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Public Initialization Methods
        public static void Initialize()
        {
            lock (syncRoot)
            {
                if (initialized)
                    return;

                ///////////////////////////////////////////////////////////////

#if NATIVE && WINDOWS
                if (GetSystemInfo(ref systemInfo))
                {
                    //
                    // NOTE: What is the processor architecture that we are
                    //       executing on?
                    //
                    processorArchitecture = systemInfo.wProcessorArchitecture;

                    //
                    // NOTE: What is the native memory page size?
                    //
                    pageSize = systemInfo.dwPageSize;
                }
#endif

                ///////////////////////////////////////////////////////////////

                //
                // NOTE: What is the name of the processor that we are
                //       executing on?
                //
                processorName = GetProcessorName(processorArchitecture);

                ///////////////////////////////////////////////////////////////

                //
                // NOTE: What is the operating system that we are executing on?
                //
                operatingSystem = Environment.OSVersion;

                ///////////////////////////////////////////////////////////////

#if NATIVE && WINDOWS
                if (GetOsVersionInfo(ref versionInfo))
                {
                    //
                    // NOTE: What is the platform we are executing on?
                    //
                    operatingSystemId = versionInfo.dwPlatformId;
                }
                else
#endif
                {
                    operatingSystemId = (OperatingSystemId)
                        GetOperatingSystemPlatformId();
                }

                ///////////////////////////////////////////////////////////////

                //
                // NOTE: What is the name of the platform that we are executing
                //       on?
                //
                if (operatingSystemId != OperatingSystemId.Unknown)
                {
                    platformName = GetPlatformName(operatingSystemId);
                }
                else
                {
                    platformName = GetPlatformName(
                        (OperatingSystemId)GetOperatingSystemPlatformId());
                }

                ///////////////////////////////////////////////////////////////

                //
                // NOTE: What is the name of the operating system that we are
                //       executing on?
                //
                operatingSystemName = GetOperatingSystemName(
                    operatingSystemId);

                ///////////////////////////////////////////////////////////////

                //
                // NOTE: Check if this process is running as Win32-on-Win64
                //       (WoW64).
                //
#if NATIVE && WINDOWS
                isWin32onWin64 = IsWin32onWin64();
#else
                isWin32onWin64 = false;
#endif

                ///////////////////////////////////////////////////////////////

                initialized = true;
            }
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Public Querying Methods
        public static ProcessorArchitecture GetProcessorArchitecture()
        {
            lock (syncRoot)
            {
                return processorArchitecture;
            }
        }

        ///////////////////////////////////////////////////////////////////////

        public static uint GetPageSize()
        {
            lock (syncRoot)
            {
                return pageSize;
            }
        }

        ///////////////////////////////////////////////////////////////////////

        public static string GetProcessorName()
        {
            lock (syncRoot)
            {
                return processorName;
            }
        }

        ///////////////////////////////////////////////////////////////////////

        public static string GetPlatformName()
        {
            lock (syncRoot)
            {
                return platformName;
            }
        }

        ///////////////////////////////////////////////////////////////////////

        public static OperatingSystem GetOperatingSystem()
        {
            lock (syncRoot)
            {
                return operatingSystem;
            }
        }

        ///////////////////////////////////////////////////////////////////////

        public static PlatformID GetOperatingSystemPlatformId()
        {
            lock (syncRoot)
            {
                return (operatingSystem != null) ? operatingSystem.Platform :
                    (PlatformID)(int)OperatingSystemId.Unknown;
            }
        }

        ///////////////////////////////////////////////////////////////////////

        public static Version GetOperatingSystemVersion()
        {
            lock (syncRoot)
            {
                return (operatingSystem != null) ?
                    operatingSystem.Version : null;
            }
        }

        ///////////////////////////////////////////////////////////////////////

        public static string GetOperatingSystemServicePack()
        {
            lock (syncRoot)
            {
#if NATIVE && WINDOWS
                return FormatOps.MajorMinor(new Version(
                    versionInfo.wServicePackMajor,
                    versionInfo.wServicePackMinor));
#else
                return (operatingSystem != null) ?
                    operatingSystem.ServicePack : null;
#endif
            }
        }

        ///////////////////////////////////////////////////////////////////////

        public static OperatingSystemId GetOperatingSystemId()
        {
            lock (syncRoot)
            {
                return operatingSystemId;
            }
        }

        ///////////////////////////////////////////////////////////////////////

        public static string GetOperatingSystemName()
        {
            lock (syncRoot)
            {
                return operatingSystemName;
            }
        }

        ///////////////////////////////////////////////////////////////////////

        public static bool GetWin32onWin64()
        {
            lock (syncRoot)
            {
                return isWin32onWin64;
            }
        }

        ///////////////////////////////////////////////////////////////////////

        public static string GetUserName(
            bool domain
            )
        {
            string result = Environment.UserName;

            if (!domain)
                return result;

            return Environment.UserDomainName +
                Path.DirectorySeparatorChar + result;
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Public Operating System Detection Support Methods
        public static bool IsUnixOperatingSystem()
        {
            // lock (syncRoot) /* EXEMPT: Possible hot-path (read-only). */
            {
                return ((operatingSystemId == OperatingSystemId.Unix) ||
                    (operatingSystemId == OperatingSystemId.Darwin) ||
                    (operatingSystemId == OperatingSystemId.Mono_on_Unix));
            }
        }

        ///////////////////////////////////////////////////////////////////////

        public static bool IsWindowsOperatingSystem()
        {
            // lock (syncRoot) /* EXEMPT: Possible hot-path (read-only). */
            {
                return ((operatingSystemId == OperatingSystemId.Windows9x) ||
                    (operatingSystemId == OperatingSystemId.WindowsNT));
            }
        }

        ///////////////////////////////////////////////////////////////////////

#if NATIVE && WINDOWS
        public static bool CheckVersion(
            PlatformID platformId,
            int major,
            int minor,
            short servicePackMajor,
            short servicePackMinor
            )
        {
            if (GetOperatingSystemPlatformId() == platformId)
            {
                Version osVersion = GetOperatingSystemVersion();

                if (osVersion.Major > major)
                {
                    return true;
                }
                else if ((osVersion.Major == major) &&
                    (osVersion.Minor > minor))
                {
                    return true;
                }
                else if ((osVersion.Major == major) &&
                    (osVersion.Minor == minor))
                {
                    ushort osServicePackMajor = versionInfo.wServicePackMajor;
                    ushort osServicePackMinor = versionInfo.wServicePackMinor;

                    if (osServicePackMajor > servicePackMajor)
                    {
                        return true;
                    }
                    else if ((osServicePackMajor == servicePackMajor) &&
                        (osServicePackMinor >= servicePackMinor))
                    {
                        return true;
                    }
                }
            }

            return false;
        }
#endif
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Public Process Bits Querying Methods
        public static int GetProcessBits() // (e.g. 32, 64, etc)
        {
            return (IntPtr.Size * ConversionOps.ByteBits);
        }

        ///////////////////////////////////////////////////////////////////////

        public static bool Is32BitProcess()
        {
            return (IntPtr.Size == sizeof(uint));
        }

        ///////////////////////////////////////////////////////////////////////

        public static bool Is64BitProcess()
        {
            return (IntPtr.Size == sizeof(ulong));
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Public Name Lookup Methods
#if NATIVE
        public static string GetProcessorName(
            string platform
            )
        {
            string processor;

            if ((platform != null) &&
                platformProcessorNames.TryGetValue(platform, out processor))
            {
                return processor;
            }

            return "unknown";
        }
#endif
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Private Name Lookup Methods
        private static string GetProcessorName(
            ProcessorArchitecture processorArchitecture
            )
        {
            lock (syncRoot)
            {
                ProcessorArchitecture count = (ProcessorArchitecture)
                    processorNames.Count;

                if ((processorArchitecture >= 0) &&
                    (processorArchitecture < count))
                {
                    return processorNames[(int)processorArchitecture];
                }
            }

            return "unknown";
        }

        ///////////////////////////////////////////////////////////////////////

        private static string GetOperatingSystemName(
            OperatingSystemId platformId
            )
        {
            lock (syncRoot)
            {
                OperatingSystemId count = (OperatingSystemId)
                    operatingSystemNames.Count;

                if ((platformId >= 0) && (platformId < count))
                {
                    return operatingSystemNames[(int)platformId];
                }
                else if (platformId == OperatingSystemId.Mono_on_Unix)
                {
                    return platformId.ToString().Replace(
                        Characters.Underscore, Characters.Space);
                }
            }

            return "unknown";
        }

        ///////////////////////////////////////////////////////////////////////

        private static string GetPlatformName(
            OperatingSystemId platformId
            )
        {
            lock (syncRoot)
            {
                OperatingSystemId count = (OperatingSystemId)
                    platformNames.Count;

                if ((platformId >= 0) && (platformId < count))
                    return platformNames[(int)platformId];
                else if (platformId == OperatingSystemId.Mono_on_Unix)
                    return OperatingSystemId.Unix.ToString();
            }

            return "unknown";
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Private Win32 Support Methods
#if NATIVE && WINDOWS
        private static bool GetSystemInfo(
            ref UnsafeNativeMethods.SYSTEM_INFO systemInfo
            )
        {
            try
            {
                /* CANNOT FAIL? */
                UnsafeNativeMethods.GetSystemInfo(ref systemInfo);

                return true;
            }
            catch
            {
                // do nothing.
            }

            return false;
        }

        ///////////////////////////////////////////////////////////////////////

        private static bool GetOsVersionInfo(
            ref UnsafeNativeMethods.OSVERSIONINFOEX versionInfo
            )
        {
            try
            {
                versionInfo.dwOSVersionInfoSize = (uint)Marshal.SizeOf(
                    versionInfo);

                return UnsafeNativeMethods.GetVersionEx(ref versionInfo);
            }
            catch
            {
                // do nothing.
            }

            return false;
        }

        ///////////////////////////////////////////////////////////////////////

        private static bool IsWin32onWin64()
        {
            try
            {
                Process process = Process.GetCurrentProcess();

                if (process != null)
                {
                    bool wow64Process = false;

                    if (UnsafeNativeMethods.IsWow64Process(
                            process.Handle, ref wow64Process))
                    {
                        return wow64Process;
                    }
                }
            }
            catch
            {
                // do nothing.
            }

            return false;
        }
#endif
        #endregion
    }
}
