// Created with MonoDevelop
//
//    Copyright (C) 2019 Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

using Microsoft.Win32;


namespace Hiperscan.Common
{
    public static class OperatingSystem
    {
        [DllImport("kernel32.dll")]
        private static extern IntPtr GetCurrentProcess();

        [DllImport("kernel32.dll")]
        private static extern IntPtr GetModuleHandle(string moduleName);

        [DllImport("kernel32")]
        private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

        [DllImport("kernel32.dll")]
        private static extern bool IsWow64Process(IntPtr hProcess, out bool wow64Process);

        public static string GetDescription()
        {
            PlatformID id = Environment.OSVersion.Platform;
            if (id == PlatformID.Win32NT      || id == PlatformID.Win32S ||
                id == PlatformID.Win32Windows || id == PlatformID.WinCE)
                return OperatingSystem.GetWindowsVersion();
            else if (id == PlatformID.MacOSX)
                return "macOS/Mac OS X";
            else
            {
                if (id == PlatformID.Unix && Environment.OSVersion.Version.Major >= 10)
                    return "macOS/Mac OS X";
                else
                    return OperatingSystem.GetUnixVersion();
            }
        }

        private static bool Is64BitOperatingSystem()
        {
            // Check if this process is natively an x64 process. If it is, it will only run on x64 environments, thus, the environment must be x64.
            if (IntPtr.Size == 8)
                return true;
            // Check if this process is an x86 process running on an x64 environment.
            IntPtr module_handler = GetModuleHandle("kernel32");
            if (module_handler != IntPtr.Zero)
            {
                IntPtr process_address = GetProcAddress(module_handler, "IsWow64Process");
                if (process_address != IntPtr.Zero)
                {
                    if (IsWow64Process(GetCurrentProcess(), out bool result) && result)
                        return true;
                }
            }
            // The environment must be an x86 environment.
            return false;
        }

        private static string GetHKLMString(string key, string value)
        {
            try
            {
                RegistryKey registry_key = Registry.LocalMachine.OpenSubKey(key);
                return registry_key?.GetValue(value).ToString() ?? string.Empty;
            }
            catch
            {
                return string.Empty;
            }
        }

        private static string GetWindowsVersion()
        {
            string os_arch;
            try
            {
                os_arch = OperatingSystem.Is64BitOperatingSystem() ? "64-bit" : "32-bit";
            }
            catch (Exception)
            {
                os_arch = "32/64-bit (Undetermined)";
            }
            string product_name  = GetHKLMString(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName");
            string release_id    = GetHKLMString(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ReleaseId");
            string csd_version   = GetHKLMString(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CSDVersion");
            string current_build = GetHKLMString(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentBuild");

            release_id    = string.IsNullOrEmpty(release_id)    ? string.Empty : " " + release_id;
            csd_version   = string.IsNullOrEmpty(csd_version)   ? string.Empty : " " + csd_version;
            current_build = string.IsNullOrEmpty(current_build) ? "Unknown" : current_build;

            return string.IsNullOrEmpty(product_name) == false ?
                         $"{product_name}{release_id}{csd_version} {os_arch} (OS Build {current_build})" :
                         "Unknown";
        }

        private static string GetUnixVersion()
        {
            string description    = "Unknown";
            string kernel_version = "Unknown";

            try
            {
                ProcessStartInfo process_start = new ProcessStartInfo("lsb_release", "-d")
                {
                    RedirectStandardOutput = true,
                    UseShellExecute = false,
                    StandardOutputEncoding = System.Text.Encoding.UTF8
                };

                Process lsb_release = Process.Start(process_start);

                process_start = new ProcessStartInfo("uname", "-r")
                {
                    RedirectStandardOutput = true,
                    UseShellExecute = false,
                    StandardOutputEncoding = System.Text.Encoding.UTF8
                };

                Process uname = Process.Start(process_start);

                lsb_release.WaitForExit();
                if (lsb_release.ExitCode == 0)
                    description = lsb_release.StandardOutput.ReadToEnd().Replace("Description:", string.Empty).Trim();

                uname.WaitForExit();
                if (uname.ExitCode == 0)
                    kernel_version = uname.StandardOutput.ReadToEnd().Trim();
            }
            catch
            {
                return "Unknown";
            }

            return $"{description} (Kernel {kernel_version})";
        }
    }
}