using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Threading;
using UnityEngine;
using UnityEngine.Networking;

namespace bessw.Unity.WebView.ChromeDevTools
{
    public class Browser
    {
        /* singleton */
        private static Browser instance;
        private Process browserProcess;

        /* browser settings */
        public static string browserExecutablePath = "chrome";
        public static bool headless = true;
        private const int debugPort = 9222;

        /// <summary>
        /// Json serializer for the devtools connection
        /// </summary>
        public static JsonSerializer devtoolsSerializer;
        /// <summary>
        /// Json serializer settings for the devtools connection
        /// </summary>
        public static JsonSerializerSettings devtoolsSerializerSettings;

        public static CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

        /**
         * get singleton instance
         */
        public static Browser getInstance()
        {
            if (instance == null || instance.browserProcess.HasExited)
            {
                instance = new Browser();
                instance.launchBrowser();
            }
            return instance;
        }

        /**
         * constructor for the static fields
         */
        static Browser()
        {
            // initialize the JsonSerializer
            devtoolsSerializerSettings = new JsonSerializerSettings
            {
                ContractResolver = new CamelCasePropertyNamesContractResolver(),
            };
            devtoolsSerializer = JsonSerializer.Create(devtoolsSerializerSettings);
        }

        /**
        * Launch headless chrome browser with remote-debugging enabled, if not already running.
        */
        private  void launchBrowser()
        {
            // allow only one instance of chrome
            if (browserProcess == null || browserProcess.HasExited)
            {
                browserProcess = new Process();
                browserProcess.StartInfo.FileName = browserExecutablePath;
                browserProcess.StartInfo.Arguments = String.Join(" ", new []{
                    $"--user-data-dir={Path.Join(Application.temporaryCachePath, "BrowserView")}",
                    $"--remote-debugging-port={debugPort}",
                    $"--remote-allow-origins=http://localhost:{debugPort}",
                    "--hide-crash-restore-bubble",
                    "--disable-first-run-ui",
                    "--no-first-run"
                });

                // set headlessBrowser to false to see the browser window
                if (headless)
                {
                    browserProcess.StartInfo.Arguments = string.Concat(browserProcess.StartInfo.Arguments, " --headless=new");
                }
                else
                {
                    browserProcess.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
                }

                // register an error handler
                browserProcess.ErrorDataReceived += (sender, e) => UnityEngine.Debug.LogError($"Browser Error: {e.Data} ExitCode: {browserProcess.ExitCode}");
                browserProcess.Exited += (sender, e) => UnityEngine.Debug.LogError($"Browser Exited, ExitCode: {browserProcess.ExitCode}");

                browserProcess.Start();
                if (browserProcess.HasExited) {
                    UnityEngine.Debug.LogError("Failed to start browser");
                }
                UnityEngine.Debug.Log($"launched '{browserProcess.StartInfo.FileName} {browserProcess.StartInfo.Arguments}'");
            }
        }

        /**
         * send web request to the devTools API
         */
        private IEnumerator DevToolsApiRequest(bool isPUT, string apiAddress, Action<string> callback)
        {
            UnityEngine.Debug.Log($"DevTools api Request: {apiAddress}");
            UnityWebRequest webRequest;
            if (isPUT)
            {
                webRequest = UnityWebRequest.Put($"http://localhost:{debugPort}{apiAddress}", "");
            }
            else
            {
                webRequest = UnityWebRequest.Get($"http://localhost:{debugPort}{apiAddress}");
            }
            yield return webRequest.SendWebRequest();

            if (webRequest.result != UnityWebRequest.Result.Success)
            {
                UnityEngine.Debug.LogError(webRequest.error);
                //TODO: handle error
            }
            else
            {
                UnityEngine.Debug.Log($"DevTools api response (for {apiAddress}):\n{webRequest.downloadHandler.text}");
                callback(webRequest.downloadHandler.text);
            }
        }

        public IEnumerator OpenNewTab(string targetUrl, Action<BrowserTab> callback)
        {
            yield return DevToolsApiRequest(true, $"/json/new?{targetUrl}", (response) =>
            {
                PageTargetInfo pageTarget = JsonConvert.DeserializeObject<PageTargetInfo>(response, devtoolsSerializerSettings);
                callback(new BrowserTab(pageTarget));

            });
        }

        /**
         * Not implemented.
         */
        [Obsolete("Not implemented.", true)]
        private IEnumerator GetOpenTabs()
        {
            yield return DevToolsApiRequest(false, "/json/list", (response) =>
            {
                UnityEngine.Debug.Log($"Currently open tabs:\n{response}");
            });
        }

        /**
         * Close the browser
         */
        public void Close()
        {
            cancellationTokenSource.Cancel();
            if (browserProcess != null && !browserProcess.HasExited)
            {
                browserProcess.Kill();
            }
        }
    }
}