using HarmonyLib;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Verse;

namespace Multiplayer.Client
{
    // Allow different factions' blueprints in the same cell
    // Ignore other factions' blueprints when building
    // Remove all blueprints when something solid is built over them
    // Don't draw other factions' blueprints
    // Don't link graphics of different factions' blueprints

    [HarmonyPatch(typeof(GenConstruct), nameof(GenConstruct.CanPlaceBlueprintAt_NewTemp))]
    static class CanPlaceBlueprintAtPatch
    {
        static MethodInfo CanPlaceBlueprintOver = AccessTools.Method(typeof(GenConstruct), nameof(GenConstruct.CanPlaceBlueprintOver));
        public static MethodInfo ShouldIgnore1Method = AccessTools.Method(typeof(CanPlaceBlueprintAtPatch), nameof(ShouldIgnore1));
        public static MethodInfo ShouldIgnore2Method = AccessTools.Method(typeof(CanPlaceBlueprintAtPatch), nameof(ShouldIgnore2));

        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> e, MethodBase method)
        {
            List <CodeInstruction> insts = e.ToList();
            for (int i = 0; i < insts.Count; i++) {
                var cur = insts[i];

                yield return cur;

                if (cur.opcode == OpCodes.Call && cur.operand as MethodInfo == CanPlaceBlueprintOver)
                {
                    var thingToIgnoreIndex = insts[i - 2].operand;

                    insts[i + 1].opcode = OpCodes.Brtrue;

                    yield return new CodeInstruction(OpCodes.Ldloc_S, thingToIgnoreIndex);
                    yield return new CodeInstruction(OpCodes.Call, ShouldIgnore1Method);
                    yield return new CodeInstruction(OpCodes.Or);
                }
            }
        }

        static bool ShouldIgnore2(ThingDef newThing, Thing oldThing) => newThing.IsBlueprint && ShouldIgnore1(oldThing);

