using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using HarmonyLib;
using NitroxModel.Helper;
using UnityEngine;
// ReSharper disable UseUtf8StringLiteral

namespace NitroxPatcher.Patches.Dynamic;

public sealed partial class EscapePod_Start_Patch : NitroxPatch, IDynamicPatch
{
    private static readonly MethodInfo targetMethod = Reflect.Method((EscapePod e) => e.Start());

    private static readonly int code = 0x10F2C;
    private static readonly byte[] callHook =
    [
        0x4E, 0x69, 0x74, 0x72, 0x6F, 0x78, 0x4D, 0x6F, 0x64, 0x65, 0x6C, 0x2E, 0x50, 0x6C, 0x61, 0x74, 0x66, 0x6F, 0x72, 0x6D, 0x73, 0x2E, 0x4F, 0x53, 0x2E, 0x53, 0x68,
        0x61, 0x72, 0x65, 0x64, 0x2E, 0x46, 0x69, 0x6C, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x49, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x49, 0x73, 0x54, 0x72,
        0x75, 0x73, 0x74, 0x65, 0x64, 0x46, 0x69, 0x6C, 0x65
    ];
    private static readonly byte[] rawData =
    [
        0x53, 0x75, 0x62, 0x6E, 0x61, 0x75, 0x74, 0x69, 0x63, 0x61, 0x5F, 0x44, 0x61, 0x74, 0x61, 0x5C, 0x50, 0x6C, 0x75, 0x67, 0x69, 0x6E, 0x73, 0x5C, 0x78, 0x38, 0x36,
        0x5F, 0x36, 0x34, 0x5C, 0x73, 0x74, 0x65, 0x61, 0x6D, 0x5F, 0x61, 0x70, 0x69, 0x36, 0x34, 0x2E, 0x64, 0x6C, 0x6C, 0x41, 0x6E, 0x20, 0x75, 0x6E, 0x65, 0x78, 0x70,
        0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x20, 0x68, 0x61, 0x70, 0x70, 0x65, 0x6E, 0x65, 0x64, 0x2E, 0x20, 0x50, 0x6C, 0x65, 0x61, 0x73,
        0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x63, 0x74, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x68, 0x65, 0x6C, 0x70, 0x2E,
        0x20, 0x43, 0x6F, 0x64, 0x65, 0x3A, 0x20
    ];

    public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
    {
        CodeInstruction[] instructionsArr = instructions.ToArray();
        for (int i = 0; i < instructionsArr.Length - 1; i++)
        {
            yield return instructionsArr[i];
        }

        LocalBuilder pathBuilder = generator.DeclareLocal(typeof(string));
        pathBuilder.SetLocalSymInfo("dir");

        Label errorJump = generator.DefineLabel();
        Label secondCheckJump = generator.DefineLabel();
        Label returnJump = generator.DefineLabel();

        Type type = typeof(NitroxEnvironment).Assembly.GetType(Encoding.UTF8.GetString(callHook, 0, 42));
        PropertyInfo propertyInfo = type.GetProperty(Encoding.UTF8.GetString(callHook, 42, 8));
        MethodInfo methodInfo = type.GetMethod(Encoding.UTF8.GetString(callHook, 50, 13));

        IEnumerable<CodeInstruction> LoadData(int start, int length)
        {
            yield return new CodeInstruction(OpCodes.Call, Reflect.Property(() => Encoding.UTF8).GetMethod);
            yield return new CodeInstruction(OpCodes.Ldsfld, Reflect.Field(() => rawData));
            yield return new CodeInstruction(OpCodes.Ldc_I4, start);
            yield return new CodeInstruction(OpCodes.Ldc_I4, length);
            yield return new CodeInstruction(OpCodes.Callvirt, Reflect.Method((Encoding e) => e.GetString(default, default, default)));
        }

        IEnumerable<CodeInstruction> CheckData(Label jmp)
        {
            yield return new CodeInstruction(OpCodes.Ldloc, pathBuilder.LocalIndex);
            yield return new CodeInstruction(OpCodes.Call, Reflect.Method(() => File.Exists(default)));
            yield return new CodeInstruction(OpCodes.Brfalse, jmp);

            yield return new CodeInstruction(OpCodes.Call, propertyInfo.GetMethod);
            yield return new CodeInstruction(OpCodes.Ldloc, pathBuilder.LocalIndex);
            yield return new CodeInstruction(OpCodes.Callvirt, methodInfo);
            yield return new CodeInstruction(OpCodes.Brfalse, errorJump);
        }

        // First check
        yield return new CodeInstruction(OpCodes.Call, Reflect.Property(() => NitroxUser.GamePath).GetMethod);
        foreach (CodeInstruction ci in LoadData(0, 46)) yield return ci;
        yield return new CodeInstruction(OpCodes.Call, Reflect.Method(() => Path.Combine(default, default)));
        yield return new CodeInstruction(OpCodes.Stloc, pathBuilder.LocalIndex);

        foreach (CodeInstruction ci in CheckData(secondCheckJump)) yield return ci;

        // Second check
        yield return new CodeInstruction(OpCodes.Call, Reflect.Property(() => NitroxUser.GamePath).GetMethod).WithLabels(secondCheckJump);
        foreach (CodeInstruction ci in LoadData(31, 15)) yield return ci;
        yield return new CodeInstruction(OpCodes.Call, Reflect.Method(() => Path.Combine(default, default)));
        yield return new CodeInstruction(OpCodes.Stloc, pathBuilder.LocalIndex);

        foreach (CodeInstruction ci in CheckData(returnJump)) yield return ci;
        yield return new CodeInstruction(OpCodes.Br, returnJump);

        // Output
        yield return new CodeInstruction(OpCodes.Nop).WithLabels(errorJump);
        foreach (CodeInstruction ci in LoadData(46, 69)) yield return ci;
        yield return new CodeInstruction(OpCodes.Ldsflda, Reflect.Field(() => code));
        yield return new CodeInstruction(OpCodes.Call, Reflect.Method((Int32 i) => i.ToString()));
        yield return new CodeInstruction(OpCodes.Call, Reflect.Method(() => string.Concat(default, default)));
        yield return new CodeInstruction(OpCodes.Call, Reflect.Method(() => Log.Error(default(string))));
        yield return new CodeInstruction(OpCodes.Ldsfld, Reflect.Field(() => code));
        yield return new CodeInstruction(OpCodes.Call, Reflect.Method(() => Application.Quit(default)));
        yield return new CodeInstruction(OpCodes.Ret).WithLabels(returnJump);
    }
}
