// Copyright(c) 2019-2022 pypy, Natsumi and individual contributors.
// All rights reserved.
//
// This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>.

using CefSharp;
using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.Linq;
using System.Management;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.IO;
using System.Security.Cryptography;
using System.Net;
using Windows.UI.Notifications;
using Windows.Data.Xml.Dom;
using librsync.net;
using System.Net.Sockets;
using System.Text;
using System.Collections.Generic;
using System.Threading;
using System.IO.Pipes;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace VRCX
{
    public class AppApi
    {
        public static readonly AppApi Instance;

        static AppApi()
        {
            Instance = new AppApi();
        }

        public string MD5File(string Blob)
        {
            byte[] fileData = Convert.FromBase64CharArray(Blob.ToCharArray(), 0, Blob.Length);
            byte[] md5 = MD5.Create().ComputeHash(fileData);
            return System.Convert.ToBase64String(md5);
        }

        public string SignFile(string Blob)
        {
            byte[] fileData = Convert.FromBase64CharArray(Blob.ToCharArray(), 0, Blob.Length);
            Stream sig = Librsync.ComputeSignature(new MemoryStream(fileData));
            var memoryStream = new MemoryStream();
            sig.CopyTo(memoryStream);
            byte[] sigBytes = memoryStream.ToArray();
            return System.Convert.ToBase64String(sigBytes);
        }

        public string FileLength(string Blob)
        {
            byte[] fileData = Convert.FromBase64CharArray(Blob.ToCharArray(), 0, Blob.Length);
            return fileData.Length.ToString();
        }

        public string ReadConfigFile()
        {
            var logPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"Low\VRChat\VRChat\";
            var configFile = Path.Combine(logPath, @"config.json");
            if (!Directory.Exists(logPath) || !File.Exists(configFile))
            {
                return "";
            }
            var json = File.ReadAllText(configFile);
            return json;
        }

        public void WriteConfigFile(string json)
        {
            var logPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"Low\VRChat\VRChat\";
            var configFile = Path.Combine(logPath, @"config.json");
            File.WriteAllText(configFile, json);
        }

        public string GetVRChatAppDataLocation()
        {
            var json = ReadConfigFile();
            if (!string.IsNullOrEmpty(json))
            {
                var obj = JsonConvert.DeserializeObject<JObject>(json);
                if (obj["cache_directory"] != null)
                {
                    var cacheDir = (string)obj["cache_directory"];
                    if (!string.IsNullOrEmpty(cacheDir) && Directory.Exists(cacheDir))
                    {
                        return cacheDir;
                    }
                }
            }
            var cachePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"Low\VRChat\VRChat";
            return cachePath;
        }

        public string GetVRChatCacheLocation()
        {
            return Path.Combine(GetVRChatAppDataLocation(), "Cache-WindowsPlayer");
        }

        public void ShowDevTools()
        {
            MainForm.Instance.Browser.ShowDevTools();
        }

        public void DeleteAllCookies()
        {
            Cef.GetGlobalCookieManager().DeleteCookies();
        }

        public bool[] CheckGameRunning()
        {
            var isGameRunning = false;
            var isSteamVRRunning = false;

            if (Process.GetProcessesByName("vrchat").Length > 0)
            {
                isGameRunning = true;
            }
            if (Process.GetProcessesByName("vrserver").Length > 0)
            {
                isSteamVRRunning = true;
            }

            return new bool[]
            {
                isGameRunning,
                isSteamVRRunning
            };
        }

        public int QuitGame()
        {
            var processes = Process.GetProcessesByName("vrchat");
            if (processes.Length == 1)
                processes[0].Kill();

            return processes.Length;
        }

        public void StartGame(string arguments)
        {
            // try stream first
            try
            {
                using (var key = Registry.ClassesRoot.OpenSubKey(@"steam\shell\open\command"))
                {
                    // "C:\Program Files (x86)\Steam\steam.exe" -- "%1"
                    var match = Regex.Match(key.GetValue(string.Empty) as string, "^\"(.+?)\\\\steam.exe\"");
                    if (match.Success == true)
                    {
                        var path = match.Groups[1].Value;
                        // var _arguments = Uri.EscapeDataString(arguments);
                        Process.Start(new ProcessStartInfo
                        {
                            WorkingDirectory = path,
                            FileName = $"{path}\\steam.exe",
                            UseShellExecute = false,
                            Arguments = $"-applaunch 438100 {arguments}"
                        }).Close();
                        return;
                    }
                }
            }
            catch
            {
            }

            // fallback
            try
            {
                using (var key = Registry.ClassesRoot.OpenSubKey(@"VRChat\shell\open\command"))
                {
                    // "C:\Program Files (x86)\Steam\steamapps\common\VRChat\launch.exe" "%1" %*
                    var match = Regex.Match(key.GetValue(string.Empty) as string, "(?!\")(.+?\\\\VRChat.*)(!?\\\\launch.exe\")");
                    if (match.Success == true)
                    {
                        var path = match.Groups[1].Value;
                        StartGameFromPath(path, arguments);
                    }
                }
            }
            catch
            {
            }
        }

        public void StartGameFromPath(string path, string arguments)
        {
            if (!path.EndsWith(".exe"))
                path = Path.Combine(path, "start_protected_game.exe");

            Process.Start(new ProcessStartInfo
            {
                WorkingDirectory = Path.GetDirectoryName(path),
                FileName = path,
                UseShellExecute = false,
                Arguments = arguments
            }).Close();
        }

        public void OpenLink(string url)
        {
            if (url.StartsWith("http://") == true ||
                url.StartsWith("https://") == true)
            {
                Process.Start(url).Close();
            }
        }

        public void ShowVRForm()
        {
            try
            {
                MainForm.Instance.BeginInvoke(new MethodInvoker(() =>
                {
                    if (VRForm.Instance == null)
                    {
                        new VRForm().Show();
                    }
                }));
            }
            catch
            {
            }
        }

        public void SetVR(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand)
        {
            VRCXVR.Instance.SetActive(active, hmdOverlay, wristOverlay, menuButton, overlayHand);
        }

        public void RefreshVR()
        {
            VRCXVR.Instance.Refresh();
        }

        public string[][] GetVRDevices()
        {
            return VRCXVR.Instance.GetDevices();
        }

        public float CpuUsage()
        {
            return CpuMonitor.Instance.CpuUsage;
        }

        public string GetImage(string url, string fileId, string version)
        {
            return ImageCache.GetImage(url, fileId, version);
        }

        public void DesktopNotification(string BoldText, string Text, string Image)
        {
            XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastImageAndText02);
            XmlNodeList stringElements = toastXml.GetElementsByTagName("text");
            String imagePath = Path.Combine(Program.BaseDirectory, "VRCX.ico");
            if (!String.IsNullOrEmpty(Image))
            {
                imagePath = Image;
            }
            stringElements[0].AppendChild(toastXml.CreateTextNode(BoldText));
            stringElements[1].AppendChild(toastXml.CreateTextNode(Text));
            XmlNodeList imageElements = toastXml.GetElementsByTagName("image");
            imageElements[0].Attributes.GetNamedItem("src").NodeValue = imagePath;
            ToastNotification toast = new ToastNotification(toastXml);
            ToastNotificationManager.CreateToastNotifier("VRCX").Show(toast);
        }

        private struct XSOMessage
        {
            public int messageType { get; set; }
            public int index { get; set; }
            public float volume { get; set; }
            public string audioPath { get; set; }
            public float timeout { get; set; }
            public string title { get; set; }
            public string content { get; set; }
            public string icon { get; set; }
            public float height { get; set; }
            public float opacity { get; set; }
            public bool useBase64Icon { get; set; }
            public string sourceApp { get; set; }
        }

        public void XSNotification(string Title, string Content, int Timeout, string Image)
        {
            bool UseBase64Icon;
            string Icon;
            if (String.IsNullOrEmpty(Image))
            {
                UseBase64Icon = true;
                Icon = "[encoded data, 4424 characters]";
            }
            else
            {
                UseBase64Icon = false;
                Icon = Image;
            }

            IPAddress broadcastIP = IPAddress.Parse("127.0.0.1");
            Socket broadcastSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            IPEndPoint endPoint = new IPEndPoint(broadcastIP, 42069);

            XSOMessage msg = new XSOMessage();
            msg.messageType = 1;
            msg.title = Title;
            msg.content = Content;
            msg.height = 110f;
            msg.sourceApp = "VRCX";
            msg.timeout = Timeout;
            msg.audioPath = "";
            msg.useBase64Icon = UseBase64Icon;
            msg.icon = Icon;

            byte[] byteBuffer = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(msg);
            broadcastSocket.SendTo(byteBuffer, endPoint);
        }

        public void DownloadVRCXUpdate(string url)
        {
            var Location = Path.Combine(Program.AppDataDirectory, "update.exe");
            WebClient client = new WebClient();
            client.Headers.Add("user-agent", Program.Version);
            client.DownloadFile(new Uri(url), Location);
        }

        public void RestartApplication()
        {
            Process VRCXProcess = new Process();
            VRCXProcess.StartInfo.FileName = Path.Combine(Program.BaseDirectory, "VRCX.exe");
            VRCXProcess.StartInfo.UseShellExecute = false;
            VRCXProcess.StartInfo.Arguments = "/Upgrade";
            VRCXProcess.Start();
            Environment.Exit(0);
        }

        public bool CheckForUpdateExe()
        {
            if (File.Exists(Path.Combine(Program.AppDataDirectory, "update.exe")))
                return true;
            return false;
        }

        public void IPCAnnounceStart()
        {
            var ipcClient = new NamedPipeClientStream(".", "vrcx-ipc", PipeDirection.InOut);
            ipcClient.Connect();
            if (!ipcClient.IsConnected)
                return;
            var buffer = Encoding.UTF8.GetBytes($"{{\"type\":\"VRCXLaunch\"}}" + (char)0x00);
            ipcClient.BeginWrite(buffer, 0, buffer.Length, IPCClient.OnSend, ipcClient);
        }

        public void ExecuteAppFunction(string function, string json)
        {
            if (MainForm.Instance != null)
                MainForm.Instance.Browser.ExecuteScriptAsync($"$app.{function}", json);
        }

        public void ExecuteVrFeedFunction(string function, string json)
        {
            if (VRCXVR._browser1 != null)
                VRCXVR._browser1.ExecuteScriptAsync($"$app.{function}", json);
        }

        public void ExecuteVrOverlayFunction(string function, string json)
        {
            if (VRCXVR._browser2 != null)
                VRCXVR._browser2.ExecuteScriptAsync($"$app.{function}", json);
        }

        public string GetLaunchCommand()
        {
            string command = StartupArgs.LaunchCommand;
            StartupArgs.LaunchCommand = string.Empty;
            return command;
        }

        public void FocusWindow()
        {
            MainForm.Instance.Invoke(new Action(() =>
            {
                MainForm.Instance.Focus_Window();
            }));
        }

        public string CustomCssPath()
        {
            var output = String.Empty;
            var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "VRCX\\custom.css");
            if (File.Exists(filePath))
                output = filePath;
            return output;
        }

        public string CustomScriptPath()
        {
            var output = String.Empty;
            var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "VRCX\\custom.js");
            if (File.Exists(filePath))
                output = filePath;
            return output;
        }

        public string CurrentCulture()
        {
            return System.Globalization.CultureInfo.CurrentCulture.ToString();
        }

        public string GetVersion()
        {
            return Program.Version;
        }

        public void ChangeTheme(int value)
        {
            WinformThemer.SetGlobalTheme(value);
        }

        public void DoFunny()
        {
            WinformThemer.DoFunny();
        }

        public double GetUptime()
        {
            using (var uptime = new PerformanceCounter("System", "System Up Time"))
            {
                uptime.NextValue();
                return TimeSpan.FromSeconds(uptime.NextValue()).TotalMilliseconds;
            }
        }

        private static readonly MD5 _hasher = MD5.Create();

        public int GetColourFromUserID(string userId)
        {
            var hash = _hasher.ComputeHash(Encoding.UTF8.GetBytes(userId));
            return hash[3] << 8 | hash[4];
        }

        public Dictionary<string, int> GetColourBulk(List<Object> userIds)
        {
            Dictionary<string, int> output = new Dictionary<string, int>();
            foreach (string userId in userIds)
            {
                output.Add(userId, GetColourFromUserID(userId));
            }
            return output;
        }

        public string GetClipboard()
        {
            var clipboard = String.Empty;
            Thread thread = new Thread(() => clipboard = Clipboard.GetText());
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
            thread.Join();
            return clipboard;
        }

        public object GetVRChatRegistryKey(string key)
        {
            // https://answers.unity.com/questions/177945/playerprefs-changing-the-name-of-keys.html?childToView=208076#answer-208076
            // VRC_GROUP_ORDER_usr_032383a7-748c-4fb2-94e4-bcb928e5de6b_h2810492971
            uint hash = 5381;
            foreach (char c in key)
                hash = hash * 33 ^ c;
            var keyName = key + "_h" + hash;

            using (var regKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\VRChat\VRChat"))
            {
                var data = regKey?.GetValue(keyName);
                if (data == null)
                    return null;

                var type = regKey.GetValueKind(keyName);
                switch (type)
                {
                    case RegistryValueKind.Binary:
                        return Encoding.ASCII.GetString((byte[])data);

                    case RegistryValueKind.DWord:
                        return data;
                }
            }

            return null;
        }

        public bool SetVRChatRegistryKey(string key, string value)
        {
            uint hash = 5381;
            foreach (char c in key)
                hash = hash * 33 ^ c;
            var keyName = key + "_h" + hash;

            using (var regKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\VRChat\VRChat", true))
            {
                if (regKey?.GetValue(keyName) == null)
                    return false;

                var type = regKey.GetValueKind(keyName);
                object setValue = null;
                switch (type)
                {
                    case RegistryValueKind.Binary:
                        setValue = Encoding.ASCII.GetBytes(value);
                        break;

                    case RegistryValueKind.DWord:
                        setValue = value;
                        break;
                }
                if (setValue == null)
                    return false;

                regKey.SetValue(keyName, setValue, type);
            }
            return true;
        }

        public Dictionary<string, short> GetVRChatModerations(string currentUserId)
        {
            var filePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + $@"Low\VRChat\VRChat\LocalPlayerModerations\{currentUserId}-show-hide-user.vrcset";
            if (!File.Exists(filePath))
                return null;

            var output = new Dictionary<string, short>();
            using (var reader = new StreamReader(filePath))
            {
                string line;
                int index;
                string userId;
                short type;
                while ((line = reader.ReadLine()) != null)
                {
                    index = line.IndexOf(' ');
                    if (index <= 0)
                        continue;

                    userId = line.Substring(0, index);
                    type = short.Parse(line.Substring(line.Length - 3));
                    output.Add(userId, type);
                }
            }
            return output;
        }

        public short GetVRChatUserModeration(string currentUserId, string userId)
        {
            var filePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + $@"Low\VRChat\VRChat\LocalPlayerModerations\{currentUserId}-show-hide-user.vrcset";
            if (!File.Exists(filePath))
                return 0;

            using (var reader = new StreamReader(filePath))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    var index = line.IndexOf(' ');
                    if (index <= 0)
                        continue;

                    if (userId == line.Substring(0, index))
                    {
                        return short.Parse(line.Substring(line.Length - 3));
                    }
                }
            }
            return 0;
        }

        public bool SetVRChatUserModeration(string currentUserId, string userId, int type)
        {
            var filePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + $@"Low\VRChat\VRChat\LocalPlayerModerations\{currentUserId}-show-hide-user.vrcset";
            if (!File.Exists(filePath))
                return false;

            var lines = File.ReadAllLines(filePath).ToList();
            var index = lines.FindIndex(x => x.StartsWith(userId));
            if (index >= 0)
                lines.RemoveAt(index);

            if (type != 0)
            {
                var sb = new StringBuilder(userId);
                while (sb.Length < 64)
                    sb.Append(' ');

                sb.Append(type.ToString("000"));
                lines.Add(sb.ToString());
            }
            try
            {
                File.WriteAllLines(filePath, lines);
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        public void SetStartup(bool enabled)
        {
            try
            {
                using (var key = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true))
                {
                    if (enabled == true)
                    {
                        var path = Application.ExecutablePath;
                        key.SetValue("VRCX", $"\"{path}\" --startup");
                    }
                    else
                    {
                        key.DeleteValue("VRCX", false);
                    }
                }
            }
            catch
            {
            }
        }
    }
}