using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32;
using Mono.Unix;

namespace Saradomin.Utilities
{
    public static class CrossPlatform
    {
        public static void LaunchURL(string url)
        {
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                url = url.Replace("&", "^&");
                Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            {
                Process.Start("xdg-open", url);
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
            {
                Process.Start("open", url);
            }
        }

        public static void OpenFolder(string path)
        {
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            {
                Process.Start("xdg-open", path);
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
            {
                Process.Start("open", path);
            }
        }

        public static bool IsJavaExecutableValid(string location)
        {
            try
            {
                if (!File.Exists(location))
                {
                    return false;
                }

                using (var fileStream = File.OpenRead(location))
                {
                    var bytes = new byte[4];
                    fileStream.Read(bytes, 0, 4);

                    if (bytes[0] == 0x7F
                        && bytes[1] == 0x45
                        && bytes[2] == 0x4C
                        && bytes[3] == 0x46)
                    {
                        return RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
                    }

                    if (bytes[0] == 'M'
                        && bytes[1] == 'Z')
                    {
                        return RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
                    }

                    if ((bytes[0] == 0xCF
                        && bytes[1] == 0xFA)
                        || (bytes[0] == 0xCA
                        && bytes[1] == 0xFE))
                    {
                        return RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
                    }

                }
            }
            catch
            {
                // Ignore
            }
            
            return false;
        }

        public static string LocateJavaExecutable()
        {
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                var envPath = Environment.GetEnvironmentVariable("JAVA_HOME");

                if (!string.IsNullOrEmpty(envPath))
                    return Path.Combine(envPath, "bin/java.exe");
                
                using (var rk = Registry.LocalMachine.OpenSubKey("SOFTWARE\\JavaSoft\\Java Runtime Environment\\"))
                {
                    if (rk == null)
                        return null;

                    var currentVersion = rk.GetValue("CurrentVersion")?.ToString();

                    if (currentVersion == null)
                        return null;
                    
                    using (var key = rk.OpenSubKey(currentVersion))
                    {
                        if (key == null)
                            return null;
                        
                        envPath = key.GetValue("JavaHome")?.ToString();
                    }
                }

                if (!string.IsNullOrEmpty(envPath))
                    return Path.Combine(envPath, "bin/java.exe");

                throw new FileNotFoundException("Failed to find Java. Make sure it's installed!");
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
                     || RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD))
            {
                var proc = new Process
                {
                    StartInfo = new("/bin/which")
                    {
                        Arguments = "java",
                        RedirectStandardOutput = true,
                        UseShellExecute = false
                    }
                };

                proc.Start();
                proc.WaitForExit();
                var data = proc.StandardOutput.ReadToEnd();

                if (!string.IsNullOrEmpty(data))
                    return UnixPath.GetCompleteRealPath(data.Trim());

                throw new FileNotFoundException("Failed to find Java. Make sure it's installed!");
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
            {
                var proc = new Process
                {
                    StartInfo = new("/usr/bin/which")
                    {
                        Arguments = "java",
                        RedirectStandardOutput = true,
                        UseShellExecute = false
                    }
                };

                proc.Start();
                proc.WaitForExit();
                var data = proc.StandardOutput.ReadToEnd();

                if (!string.IsNullOrEmpty(data))
                    return Path.Combine(UnixPath.GetCompleteRealPath(data.Trim()));

                throw new FileNotFoundException("Failed to find Java. Make sure it's installed!");
            }
            else
            {
                throw new NotSupportedException("Your platform is not supported.");
            }
        }

        public static string LocateUnixUserHome()
        {
            return Environment.GetEnvironmentVariable("XDG_DATA_HOME")
                ?? Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".local",
                    "share"
                );
        }

