using HarmonyLib;
using KFCommonUtilityLib.Scripts.StaticManagers;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using UnityEngine;
using CameraShake;
using System;

[HarmonyPatch]
class RecoilPatch
{
    [HarmonyPatch(typeof(EntityPlayerLocal), "Awake")]
    [HarmonyPostfix]
    private static void Postfix_Awake_EntityPlayerLocal(EntityPlayerLocal __instance)
    {
        RecoilManager.InitPlayer(__instance);
    }

    [HarmonyPatch(typeof(vp_FPCamera), nameof(vp_FPCamera.LateUpdate))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_LateUpdate_vp_FPCamera(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();

        var mtd_update = AccessTools.Method(typeof(vp_FPCamera), nameof(vp_FPCamera.Update3rdPerson));
        var prop_trans = AccessTools.PropertyGetter(typeof(vp_Component), nameof(vp_Component.transform));

        for (int i = 0; i < codes.Count; i++)
        {
            if (codes[i].Calls(mtd_update))
            {
                codes.InsertRange(i - 1, new[]
                {
                    new CodeInstruction(OpCodes.Ldarg_0),
                    new CodeInstruction(OpCodes.Call, prop_trans),
                    CodeInstruction.Call(typeof(KFExtensions), nameof(KFExtensions.AddMissingComponent), new Type[]{ typeof(Transform) }, new Type[]{ typeof(CameraShaker) }),
                    CodeInstruction.Call(typeof(CameraShaker), nameof(CameraShaker.UpdateShake))
                });
                break;
            }
        }

        return codes;
    }

    [HarmonyPatch(typeof(GameManager), nameof(GameManager.SaveAndCleanupWorld))]
    [HarmonyPostfix]
    private static void Postfix_SaveAndCleanupWorld_GameManager()
    {
        RecoilManager.Cleanup();
    }

    [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.OnFired))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_OnFired_EntityPlayerLocal(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();
        FieldInfo fld_isfpv = AccessTools.Field(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.bFirstPersonView));
        ConstructorInfo mtd_ctor = AccessTools.Constructor(typeof(Vector2), new[] { typeof(float), typeof(float) });

        for (int i = 0; i < codes.Count - 1; i++)
        {
            if (codes[i].Is(OpCodes.Call, mtd_ctor) && codes[i + 1].opcode == OpCodes.Ldloc_0)
            {
                for (int j = i + 1; j < codes.Count - 1; j++)
                {
                    if (codes[j].opcode == OpCodes.Pop && codes[j + 1].opcode == OpCodes.Ldarg_0)
                    {
                        codes.RemoveRange(i + 1, j - i);
                        break;
                    }
                }
                codes.InsertRange(i + 1, new[]
                {
                    new CodeInstruction(OpCodes.Ldarg_0),
                    new CodeInstruction(OpCodes.Ldloca_S, 0),
                    new CodeInstruction(OpCodes.Ldloca_S, 1),
                    CodeInstruction.Call(typeof(RecoilPatch), nameof(RecoilPatch.ModifyRecoil))
                });
                i += 4;
            }
            else if (codes[i].LoadsField(fld_isfpv))
            {
                codes.RemoveRange(i + 2, codes.Count - i - 3);
                codes.InsertRange(i + 2, new[]
                {
                    new CodeInstruction(OpCodes.Ldloc_0),
                    new CodeInstruction(OpCodes.Ldloc_1),
                    CodeInstruction.Call(typeof(RecoilManager), nameof(RecoilManager.AddRecoil))
                });
                break;
            }
        }
        return codes;
    }

    private static void ModifyRecoil(EntityPlayerLocal player, ref Vector2 kickHor, ref Vector2 kickVer)
    {
        float multiplierHor = Mathf.Max(1 - EffectManager.GetValue(CustomEnums.KickDegreeHorizontalModifier, player.inventory.holdingItemItemValue, 0f, player), 0);
        float multiplierVer = Mathf.Max(1 - EffectManager.GetValue(CustomEnums.KickDegreeVerticalModifier, player.inventory.holdingItemItemValue, 0f, player), 0);
        kickHor *= multiplierHor;
        kickVer *= multiplierVer;
    }

    [HarmonyPatch(typeof(PlayerMoveController), nameof(PlayerMoveController.Update))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> Transpiler_Update_PlayerMoveController(IEnumerable<CodeInstruction> instructions)
    {
        var codes = instructions.ToList();

        FieldInfo fld_rotation = AccessTools.Field(typeof(MovementInput), nameof(MovementInput.rotation));
        FieldInfo fld_rx = AccessTools.Field(typeof(Vector3), nameof(Vector3.x));
        FieldInfo fld_ry = AccessTools.Field(typeof(Vector3), nameof(Vector3.y));

        for (int i = 0; i < codes.Count; i++)
        {
            if (codes[i].LoadsField(fld_rotation, true))
            {
                if (codes[i + 1].LoadsField(fld_rx, true))
                {
                    for (; i < codes.Count; i++)
                    {
                        if (codes[i].opcode == OpCodes.Stind_R4)
                        {
                            codes.Insert(i, CodeInstruction.Call(typeof(RecoilManager), nameof(RecoilManager.CompensateX)));
                            break;
                        }
                    }
                }
                else if (codes[i + 1].LoadsField(fld_ry, true))
                {
                    for (; i < codes.Count; i++)
                    {
                        if (codes[i].opcode == OpCodes.Stind_R4)
                        {
                            codes.Insert(i, CodeInstruction.Call(typeof(RecoilManager), nameof(RecoilManager.CompensateY)));
                            break;
                        }
                    }
                }
            }
        }

        return codes;
    }

    [HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.MoveByInput))]
    [HarmonyPrefix]
    private static bool Prefix_MoveByInput_EntityPlayerLocal()
    {
        RecoilManager.ApplyRecoil();
        return true;
    }
}