using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using MinecraftClient.CommandHandler;
using MinecraftClient.Scripting;

namespace MinecraftClient.ChatBots
{
    /// <summary>
    /// Runs a list of commands
    /// </summary>

    public class Script : ChatBot
    {
        private string? file;
        private string[] lines = Array.Empty<string>();
        private string[] args = Array.Empty<string>();
        private int sleepticks = 10;
        private int nextline = 0;
        private readonly string? owner;
        private bool csharp;
        private Thread? thread;
        private readonly Dictionary<string, object>? localVars;

        public Script(string filename)
        {
            ParseArguments(filename);
        }

        public Script(string filename, string? ownername, Dictionary<string, object>? localVars)
            : this(filename)
        {
            owner = ownername;
            this.localVars = localVars;
        }

        private void ParseArguments(string argstr)
        {
            List<string> args = new();
            StringBuilder str = new();

            bool escape = false;
            bool quotes = false;

            foreach (char c in argstr)
            {
                if (escape)
                {
                    if (c != '"')
                        str.Append('\\');
                    str.Append(c);
                    escape = false;
                }
                else
                {
                    if (c == '\\')
                        escape = true;
                    else if (c == '"')
                        quotes = !quotes;
                    else if (c == ' ' && !quotes)
                    {
                        if (str.Length > 0)
                            args.Add(str.ToString());
                        str.Clear();
                    }
                    else str.Append(c);
                }
            }

            if (str.Length > 0)
                args.Add(str.ToString());

            if (args.Count > 0)
            {
                file = args[0];
                args.RemoveAt(0);
                this.args = args.ToArray();
            }
            else file = "";
        }

        public static bool LookForScript(ref string filename)
        {
            //Automatically look in subfolders and try to add ".txt" file extension
             char dir_slash = Path.DirectorySeparatorChar;
            string[] files = new string[]
            {
                filename,
                filename + ".txt",
                filename + ".cs",
                "scripts" + dir_slash + filename,
                "scripts" + dir_slash + filename + ".txt",
                "scripts" + dir_slash + filename + ".cs",
                "config" + dir_slash + filename,
                "config" + dir_slash + filename + ".txt",
                "config" + dir_slash + filename + ".cs",
            };

            foreach (string possible_file in files)
            {
                if (System.IO.File.Exists(possible_file))
                {
                    filename = possible_file;
                    return true;
                }
            }

            if (Settings.Config.Logging.DebugMessages)
            {
                string caller = "Script";
                try
                {
                    StackFrame frame = new(1);
                    MethodBase method = frame.GetMethod()!;
                    Type type = method.DeclaringType!;
                    caller = type.Name;
                }
                catch { }
                ConsoleIO.WriteLineFormatted(string.Format(Translations.bot_script_not_found, caller, filename));
            }

            return false;
        }

        public override void Initialize()
        {
            //Load the given file from the startup parameters
            if (LookForScript(ref file!))
            {
                lines = System.IO.File.ReadAllLines(file, Encoding.UTF8);
                csharp = file.EndsWith(".cs");
                thread = null;

                if (!String.IsNullOrEmpty(owner))
                    SendPrivateMessage(owner, string.Format(Translations.bot_script_pm_loaded, file));
            }
            else
            {
                LogToConsole(string.Format(Translations.bot_script_file_not_found, System.IO.Path.GetFullPath(file)));

                if (!String.IsNullOrEmpty(owner))
                    SendPrivateMessage(owner, string.Format(Translations.bot_script_file_not_found, file));

                UnloadBot(); //No need to keep the bot active
            }
        }

        public override void Update()
        {
            if (csharp) //C# compiled script
            {
                //Initialize thread on first update
                if (thread == null)
                {
                    thread = new Thread(() =>
                    {
                        try
                        {
                            CSharpRunner.Run(this, lines, args, localVars, scriptName: file!);
                        }
                        catch (CSharpException e)
                        {
                            string errorMessage = string.Format(Translations.bot_script_fail, file, e.ExceptionType);
                            LogToConsole(errorMessage);
                            if (owner != null)
                                SendPrivateMessage(owner, errorMessage);
                            LogToConsole(e.InnerException);
                        }
                    })
                    {
                        Name = "MCC Script - " + file
                    };
                    thread.Start();
                }

                //Unload bot once the thread has finished running
                if (thread != null && !thread.IsAlive)
                {
                    UnloadBot();
                }
            }
            else //Classic MCC script interpreter
            {
                if (sleepticks > 0) { sleepticks--; }
                else
                {
                    if (nextline < lines.Length) //Is there an instruction left to interpret?
                    {
                        string instruction_line = lines[nextline].Trim(); // Removes all whitespaces at start and end of current line
                        nextline++; //Move the cursor so that the next time the following line will be interpreted

                        if (instruction_line.Length > 1)
                        {
                            if (instruction_line[0] != '#' && instruction_line[0] != '/' && instruction_line[1] != '/')
                            {
                                instruction_line = Settings.Config.AppVar.ExpandVars(instruction_line, localVars);
                                string instruction_name = instruction_line.Split(' ')[0];
                                switch (instruction_name.ToLower())
                                {
                                    case "wait":
                                        int ticks = 10;
                                        try
                                        {
                                            ticks = Convert.ToInt32(instruction_line[5..]);
                                        }
                                        catch { }
                                        sleepticks = ticks;
                                        break;
                                    default:
                                        CmdResult response = new();
                                        if (PerformInternalCommand(instruction_line, ref response))
                                        {
                                            if (instruction_name.ToLower() != "log")
                                            {
                                                LogToConsole(instruction_line);
                                            }
                                            if (response.status != CmdResult.Status.Done || !string.IsNullOrWhiteSpace(response.result))
                                            {
                                                LogToConsole(response);
                                            }
                                        }
                                        else
                                        {
                                            Update(); //Unknown command : process next line immediately
                                        }
                                        break;
                                }
                            }
                            else { Update(); } //Comment: process next line immediately
                        }
                    }
                    else
                    {
                        //No more instructions to interpret
                        UnloadBot();
                    }
                }
            }
        }
    }
}