        public static string Get2009scapeHome()
        {
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
                || RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD))
            {
                return Path.Combine(
                    LocateUnixUserHome(),
                    "EmoScape"
                );
            }
            else
            {
                var userProfile = Path.Combine (
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    "EmoScape"
                );
                var appData = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
                    "EmoScape"
                );
                return Directory.Exists(userProfile) ? userProfile : appData;
            }
        }

        public static string GetSaradominHome()
        {
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
                || RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD))
            {
                return Path.Combine(
                    // Get the XDG_DATA_HOME environment variable, or if it doesn't exist, use the default ~/.local/share
                    LocateUnixUserHome(),
                    "EmoScape",
                    "saradomin"
                );
            }

            return Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                "EmoScape",
                "saradomin"
            );
        }

        public static string GetSingleplayerBackupsHome()
        {
            return Path.Combine(Get2009scapeHome(), "singleplayer_backups");
        }

        public static string GetSingleplayerHome()
        {
            return Path.Combine(Get2009scapeHome(), "singleplayer");
        }

        public static string LocateSingleplayerExecutable()
        {
            return Path.Combine(GetSingleplayerHome(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "launch.bat" : "launch.sh");
        }
        
        public static string Get2009scapeExecutable()
        {
            return Path.Combine(Get2009scapeHome(), "2009scape.jar");
        }

        public static string GetServerProfilePath(string baseDirectory)
        {
            baseDirectory ??= Get2009scapeHome();
            return Path.Combine(baseDirectory, "server_profiles.json");
        }

        public static string RunCommandAndGetOutput(string command, Action<string> onOutputReceived = null, Action<string> onErrorReceived = null)
        {
            Process process = new Process();
            StringBuilder output = new StringBuilder();
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                process.StartInfo = new ProcessStartInfo("cmd.exe", "/c " + command)
                {
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    CreateNoWindow = true,
                };
            }
            else if (
                RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
                || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
            )
            {
                process.StartInfo = new ProcessStartInfo("bash", "-c \"" + command + "\"")
                {
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    CreateNoWindow = true,
                };
            }

            process.OutputDataReceived += (_, e) =>
            {
                if (e.Data == null) return;
                output.AppendLine(e.Data);
                onOutputReceived?.Invoke(e.Data);
            };

            process.ErrorDataReceived += (_, e) =>
            {
                if (e.Data == null) return; 
                output.AppendLine(e.Data);
                onErrorReceived?.Invoke(e.Data);
            };

            process.Start();

            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            process.WaitForExit();

            return output.ToString();
        }

        public static string GetJava11DownloadUrl()
        {
            string architecture = GetSystemArchitecture();
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                return "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.20%2B8/OpenJDK11U-jre_x64_windows_hotspot_11.0.20_8.zip";
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            {
                return architecture == "x64"
                    ? "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.20%2B8/OpenJDK11U-jre_x64_linux_hotspot_11.0.20_8.tar.gz"
                    : "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.20%2B8/OpenJDK11U-jre_aarch64_linux_hotspot_11.0.20_8.tar.gz";
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
            {
                return architecture == "x64"
                    ? "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.20%2B8/OpenJDK11U-jre_x64_mac_hotspot_11.0.20_8.tar.gz"
                    : "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.20%2B8/OpenJDK11U-jre_aarch64_mac_hotspot_11.0.20_8.tar.gz";
            }
            else
            {
                throw new NotSupportedException("Your platform is not supported.");
            }
        }

        private static string GetSystemArchitecture()
        {
            if (RuntimeInformation.OSArchitecture == Architecture.X64)
            {
                return "x64";
            }
            else if (RuntimeInformation.OSArchitecture == Architecture.Arm64)
            {
                return "aarch64";
            }
            else
            {
                throw new NotSupportedException("Your architecture is not supported.");
            }
        }
        public static bool IsDirectoryWritable(string directoryPath)
        {
            var testFilePath = Path.Combine(directoryPath, "test");

            try
            {
                File.Create(testFilePath).Dispose();
                File.Delete(testFilePath);

                return true;
            }
            catch (UnauthorizedAccessException)
            {
                return false;
            }
        }
        
        public static Process StartJavaProcess(string javaExecutable, string jarPath, string memoryAllocation, Action<string> outputHandler, Action onExit)
        {
            Process process = new Process();
            process.StartInfo = new ProcessStartInfo
            {
                FileName = javaExecutable,
                Arguments = $"-Xmx{memoryAllocation} -Xms{memoryAllocation} -jar \"{jarPath}\"",
                UseShellExecute = false,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                WorkingDirectory = Path.Combine(CrossPlatform.GetSingleplayerHome(), "game")
            };

            process.OutputDataReceived += (_, args) =>
            {
                if (string.IsNullOrEmpty(args.Data)) return;
                outputHandler?.Invoke(args.Data);
            };

            process.ErrorDataReceived += (_, args) =>
            {
                if (string.IsNullOrEmpty(args.Data)) return;
                outputHandler?.Invoke(args.Data);
            };

            process.EnableRaisingEvents = true;
            process.Exited += (_, _) => onExit?.Invoke();

            process.Start();

            process.BeginOutputReadLine();
            process.BeginErrorReadLine();
            return process;
        }
    }
}