        static bool ShouldIgnore1(Thing oldThing) => oldThing.def.IsBlueprint && oldThing.Faction != Faction.OfPlayer;
    }

    [HarmonyPatch(typeof(GenConstruct), nameof(GenConstruct.CanPlaceBlueprintAt_NewTemp))]
    static class CanPlaceBlueprintAtPatch2
    {
        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> e, MethodBase original)
        {
            byte thingToIgnore_Ldarg_S = (byte) original.GetParameters().FirstIndexOf(p => p.Name == "thingToIgnore");

            if (thingToIgnore_Ldarg_S < 1) {
                Log.Error($"FAIL: {nameof(CanPlaceBlueprintAtPatch2)} can't find thingToIgnore");
                return e;
            }

            List<CodeInstruction> insts = e.ToList();

            int loop = new CodeFinder(original, insts).
                Forward(OpCodes.Ldstr, "IdenticalThingExists").
                Backward(OpCodes.Ldarg_S, thingToIgnore_Ldarg_S);

            insts.Insert(
                loop - 1,
                new CodeInstruction(OpCodes.Ldloc_S, insts[loop - 1].operand),
                new CodeInstruction(OpCodes.Call, CanPlaceBlueprintAtPatch.ShouldIgnore1Method),
                new CodeInstruction(OpCodes.Brtrue, insts[loop + 1].operand)
            );

            return insts;
        }
    }

    [HarmonyPatch(typeof(GenConstruct), nameof(GenConstruct.InteractionCellStandable))]
    static class InteractionCellStandablePatch
    {
        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> e, MethodBase original)
        {
            byte thingToIgnore_Ldarg_S = (byte) original.GetParameters().FirstIndexOf(p => p.Name == "thingToIgnore");

            if (thingToIgnore_Ldarg_S < 1) {
                Log.Error($"FAIL: {nameof(InteractionCellStandablePatch)} can't find thingToIgnore");
                return e;
            }

            List<CodeInstruction> insts = e.ToList();

            int loop = new CodeFinder(original, insts).
                Forward(OpCodes.Ldstr, "InteractionSpotBlocked").
                Backward(OpCodes.Ldarg_S, thingToIgnore_Ldarg_S);

            insts.Insert(
                loop - 3,
                new CodeInstruction(insts[loop - 3].opcode, insts[loop - 3].operand),
                new CodeInstruction(insts[loop - 2].opcode, insts[loop - 2].operand),
                new CodeInstruction(OpCodes.Callvirt, SpawnBuildingAsPossiblePatch.ThingListGet),
                new CodeInstruction(OpCodes.Call, CanPlaceBlueprintAtPatch.ShouldIgnore1Method),
                new CodeInstruction(OpCodes.Brtrue, insts[loop + 1].operand)
            );

            return insts;
        }
    }

    [HarmonyPatch(typeof(GenConstruct), nameof(GenConstruct.NotBlockingAnyInteractionCells))]
    static class NotBlockingAnyInteractionCellsPatch
    {
        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> e, MethodBase original)
        {
            byte thingToIgnore_Ldarg_S = (byte) original.GetParameters().FirstIndexOf(p => p.Name == "thingToIgnore");

            if (thingToIgnore_Ldarg_S < 1) {
                Log.Error($"FAIL: {nameof(NotBlockingAnyInteractionCellsPatch)} can't find thingToIgnore");
                return e;
            }

            List<CodeInstruction> insts = e.ToList();

            int loop = new CodeFinder(original, insts).
                Forward(OpCodes.Ldstr, "WouldBlockInteractionSpot").
                Backward(OpCodes.Ldarg_S, thingToIgnore_Ldarg_S);

            insts.Insert(
                loop - 1,
                new CodeInstruction(insts[loop - 1].opcode, insts[loop - 1].operand),
                new CodeInstruction(OpCodes.Call, CanPlaceBlueprintAtPatch.ShouldIgnore1Method),
                new CodeInstruction(OpCodes.Brtrue, insts[loop + 1].operand)
            );

            return insts;
        }
    }

    [HarmonyPatch(typeof(PlaceWorker_NeverAdjacentTrap), nameof(PlaceWorker_NeverAdjacentTrap.AllowsPlacing))]
    static class PlaceWorkerTrapPatch
    {
        static IEnumerable<CodeInstruction> Transpiler(ILGenerator gen, IEnumerable<CodeInstruction> e, MethodBase original)
        {
            List<CodeInstruction> insts = e.ToList();
            Label label = gen.DefineLabel();

            var finder = new CodeFinder(original, insts);
            int pos = finder.Forward(OpCodes.Stloc_S, 5);

            insts.Insert(
                pos + 1,
                new CodeInstruction(OpCodes.Ldloc_S, 5),
                new CodeInstruction(OpCodes.Call, CanPlaceBlueprintAtPatch.ShouldIgnore1Method),
                new CodeInstruction(OpCodes.Brtrue, label)
            );

            int ret = finder.Start().Forward(OpCodes.Ret);
            insts[ret + 1].labels.Add(label);

            return insts;
        }
    }

    [HarmonyPatch(typeof(GenSpawn), nameof(GenSpawn.WipeExistingThings))]
    static class WipeExistingThingsPatch
    {
        static MethodInfo SpawningWipes = AccessTools.Method(typeof(GenSpawn), nameof(GenSpawn.SpawningWipes));

        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> insts)
        {
            foreach (CodeInstruction inst in insts)
            {
                yield return inst;

                if (inst.opcode == OpCodes.Call && inst.operand as MethodInfo == SpawningWipes)
                {
                    yield return new CodeInstruction(OpCodes.Ldarg_2);
                    yield return new CodeInstruction(OpCodes.Ldloc_3);
                    yield return new CodeInstruction(OpCodes.Call, CanPlaceBlueprintAtPatch.ShouldIgnore2Method);
                    yield return new CodeInstruction(OpCodes.Not);
                    yield return new CodeInstruction(OpCodes.And);
                }
            }
        }
    }

    [HarmonyPatch(typeof(GenSpawn), nameof(GenSpawn.WipeAndRefundExistingThings))]
    static class WipeAndRefundExistingThingsPatch
    {
        static MethodInfo SpawningWipes = AccessTools.Method(typeof(GenSpawn), nameof(GenSpawn.SpawningWipes));

        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> insts)
        {
            foreach (CodeInstruction inst in insts)
            {
                yield return inst;

                if (inst.opcode == OpCodes.Call && inst.operand as MethodInfo == SpawningWipes)
                {
                    yield return new CodeInstruction(OpCodes.Ldarg_2);
                    yield return new CodeInstruction(OpCodes.Ldloc_S, 4);
                    yield return new CodeInstruction(OpCodes.Call, CanPlaceBlueprintAtPatch.ShouldIgnore2Method);
                    yield return new CodeInstruction(OpCodes.Not);
                    yield return new CodeInstruction(OpCodes.And);
                }
            }
        }
    }

    [HarmonyPatch(typeof(GenSpawn), nameof(GenSpawn.SpawnBuildingAsPossible))]
    static class SpawnBuildingAsPossiblePatch
    {
        static MethodInfo SpawningWipes = AccessTools.Method(typeof(GenSpawn), nameof(GenSpawn.SpawningWipes));
        public static MethodInfo ThingListGet = AccessTools.IndexerGetter(typeof(List<Thing>), [typeof(int)]);
        static FieldInfo ThingDefField = AccessTools.Field(typeof(Thing), nameof(Thing.def));

        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> insts)
        {
            foreach (CodeInstruction inst in insts)
            {
                yield return inst;

                if (inst.opcode == OpCodes.Call && inst.operand as MethodInfo == SpawningWipes)
                {
                    yield return new CodeInstruction(OpCodes.Ldarg_0);
                    yield return new CodeInstruction(OpCodes.Ldfld, ThingDefField);
                    yield return new CodeInstruction(OpCodes.Ldloc_3);
                    yield return new CodeInstruction(OpCodes.Ldloc_S, 4);
                    yield return new CodeInstruction(OpCodes.Callvirt, ThingListGet);
                    yield return new CodeInstruction(OpCodes.Call, CanPlaceBlueprintAtPatch.ShouldIgnore2Method);
                    yield return new CodeInstruction(OpCodes.Not);
                    yield return new CodeInstruction(OpCodes.And);
                }
            }
        }
    }

    [HarmonyPatch(typeof(GenPlace), nameof(GenPlace.HaulPlaceBlockerIn))]
    static class HaulPlaceBlockerInPatch
    {
        static IEnumerable<CodeInstruction> Transpiler(ILGenerator gen, IEnumerable<CodeInstruction> e, MethodBase original)
        {
            List<CodeInstruction> insts = e.ToList();
            Label label = gen.DefineLabel();

            CodeFinder finder = new CodeFinder(original, insts);
            int pos = finder.Forward(OpCodes.Stloc_2);

            insts.Insert(
                pos + 1,
                new CodeInstruction(OpCodes.Ldloc_2),
                new CodeInstruction(OpCodes.Call, CanPlaceBlueprintAtPatch.ShouldIgnore1Method),
                new CodeInstruction(OpCodes.Brtrue, label)
            );

            int ret = finder.End().Advance(-1).Backward(OpCodes.Ret);
            insts[ret + 1].labels.Add(label);

            return insts;
        }
    }

    [HarmonyPatch(typeof(GenSpawn), nameof(GenSpawn.SpawningWipes))]
    static class SpawningWipesBlueprintPatch
    {
        static void Postfix(ref bool __result, BuildableDef newEntDef, BuildableDef oldEntDef)
        {
            ThingDef newDef = newEntDef as ThingDef;
            ThingDef oldDef = oldEntDef as ThingDef;
            if (newDef == null || oldDef == null) return;

            if (!newDef.IsBlueprint && oldDef.IsBlueprint &&
                !GenConstruct.CanPlaceBlueprintOver(GenConstruct.BuiltDefOf(oldDef), newDef))
                __result = true;
        }
    }

    [HarmonyPatch(typeof(Thing), nameof(Thing.Print))]
    static class BlueprintPrintPatch
    {
        static bool Prefix(Thing __instance)
        {
            if (Multiplayer.Client == null || !__instance.def.IsBlueprint) return true;
            return __instance.Faction == null || __instance.Faction == Multiplayer.RealPlayerFaction;
        }
    }

    // LinkGrid is one building per cell, so only the player faction's blueprints are shown and linked
    [HarmonyPatch(typeof(LinkGrid), nameof(LinkGrid.Notify_LinkerCreatedOrDestroyed))]
    static class LinkGridBlueprintPatch
    {
        static bool Prefix(Thing linker)
        {
            return !linker.def.IsBlueprint || linker.Faction == Multiplayer.RealPlayerFaction;
        }
    }

    // todo revisit for pvp
    //[HarmonyPatch(typeof(Designator_Build), nameof(Designator_Build.DesignateSingleCell))]
    static class DisableInstaBuild
    {
        static MethodInfo GetStatValueAbstract = AccessTools.Method(typeof(StatExtension), nameof(StatExtension.GetStatValueAbstract), new []{ typeof(BuildableDef), typeof(StatDef), typeof(ThingDef) });
        static MethodInfo WorkToBuildMethod = AccessTools.Method(typeof(DisableInstaBuild), nameof(WorkToBuild));

        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> e, MethodBase original)
        {
            List<CodeInstruction> insts = e.ToList();
            int pos = new CodeFinder(original, insts).Forward(OpCodes.Call, GetStatValueAbstract);
            insts[pos + 1] = new CodeInstruction(OpCodes.Call, WorkToBuildMethod);

            return insts;
        }

        static float WorkToBuild() => Multiplayer.Client == null ? 0f : -1f;
    }

    [HarmonyPatch(typeof(Frame), nameof(Frame.WorkToBuild), MethodType.Getter)]
    static class NoZeroWorkFrames
    {
        static void Postfix(ref float __result)
        {
            __result = Math.Max(5, __result); // >=5 otherwise the game complains about jobs starting too fast
        }
    }

    [HarmonyPatch(typeof(WorkGiver_ConstructDeliverResourcesToBlueprints), nameof(WorkGiver_ConstructDeliverResourcesToBlueprints.NoCostFrameMakeJobFor))]
    static class OnlyConstructorsPlaceNoCostFrames
    {
        static MethodInfo IsConstructionMethod = AccessTools.Method(typeof(OnlyConstructorsPlaceNoCostFrames), nameof(IsConstruction));

        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> insts)
        {
            foreach (CodeInstruction inst in insts)
            {
                yield return inst;

                if (inst.opcode == OpCodes.Isinst && inst.operand as Type == typeof(Blueprint))
                {
                    yield return new CodeInstruction(OpCodes.Ldnull);
                    yield return new CodeInstruction(OpCodes.Cgt_Un);

                    yield return new CodeInstruction(OpCodes.Ldarg_0);
                    yield return new CodeInstruction(OpCodes.Call, IsConstructionMethod);

                    yield return new CodeInstruction(OpCodes.And);
                }
            }
        }

        static bool IsConstruction(WorkGiver w) => w.def.workType == WorkTypeDefOf.Construction;
    }

}
