using Harmony;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection.Emit;
using System.Text;

namespace IPA.Logging
{
    internal class StdoutInterceptor : TextWriter
    {
        public override Encoding Encoding => Encoding.Default;

        private bool isStdErr;

        public override void Write(char value)
        {
            Write(value.ToString());
        }

        private string lineBuffer = "";
        private object bufferLock = new object();

        public override void Write(string value)
        {
            lock (bufferLock)
            { // avoid threading issues
                lineBuffer += value;

                var parts = lineBuffer.Split(new[] { Environment.NewLine, "\n", "\r" }, StringSplitOptions.None);
                for (int i = 0; i < parts.Length; i++)
                {
                    if (i + 1 == parts.Length) // last element
                        lineBuffer = parts[i];
                    else
                    {
                        var str = parts[i];
                        if (string.IsNullOrEmpty(str)) continue;
                        if (!isStdErr && WinConsole.IsInitialized)
                            str = ConsoleColorToForegroundSet(currentColor) + str;

                        if (isStdErr)
                            Logger.stdout.Error(str);
                        else
                            Logger.stdout.Info(str);
                    }
                }
            }
        }

        private const ConsoleColor defaultColor = ConsoleColor.Gray;
        private ConsoleColor currentColor = defaultColor;

        private static string ConsoleColorToForegroundSet(ConsoleColor col)
        {
            if (!WinConsole.UseVTEscapes) return "";
            string code = "0"; // reset

            switch (col)
            {
                case ConsoleColor.Black:
                    code = "30";
                    break;
                case ConsoleColor.DarkBlue:
                    code = "34";
                    break;
                case ConsoleColor.DarkGreen:
                    code = "32";
                    break;
                case ConsoleColor.DarkCyan:
                    code = "36";
                    break;
                case ConsoleColor.DarkRed:
                    code = "31";
                    break;
                case ConsoleColor.DarkMagenta:
                    code = "35";
                    break;
                case ConsoleColor.DarkYellow:
                    code = "33";
                    break;
                case ConsoleColor.Gray:
                    code = "37";
                    break;
                case ConsoleColor.DarkGray:
                    code = "90"; // literally bright black
                    break;
                case ConsoleColor.Blue:
                    code = "94";
                    break;
                case ConsoleColor.Green:
                    code = "92";
                    break;
                case ConsoleColor.Cyan:
                    code = "96";
                    break;
                case ConsoleColor.Red:
                    code = "91";
                    break;
                case ConsoleColor.Magenta:
                    code = "95";
                    break;
                case ConsoleColor.Yellow:
                    code = "93";
                    break;
                case ConsoleColor.White:
                    code = "97";
                    break;
            }

            return "\x1b[" + code + "m";
        }

        private static StdoutInterceptor stdoutInterceptor;
        private static StdoutInterceptor stderrInterceptor;

        private static class ConsoleHarmonyPatches
        {
            public static void Patch(HarmonyInstance harmony)
            {
                var console = typeof(Console);
                var resetColor = console.GetMethod("ResetColor");
                var foregroundProperty = console.GetProperty("ForegroundColor");
                var setFg = foregroundProperty?.GetSetMethod();
                var getFg = foregroundProperty?.GetGetMethod();

                if (resetColor != null)
                    harmony.Patch(resetColor, transpiler: new HarmonyMethod(typeof(ConsoleHarmonyPatches), nameof(PatchResetColor)));
                if (foregroundProperty != null)
                {
                    harmony.Patch(setFg, transpiler: new HarmonyMethod(typeof(ConsoleHarmonyPatches), nameof(PatchSetForegroundColor)));
                    harmony.Patch(getFg, transpiler: new HarmonyMethod(typeof(ConsoleHarmonyPatches), nameof(PatchGetForegroundColor)));
                }
            }

            public static ConsoleColor GetColor() => stdoutInterceptor.currentColor;
            public static void SetColor(ConsoleColor col) => stdoutInterceptor.currentColor = col;
            public static void ResetColor() => stdoutInterceptor.currentColor = defaultColor;

            public static IEnumerable<CodeInstruction> PatchGetForegroundColor(IEnumerable<CodeInstruction> _)
            {
                var getColorM = typeof(ConsoleHarmonyPatches).GetMethod("GetColor");
                return new[] {
                    new CodeInstruction(OpCodes.Tailcall),
                    new CodeInstruction(OpCodes.Call, getColorM),
                    new CodeInstruction(OpCodes.Ret)
                };
            }

            public static IEnumerable<CodeInstruction> PatchSetForegroundColor(IEnumerable<CodeInstruction> _)
            {
                var setColorM = typeof(ConsoleHarmonyPatches).GetMethod("SetColor");
                return new[] {
                    new CodeInstruction(OpCodes.Ldarg_0),
                    new CodeInstruction(OpCodes.Tailcall),
                    new CodeInstruction(OpCodes.Call, setColorM),
                    new CodeInstruction(OpCodes.Ret)
                };
            }

            public static IEnumerable<CodeInstruction> PatchResetColor(IEnumerable<CodeInstruction> _)
            {
                var resetColor = typeof(ConsoleHarmonyPatches).GetMethod("ResetColor");
                return new[] {
                    new CodeInstruction(OpCodes.Tailcall),
                    new CodeInstruction(OpCodes.Call, resetColor),
                    new CodeInstruction(OpCodes.Ret)
                };
            }
        }

        private static HarmonyInstance harmony;
        private static bool usingInterceptor = false;

        public static void Intercept()
        {
            if (!usingInterceptor)
            {
                usingInterceptor = true;
                if (harmony == null)
                    harmony = HarmonyInstance.Create("BSIPA Console Redirector Patcher");
                if (stdoutInterceptor == null)
                    stdoutInterceptor = new StdoutInterceptor();
                if (stderrInterceptor == null)
                    stderrInterceptor = new StdoutInterceptor() { isStdErr = true };

                RedirectConsole();
                ConsoleHarmonyPatches.Patch(harmony);
            }
        }

        public static void RedirectConsole()
        {
            if (usingInterceptor)
            {
                Console.SetOut(stdoutInterceptor);
                Console.SetError(stderrInterceptor);
            }
        }
    }
}
