using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using HarmonyLib;
using MonoMod.RuntimeDetour;
using MonoMod.RuntimeDetour.Platforms;
using MonoMod.Utils;

namespace BepInEx.Unity.Mono.Preloader.RuntimeFixes;

internal static class XTermFix
{
    public static int intOffset;

    public static void Apply()
    {
        if (PlatformHelper.Is(Platform.Windows))
            return;

        if (typeof(Console).Assembly.GetType("System.ConsoleDriver") == null)
            // Mono version is too old, use our own TTY implementation instead
            return;

        if (AccessTools.Method("System.TermInfoReader:DetermineVersion") != null)
            // Fix has been applied officially
            return;

        // Apparently on older Unity versions (4.x), using Process.Start can run Console..cctor
        // And since MonoMod's PlatformHelper (used by DetourHelper.Native) runs Process.Start to determine ARM/x86 platform,
        // this causes a crash owing to TermInfoReader running before it can be patched and fixed
        // Because Doorstop does not support ARM at the moment, we can get away with just forcing x86 detour platform.
        // TODO: Figure out a way to detect ARM on Unix without running Process.Start
        DetourHelper.Native = new DetourNativeX86Platform();

        var harmony = new Harmony("com.bepinex.xtermfix");

        harmony.Patch(AccessTools.Method("System.TermInfoReader:ReadHeader"),
                      new HarmonyMethod(typeof(XTermFix), nameof(ReadHeaderPrefix)));

        harmony.Patch(AccessTools.Method("System.TermInfoReader:Get", new[] { AccessTools.TypeByName("System.TermInfoNumbers") }),
                      transpiler: new HarmonyMethod(typeof(XTermFix), nameof(GetTermInfoNumbersTranspiler)));

        harmony.Patch(AccessTools.Method("System.TermInfoReader:Get", new[] { AccessTools.TypeByName("System.TermInfoStrings") }),
                      transpiler: new HarmonyMethod(typeof(XTermFix), nameof(GetTermInfoStringsTranspiler)));

        harmony.Patch(AccessTools.Method("System.TermInfoReader:GetStringBytes", new[] { AccessTools.TypeByName("System.TermInfoStrings") }),
                      transpiler: new HarmonyMethod(typeof(XTermFix), nameof(GetTermInfoStringsTranspiler)));

        DetourHelper.Native = null;
    }

    public static int GetInt32(byte[] buffer, int offset)
    {
        int b1 = buffer[offset];
        int b2 = buffer[offset + 1];
        int b3 = buffer[offset + 2];
        int b4 = buffer[offset + 3];

        return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
    }

    public static short GetInt16(byte[] buffer, int offset)
    {
        int b1 = buffer[offset];
        int b2 = buffer[offset + 1];

        return (short) (b1 | (b2 << 8));
    }

    public static int GetInteger(byte[] buffer, int offset) =>
        intOffset == 2
            ? GetInt16(buffer, offset)
            : GetInt32(buffer, offset);

    public static void DetermineVersion(short magic)
    {
        if (magic == 0x11a)
            intOffset = 2;
        else if (magic == 0x21e)
            intOffset = 4;
        else
            throw new Exception($"Unknown xterm header format: {magic}");
    }

    public static bool ReadHeaderPrefix(byte[] buffer,
                                        ref int position,
                                        ref short ___boolSize,
                                        ref short ___numSize,
                                        ref short ___strOffsets)
    {
        var magic = GetInt16(buffer, position);
        position += 2;
        DetermineVersion(magic);

        // nameSize = GetInt16(buffer, position);
        position += 2;
        ___boolSize = GetInt16(buffer, position);
        position += 2;
        ___numSize = GetInt16(buffer, position);
        position += 2;
        ___strOffsets = GetInt16(buffer, position);
        position += 2;
        // strSize = GetInt16(buffer, position);
        position += 2;

        return false;
    }

    public static IEnumerable<CodeInstruction> GetTermInfoNumbersTranspiler(IEnumerable<CodeInstruction> instructions)
    {
        // This implementation does not seem to have changed so I will be using indexes like the lazy fuck I am

        var list = instructions.ToList();

        list[31] = new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(XTermFix), nameof(intOffset)));
        list[36] = new CodeInstruction(OpCodes.Nop);
        list[39] = new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(XTermFix), nameof(GetInteger)));

        return list;
    }

    public static IEnumerable<CodeInstruction> GetTermInfoStringsTranspiler(IEnumerable<CodeInstruction> instructions)
    {
        // This implementation does not seem to have changed so I will be using indexes like the lazy fuck I am

        var list = instructions.ToList();

        list[32] = new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(XTermFix), nameof(intOffset)));

        return list;
    }
}
