using ProcessWrapper;
using Func;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.IO;
using Photino.NET;
using System.Text.Json;

namespace ModMenu {
    public class Program {
        public static int pid = 0;
        public static int OsType = 0;
        public static IProcessWrapper OsProcessWrapper = null;
        public static ulong cbase = 0;
        public static ulong cbaseCorrected = 0;
        public static Dictionary<string, string> config = null;

        public static byte[] readBytes(uint address, uint length) {
            return OsProcessWrapper.ReadProcessMemory(pid, (ulong)(cbase + 0xE000000) + address - 0x10000000, length);
        }

        public static uint readUInt32(uint address) {
            return BitConverter.ToUInt32(OsProcessWrapper.ReadProcessMemory(pid, (ulong)(cbase + 0xE000000) + address - 0x10000000, 4).Reverse().ToArray(), 0);
        }

        public static float readFloat(uint address) {
            var b = OsProcessWrapper.ReadProcessMemory(pid, (ulong)(cbase + 0xE000000) + address - 0x10000000, 4);
            Array.Reverse(b, 0, b.Length);
            return BitConverter.ToSingle(b, 0);
        }

        public static void writeBytes(uint address, byte[] bytes) {
            OsProcessWrapper.WriteProcessMemory(pid, (ulong)(cbase + 0xE000000) + address - 0x10000000, bytes);
        }

        public static void writeUInt32(uint address, uint value) {
            byte[] b = BitConverter.GetBytes(value);
            Array.Reverse(b, 0, b.Length);
            OsProcessWrapper.WriteProcessMemory(pid, (ulong)(cbase + 0xE000000) + address - 0x10000000, b);
        }

        public static void writeInt16(uint address, Int16 value) {
            byte[] b = BitConverter.GetBytes(value);
            Array.Reverse(b, 0, b.Length);
            OsProcessWrapper.WriteProcessMemory(pid, (ulong)(cbase + 0xE000000) + address - 0x10000000, b);
        }

        public static void writeFloat(uint address, float value) {
            byte[] b = BitConverter.GetBytes(value);
            Array.Reverse(b, 0, b.Length);
            OsProcessWrapper.WriteProcessMemory(pid, (ulong)(cbase + 0xE000000) + address - 0x10000000, b);
        }

        public static string FindExecutable(string name) {
            // Not required for Mac/Windows
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return null;
            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return null;

            var pathEnv = Environment.GetEnvironmentVariable("PATH");
            if (string.IsNullOrEmpty(pathEnv)) return null;
            var paths = pathEnv.Split(':');

            foreach (var path in paths) {
                var fullPath = Path.Combine(path, name);
                if (File.Exists(fullPath)) {
                    return fullPath;
                }
            }

            return null;
        }

        public static bool IsWayland() {
            string runtimeDir = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR");
            if (string.IsNullOrEmpty(runtimeDir) || !Directory.Exists(runtimeDir)) return false;

            return Directory.EnumerateFiles(runtimeDir, "wayland-*").Any();
        }

