using CefSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other;

#if DEBUG
using System.Diagnostics;
using System.Reflection;
using TweetDuck.Core;
using TweetDuck.Plugins;
#endif

namespace TweetDuck.Resources{
    static class ScriptLoader{
        private static readonly Dictionary<string, string> CachedData = new Dictionary<string, string>(16);

        public static string LoadResourceSilent(string name){
            return LoadResource(name, null);
        }

        public static string LoadResource(string name, Control sync){
            if (CachedData.TryGetValue(name, out string resourceData)){
                return resourceData;
            }

            string path = Program.ScriptPath;

            #if DEBUG
            if (Directory.Exists(HotSwapTargetDir)){
                path = Path.Combine(HotSwapTargetDir, "scripts");
                Debug.WriteLine("Hot swap active, redirecting "+name);
            }
            #endif

            string resource;

            try{
                string contents = File.ReadAllText(Path.Combine(path, name), Encoding.UTF8);
                int separator;

                // first line can be either:
                // #<version>\r\n
                // #<version>\n

                if (contents[0] != '#'){
                    ShowLoadError(sync, $"File {name} appears to be corrupted, please try reinstalling the app.");
                    separator = 0;
                }
                else{
                    separator = contents.IndexOf('\n');
                    string fileVersion = contents.Substring(1, separator-1).TrimEnd();

                    if (fileVersion != Program.VersionTag){
                        ShowLoadError(sync, $"File {name} is made for a different version of TweetDuck ({fileVersion}) and may not function correctly in this version, please try reinstalling the app.");
                    }
                }

                resource = contents.Substring(separator).TrimStart();
            }catch(Exception ex){
                ShowLoadError(sync, $"Could not load {name}. The program will continue running with limited functionality.\n\n{ex.Message}");
                resource = null;
            }

            return CachedData[name] = resource;
        }

        public static bool ExecuteFile(IFrame frame, string file, Control sync){
            string script = LoadResource(file, sync);
            ExecuteScript(frame, script, "root:"+Path.GetFileNameWithoutExtension(file));
            return script != null;
        }

        public static void ExecuteScript(IFrame frame, string script, string identifier){
            if (script != null){
                frame.ExecuteJavaScriptAsync(script, identifier, 1);
            }
        }

        private static void ShowLoadError(Control sync, string message){
            sync?.InvokeSafe(() => FormMessage.Error("Resource Error", message, FormMessage.OK));
        }
        
        #if DEBUG
        private static readonly string HotSwapProjectRoot = FixPathSlash(Path.GetFullPath(Path.Combine(Program.ProgramPath, "../../../")));
        private static readonly string HotSwapTargetDir = FixPathSlash(Path.Combine(HotSwapProjectRoot, "bin", "tmp"));
        private static readonly string HotSwapRebuildScript = Path.Combine(HotSwapProjectRoot, "bld", "post_build.exe");

        static ScriptLoader(){
            if (File.Exists(HotSwapRebuildScript)){
                Debug.WriteLine("Activating resource hot swap...");

                ResetHotSwap();
                Application.ApplicationExit += (sender, args) => ResetHotSwap();
            }
        }

        public static void HotSwap(){
            if (!File.Exists(HotSwapRebuildScript)){
                Debug.WriteLine("Failed resource hot swap, missing rebuild script: "+HotSwapRebuildScript);
                return;
            }
            
            ResetHotSwap();
            Directory.CreateDirectory(HotSwapTargetDir);

            Stopwatch sw = Stopwatch.StartNew();

            using(Process process = Process.Start(new ProcessStartInfo{
                FileName = HotSwapRebuildScript,
                Arguments = $"\"{HotSwapTargetDir}\\\" \"{HotSwapProjectRoot}\\\" \"Debug\" \"{Program.VersionTag}\"",
                WindowStyle = ProcessWindowStyle.Hidden
            })){
                // ReSharper disable once PossibleNullReferenceException
                if (!process.WaitForExit(8000)){
                    Debug.WriteLine("Failed resource hot swap, script did not finish in time");
                    return;
                }
                else if (process.ExitCode != 0){
                    Debug.WriteLine("Failed resource hot swap, script exited with code "+process.ExitCode);
                    return;
                }
            }

            sw.Stop();
            Debug.WriteLine("Finished rebuild script in "+sw.ElapsedMilliseconds+" ms");

            CachedData.Clear();

            // Force update plugin manager setup scripts

            string newPluginRoot = Path.Combine(HotSwapTargetDir, "plugins");
            
            const BindingFlags flagsInstance = BindingFlags.Instance | BindingFlags.NonPublic;

            Type typePluginManager = typeof(PluginManager);
            Type typeFormBrowser = typeof(FormBrowser);

            // ReSharper disable PossibleNullReferenceException
            object instPluginManager = typeFormBrowser.GetField("plugins", flagsInstance).GetValue(FormManager.TryFind<FormBrowser>());
            typePluginManager.GetField("rootPath", flagsInstance).SetValue(instPluginManager, newPluginRoot);

            Debug.WriteLine("Reloading hot swapped plugins...");
            ((PluginManager)instPluginManager).Reload();
            // ReSharper restore PossibleNullReferenceException
        }

        private static void ResetHotSwap(){
            try{
                Directory.Delete(HotSwapTargetDir, true);
            }catch(DirectoryNotFoundException){}
        }

        private static string FixPathSlash(string path){
            return path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)+'\\';
        }
        #endif
    }
}