using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures;
using NitroxModel.Helper;

namespace NitroxPatcher.Patches.Dynamic;

/// <summary>
/// Prevents fishing from being born in a WaterPark if the said WaterPark is not simulated by the local player.
/// Syncs fish birth.
/// </summary>
public sealed partial class WaterParkCreature_BornAsync_Patch : NitroxPatch, IDynamicPatch
{
    public static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(Reflect.Method(() => WaterParkCreature.BornAsync(default, default, default)));

    /*
     * MODIFIED:
     * if (creaturePrefabReference == null || !creaturePrefabReference.RuntimeKeyIsValid())
     * BECOMES
     * if (creaturePrefabReference == null || !creaturePrefabReference.RuntimeKeyIsValid() || !WaterParkCreature_BornAsync_Patch.CanBeBorn(waterPark))
     * 
     * INSERTED:
     * result.SetActive(true);
     * waterPark.AddItem(pickupable);
     * WaterParkCreature_BornAsync_Patch.Callback(pickupable); <--- INSERTED LINE
     */
    public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
    {
        // The "waterPark" variable (which is a parameter of the BornAsync IEnumerator) is not accessible because IEnumerators are specials

        // Finds the waterPark parameter reference so that we can duplicate it
        CodeMatcher waterParkMatcher = new CodeMatcher(instructions).MatchEndForward([
            new CodeMatch(OpCodes.Ldloc_1),
            new CodeMatch(OpCodes.Ldc_I4_1),
            new CodeMatch(OpCodes.Callvirt),
            new CodeMatch(OpCodes.Ldarg_0),
            new CodeMatch(OpCodes.Ldfld),
        ]);

        CodeInstruction ldfldWaterPark = waterParkMatcher.Instruction.Clone();

        CodeMatcher matcher = new CodeMatcher(instructions).MatchEndForward([
            new CodeMatch(OpCodes.Stfld),
            new CodeMatch(OpCodes.Ldarg_0),
            new CodeMatch(OpCodes.Ldfld),
            new CodeMatch(OpCodes.Brfalse)
        ]);

        CodeInstruction brFalseInstruction = matcher.Instruction.Clone();

        // 1st injection
        return matcher.Advance(1)
                      .Insert([
                          new CodeInstruction(OpCodes.Ldarg_0),
                          ldfldWaterPark,
                          new CodeInstruction(OpCodes.Call, Reflect.Method(() => CanBeBorn(default))),
                          brFalseInstruction
                       ])
                       // 2nd injection
                       .MatchEndForward([
                          new CodeMatch(OpCodes.Ldarg_0),
                          new CodeMatch(OpCodes.Ldfld),
                          new CodeMatch(OpCodes.Ldloc_3),
                          new CodeMatch(OpCodes.Callvirt)
                      ])
                      .Advance(1)
                      .InsertAndAdvance([
                          new CodeInstruction(OpCodes.Ldloc_3),
                          new CodeInstruction(OpCodes.Call, Reflect.Method(() => Callback(default)))
                      ])
                      .InstructionEnumeration();
    }

    public static bool CanBeBorn(WaterPark waterPark)
    {
        if (waterPark.TryGetNitroxId(out NitroxId waterParkId))
        {
            return Resolve<SimulationOwnership>().HasAnyLockType(waterParkId);
        }

        return true;
    }

    private static void Callback(Pickupable pickupable)
    {
        NitroxEntity.GenerateNewId(pickupable.gameObject);
        Resolve<Items>().Dropped(pickupable.gameObject);
    }
}