        [STAThread]
        public static void Main(string[] args) {
            Console.OutputEncoding = System.Text.Encoding.UTF8;

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
                OsType = 1;
                OsProcessWrapper = new LinuxProcessWrapper();
                if (OsProcessWrapper.getuid() != 0) {
                    var pkexecPath = FindExecutable("pkexec");

                    if (!string.IsNullOrEmpty(pkexecPath)) {
                        string exePath = Process.GetCurrentProcess().MainModule.FileName;
                        string display;
                        string runtimeDir;
                        string command = null;

                        Console.WriteLine($"Wayland is: {IsWayland()}");

                        if (IsWayland()) {
                            display = Environment.GetEnvironmentVariable("WAYLAND_DISPLAY");
                            runtimeDir = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR");
                            command = $"--disable-internal-agent env XDG_RUNTIME_DIR={runtimeDir} WAYLAND_DISPLAY={display} \"{exePath}\"";
                        } else {
                            display = Environment.GetEnvironmentVariable("DISPLAY");
                            command = $"--disable-internal-agent env DISPLAY={display} \"{exePath}\"";
                        }

                        if (string.IsNullOrEmpty(command)) {
                            Console.WriteLine("Please run as root...");
                            System.Environment.Exit(1);
                        }

                        var startInfo = new ProcessStartInfo {
                            FileName = pkexecPath,
                            Arguments = command,
                            UseShellExecute = false
                        };

                        try {
                            using (Process elevatedProcess = Process.Start(startInfo)) {
                                elevatedProcess?.WaitForExit();
                                Environment.Exit(elevatedProcess?.ExitCode ?? 0);
                            };
                        } catch (Exception ex) {
                            Console.WriteLine($"Elevation failed: {ex.Message}");
                            Environment.Exit(1);
                        }
                    } else {
                        Console.WriteLine("Please run as root...");
                        System.Environment.Exit(1);
                    }
                }
            } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
                OsType = 2;
                OsProcessWrapper = new MacProcessWrapper();
                if (OsProcessWrapper.getuid() != 0) {
                    string basePath = AppContext.BaseDirectory;
                    string rootHelperPath = Path.Combine(basePath, "root_helper");

                    if (Path.Exists(rootHelperPath)) {
                        var startInfo = new ProcessStartInfo {
                            FileName = rootHelperPath,
                            UseShellExecute = false
                        };

                        try {
                            Process.Start(startInfo);
                            Environment.Exit(0);
                        } catch (Exception ex) {
                            Console.WriteLine($"Elevation failed: {ex.Message}");
                            Environment.Exit(1);
                        }
                    } else {
                        Console.WriteLine("Please run as root...");
                        System.Environment.Exit(1);
                    }
                }
            } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
                OsType = 3;
                OsProcessWrapper = new WindowsProcessWrapper();
            } else {
                Console.WriteLine("Unsupported Operating System exiting...");
                return;
            }

            // Root access needed past here for Linux/Mac
            string[] targetNames = { "cemu", "xapfish", ".cemu-wrapped", "cemu_release" };
            Process targetProcess = Process.GetProcesses()
                .FirstOrDefault(p => targetNames.Any(name => p.ProcessName.Equals(name, StringComparison.OrdinalIgnoreCase)));

            if (targetProcess == null) {
                new PhotinoWindow()
                    .SetSize(0, 0)
                    .SetMinimized(true)
                    .SetNotificationRegistrationId("8FDF1B15-3408-47A6-8EF5-2B0676B76277")
                    .SetNotificationsEnabled(false)
                    .RegisterWindowCreatedHandler((sender, e) => {
                        var window = sender as PhotinoWindow;
                        window.ShowMessage("Error", "Could not find Cemu process.", PhotinoDialogButtons.Ok, PhotinoDialogIcon.Error);
                        window.Close();
                        System.Environment.Exit(0);
                    })
                    .LoadRawString("<html></html>")
                    .WaitForClose();
                return;
            }
            pid = targetProcess.Id;
            cbase = OsProcessWrapper.FindCemuBase(pid, 1308622848);
            Console.WriteLine($"Found target process: {targetProcess.ProcessName} (PID: {pid})");

            if (cbase != 0) {
                Console.WriteLine("SplatTool v3.2.1 by CrafterPika");
                Console.WriteLine("Special Thanks: Winterberry, Javi.ig and Tombuntu, KittenTM.");

                cbaseCorrected = (Program.cbase + 0xE000000) - 0x10000000;

                string indexPath; 
                string iconPath;
                string configPath;

                if (OsType == 2) {
                    indexPath = Path.Combine(AppContext.BaseDirectory, "..", "Resources", "modmenu", "index.html");
                    iconPath = Path.Combine(AppContext.BaseDirectory, "..", "Resources", "icon.ico");
                    configPath = Path.Combine(AppContext.BaseDirectory, "..", "Resources", "config.json");
                } else {
                    indexPath = Path.Combine(AppContext.BaseDirectory, "modmenu", "index.html");
                    iconPath = Path.Combine(AppContext.BaseDirectory, "icon.ico");
                    configPath = Path.Combine(AppContext.BaseDirectory, "config.json");
                }

                string configJson = File.ReadAllText(configPath);
                config = JsonSerializer.Deserialize<Dictionary<string, string>>(configJson);

                var window = new PhotinoWindow()
                    .SetTitle("SplatTool Mod Menu")
                    .SetNotificationRegistrationId("8FDF1B15-3408-47A6-8EF5-2B0676B76277")
                    .SetNotificationsEnabled(false)
                    .SetUseOsDefaultSize(false)
                    .SetSize(900, 700)
                    .Center()
                    .RegisterWebMessageReceivedHandler((object sender, string message) => {
                        var w = sender as PhotinoWindow;
                        var data = JsonSerializer.Deserialize<ModMenuRequest>(message);
                        if (data != null) {
                            string result = httpMenu.RunCode(data);
                            w.SendWebMessage(result);
                        }
                    });

                if (File.Exists(iconPath)) {
                    window.SetIconFile(iconPath);
                }

                window.Load(indexPath).WaitForClose();

            } else {
                new PhotinoWindow()
                    .SetSize(0, 0)
                    .SetNotificationRegistrationId("8FDF1B15-3408-47A6-8EF5-2B0676B76277")
                    .SetNotificationsEnabled(false)
                    .SetMinimized(true)
                    .RegisterWindowCreatedHandler((sender, e) => {
                        var window = sender as PhotinoWindow;
                        window.ShowMessage("Error", "Cemu base not found! Is Splatoon running?!", PhotinoDialogButtons.Ok, PhotinoDialogIcon.Error);
                        window.Close();
                        System.Environment.Exit(0);
                    })
                    .LoadRawString("<html></html>")
                    .WaitForClose();
                return;
            }
        }
    }
}
