using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using HarmonyLib;
using Mafi;
using Mafi.Collections.ImmutableCollections;
using Mafi.Core;
using Mafi.Core.Buildings.Cargo.Modules;
using Mafi.Core.Buildings.Cargo.Ships.Modules;
using Mafi.Core.Buildings.Settlements;
using Mafi.Core.Buildings.Storages;
using Mafi.Core.Entities;
using Mafi.Core.Entities.Dynamic;
using Mafi.Core.Entities.Static;
using Mafi.Core.Entities.Static.Layout;
using Mafi.Core.Factory.Machines;
using Mafi.Core.Factory.Transports;
using Mafi.Core.Population;
using Mafi.Core.Ports.Io;
using Mafi.Core.Products;
using Mafi.Core.Prototypes;
using Mafi.Core.Terrain;
using Mafi.Core.World.Entities;

namespace CaptainOfIndustry {
    [HarmonyPatch]
    public class Patches {
        [HarmonyPrefix]
        [HarmonyPatch(typeof(ExcavatorProtoBuilder.ExcavatorProtoBuilderState), "SetCapacity")]
        static void excavatorCapacityMultiplier(ref int quantity) {
//            Console.WriteLine("IndustrialCyka: Old excavator capacity: {0}", quantity);
            quantity *= Main.excavatorCapacityMultiplier.Value;
//            Console.WriteLine("IndustrialCyka: Old excavator capacity: {0}", quantity);
        }

        [HarmonyPrefix]
        [HarmonyPatch(typeof(ExcavatorProtoBuilder.ExcavatorProtoBuilderState), "SetMinedThicknessByDistanceMeters")]
        static void excavatorMiningAreaMultiplier(ref float[] thicknessMeters) {
            for (int i = 0; i < thicknessMeters.Length; i++) {
//                Console.WriteLine("IndustrialCyka: Old mining area: {0}", thicknessMeters[i]);
                thicknessMeters[i] *= Main.excavatorMiningAreaMultiplier.Value;
//                Console.WriteLine("IndustrialCyka: New mining area: {0}", thicknessMeters[i]);
            }
        }

        [HarmonyPrefix]
        [HarmonyPatch(typeof(ExcavatorProtoBuilder.ExcavatorProtoBuilderState), "SetMaxMiningDistance")]
        static void excavatorReachMultiplier(ref RelTile1i minMiningDistance, ref RelTile1i maxMiningDistance) {
            Console.WriteLine("Min distance: {0}, max distance: {1}", minMiningDistance, maxMiningDistance);
            minMiningDistance = new RelTile1i((int)(minMiningDistance.Value * Main.excavatorMinReachMultiplier.Value));
            maxMiningDistance = new RelTile1i((int)(maxMiningDistance.Value * Main.excavatorMaxReachMultiplier.Value));
            Console.WriteLine("Min distance: {0}, max distance: {1}", minMiningDistance, maxMiningDistance);
        }

        [HarmonyPrefix]
        [HarmonyPatch(typeof(TruckProtoBuilder.TruckProtoBuilderState), "SetCapacity")]
        static void truckCapacityMultiplier(ref int quantity) {
//            Console.WriteLine("IndustrialCyka: Old truck capacity: {0}", quantity);
            quantity *= Main.truckCapacityMultiplier.Value;
//            Console.WriteLine("IndustrialCyka: New truck capacity: {0}", quantity);
        }

        // [HarmonyPrefix]
        // [HarmonyPatch(typeof(StorageProtoBuilder.State), "SetCapacity")]
        // static void storageCapacityMultiplier(ref int capacity) {
//        //     Console.WriteLine("IndustrialCyka: Old storage capacity: {0}", capacity);
        //     capacity = (int)(capacity * Main.storageCapacityMultiplier.Value);
//        //     Console.WriteLine("IndustrialCyka: New storage capacity: {0}", capacity);
        // }

        [HarmonyPostfix]
        [HarmonyPatch(typeof(CargoShipModuleProto), MethodType.Constructor,
            new[] {
                typeof(Proto.ID), typeof(Proto.Str), typeof(ProductType), typeof(Quantity),
                typeof(CargoShipModuleProto.Gfx)
            })]
        static void cargoShipCapacityMultiplier(CargoShipModuleProto __instance) {
//            Console.WriteLine("IndustrialCyka: Old ship capacity: {0}", __instance.Capacity.Value);
            __instance.Capacity = new Quantity(__instance.Capacity.Value * Main.cargoShipCapacityMultiplier.Value);
//            Console.WriteLine("IndustrialCyka: New ship capacity: {0}", __instance.Capacity.Value);
        }

        // No workey... idk how it's supposed to work, speed is always 1 or 100%
        // [HarmonyPrefix]
        // [HarmonyPatch(typeof(DrivingEntity), "SetSpeedFactor")]
        // static void vehicleSpeedMultiplier(ref Percent speedFactor) {
//        //     Console.WriteLine("IndustrialCyka: Old speed: {0}", speedFactor.ToString());
        //     // typeof(Percent)
        //     //     .GetField("RawValue",BindingFlags.Instance|BindingFlags.NonPublic)
        //     //     .SetValue(speedFactor,speedFactor.ToFloat() / Main.vehicleSpeedMultiplier.Value);
        //     Percent newSpeedFactor = Percent.FromFloat(speedFactor.ToFloat() / Main.vehicleSpeedMultiplier.Value);
        //     speedFactor = newSpeedFactor;
//        //     Console.WriteLine("IndustrialCyka: New speed: {1}", newSpeedFactor.RawValue.ToString());
        // }

//         [HarmonyPostfix]
//         [HarmonyPatch(typeof(SmoothDriver), "SetSpeedFactor")]
//         static void vehicleSpeedMultiplier(SmoothDriver __instance) {
//             Fix32 speedMulti = Fix32.FromFloat(Main.vehicleSpeedMultiplier.Value);
//
//             Traverse traverse = Traverse.Create(__instance);
//             Traverse maxForwardsSpeedField = traverse.Field("m_maxForwardsSpeed");
//             Traverse maxBackwardsSpeedField = traverse.Field("m_maxBackwardsSpeed");
//             Traverse maxAccelerationField = traverse.Field("m_maxAcceleration");
//
// //            Console.WriteLine("IndustrialCyka: Old speeds: (F) {0}, (B) {1}, (A) {2}", maxForwardsSpeedField.GetValue(),
//                 // maxBackwardsSpeedField.GetValue(), maxAccelerationField.GetValue());
//
//             maxForwardsSpeedField.SetValue((Fix32)maxForwardsSpeedField.GetValue() * speedMulti);
//             maxBackwardsSpeedField.SetValue((Fix32)maxBackwardsSpeedField.GetValue() * speedMulti);
//             maxAccelerationField.SetValue((Fix32)maxAccelerationField.GetValue() * speedMulti);
//
// //            Console.WriteLine("IndustrialCyka: New speeds: (F) {0}, (B) {1}, (A) {2}", maxForwardsSpeedField.GetValue(),
//                 // maxBackwardsSpeedField.GetValue(), maxAccelerationField.GetValue());
//         }

        [HarmonyPostfix]
        [HarmonyPatch(typeof(StorageBaseProto), MethodType.Constructor,
            new[] {
                typeof(StaticEntityProto.ID),
                typeof(Proto.Str),
                typeof(EntityLayout),
                typeof(Quantity),
                typeof(EntityCosts),
                typeof(LayoutEntityProto.Gfx),
                typeof(Quantity),
                typeof(Duration),
                typeof(IEnumerable<Tag>)
            })]
        static void storageCapacityMultiplier(StorageProto __instance) {
//            Console.WriteLine("IndustrialCyka: Old storage capacity: {0}", __instance.Capacity.Value);
//            Console.WriteLine("IndustrialCyka: Old storage transfer limit: {0}", __instance.TransferLimit.Value);
            Traverse traverse = Traverse.Create(__instance);

            traverse.Field("Capacity")
                .SetValue(new Quantity((int)(__instance.Capacity.Value * Main.storageCapacityMultiplier.Value)));
            traverse.Field("TransferLimit").SetValue(new Quantity(10000));
            traverse.Field("TransferLimitDuration").SetValue(Duration.FromTicks(1));

//            Console.WriteLine("IndustrialCyka: New storage capacity: {0}", __instance.Capacity.Value);
//            Console.WriteLine("IndustrialCyka: New storage transfer limit: {0}", __instance.TransferLimit.Value);
        }

        [HarmonyPrefix]
        [HarmonyPatch(typeof(UpointsManager), "GenerateUnity")]
        static void unityGenerationMultiplier(Proto.ID categoryId, ref Upoints generated) {
//            Console.WriteLine("IndustrialCyka: Old generated {0} unity", generated.Value);
            Fix32 multi = Fix32.FromFloat(Main.unityGenerationMultiplier.Value);
            generated = new Upoints(generated.Value * multi);
//            Console.WriteLine("IndustrialCyka: New generated {0} unity", generated.Value);
        }

        [HarmonyTranspiler]
        [HarmonyPatch(typeof(Machine), "updateWorkOnRecipes")]
        static IEnumerable<CodeInstruction> unityBooster(IEnumerable<CodeInstruction> instructions) {
            Dictionary<int, OpCode> matchTable = new Dictionary<int, OpCode>();
            matchTable[0] = OpCodes.Ldsfld;
            matchTable[1] = OpCodes.Br;
            matchTable[2] = OpCodes.Ldc_I4_2;
            int matches = 0;
            int totalMatch = matchTable.Count;

            var codes = new List<CodeInstruction>(instructions);
            for (int i = 0; i < codes.Count; i++) {
                if (matches >= totalMatch) {
                    break;
                }

                if (codes[i].opcode.Equals(matchTable[matches])) {
                    if (matches == totalMatch - 1) {
                        codes[i].opcode = OpCodes.Ldc_I4_6;
                    }

                    matches++;
                }
            }

            // for (int i = 0; i < codes.Count; i++) {
            //     Console.WriteLine(codes[i].ToString());
            // }

            return codes.AsEnumerable();
        }

        // Could not make this work either... idk why... Doesn't make sense...
        // [HarmonyPostfix]
        // [HarmonyPatch(typeof(SimpleVirtualResource), "MineResourceAt")]
        // static void infiniteGroundResources(SimpleVirtualResource __instance) {
//        //     Console.WriteLine("IndustrialCyka: Patching ground resources (Capacity)");
        //     Traverse traverse = Traverse.Create(__instance);
        //     Traverse capacityField = traverse.Field("Capacity");
        //     capacityField.SetValue(1000000000);
        //     Traverse quantityField = traverse.Field("Quantity");
        //     quantityField.SetValue(1000000000);
        // }

        [HarmonyPrefix]
        [HarmonyPatch(typeof(ProductBuffer), "ForceNewCapacityTo")]
        static void bufferCapacityMultiplier(ref Quantity newCapacity) {
            if (newCapacity.Value <= 0) {
                return;
            }

            Quantity newNewCapacity = new Quantity((int)(newCapacity.Value * Main.bufferCapacityMultiplier.Value));
            newCapacity = newNewCapacity;
        }

        [HarmonyPostfix]
        [HarmonyPatch(typeof(CargoDepotModuleProto), MethodType.Constructor,
            new[] {
                typeof(CargoDepotModuleProto.ID),
                typeof(Proto.Str),
                typeof(EntityLayout),
                typeof(ProductType),
                typeof(Option<CargoDepotModuleProto>),
                typeof(Quantity),
                typeof(Quantity),
                typeof(Duration),
                typeof(Electricity),
                typeof(bool),
                typeof(Percent),
                typeof(EntityCosts),
                typeof(CargoDepotModuleProto.Gfx),
                typeof(IEnumerable<Tag>),
            })]
        static void depotTransferSpeedMultiplier(CargoDepotModuleProto __instance) {
            // Console.WriteLine("IndustrialCyka: Old storage capacity: {0}", __instance.Capacity.Value);
            Traverse traverse = Traverse.Create(__instance);

            traverse.Field("QuantityPerExchange")
                .SetValue(new Quantity((int)(__instance.QuantityPerExchange.Value *
                                             Main.depotTransferSpeedMultiplier.Value)));

//            Console.WriteLine("IndustrialCyka: New storage capacity: {0}", __instance.Capacity.Value);
        }

        [HarmonyPostfix]
        [HarmonyPatch(typeof(WorldMapMineProto), MethodType.Constructor,
            new[] {
                typeof(EntityProto.ID),
                typeof(Proto.Str),
                typeof(ProductQuantity),
                typeof(Duration),
                typeof(Upoints),
                typeof(UpointsCategoryProto),
                typeof(EntityCosts),
                typeof(Func<int, EntityCosts>),
                typeof(int),
                typeof(Quantity),
                typeof(WorldMapEntityProto.Gfx),
                typeof(int),
                typeof(int),
                typeof(IEnumerable<Tag>),
            })]
        static void worldMineSpeedMultiplier(WorldMapMineProto __instance) {
            // Console.WriteLine("Before: {0}", __instance.ProducedProductPerStep.Quantity.Value);
            ProductQuantity newProductQuantity = new ProductQuantity(__instance.ProducedProductPerStep.Product,
                new Quantity((int)(__instance.ProducedProductPerStep.Quantity.Value *
                                   Main.worldMineSpeedMultiplier.Value)));

            Traverse.Create(__instance)
                .Field("ProducedProductPerStep")
                .SetValue(newProductQuantity);
            // Console.WriteLine("After: {0}", __instance.ProducedProductPerStep.Quantity.Value);
        }

        [HarmonyPostfix]
        [HarmonyPatch(typeof(WorldMapMineProto), MethodType.Constructor,
            new[] {
                typeof(EntityProto.ID),
                typeof(Proto.Str),
                typeof(ProductQuantity),
                typeof(Duration),
                typeof(Upoints),
                typeof(UpointsCategoryProto),
                typeof(EntityCosts),
                typeof(Func<int, EntityCosts>),
                typeof(int),
                typeof(Quantity),
                typeof(WorldMapEntityProto.Gfx),
                typeof(int),
                typeof(int),
                typeof(IEnumerable<Tag>),
            })]
        static void worldMineLevelIncrementMultiplier(WorldMapMineProto __instance) {
            Traverse traverse = Traverse.Create(__instance);

            // int level = traverse.Field("Level").GetValue<int>();
            int maxLevel = traverse.Field("MaxLevel").GetValue<int>();
            // int levelsPerUpgrade = traverse.Field("LevelsPerUpgrade").GetValue<int>();

            int newLevelsPerUpgrade = 10;
            int newMaxLevel = (int)Math.Ceiling((double)maxLevel / newLevelsPerUpgrade) * newLevelsPerUpgrade;

            // Console.WriteLine("Level: {0}, MaxLevel: {1}, LevelsPerUpgrade: {2}", level, maxLevel, levelsPerUpgrade);
            // Console.WriteLine("NewMaxLevel: {0}", newMaxLevel);

            traverse.Field("MaxLevel").SetValue(newMaxLevel);
            traverse.Field("LevelsPerUpgrade").SetValue(newLevelsPerUpgrade);
        }

        [HarmonyTranspiler]
        [HarmonyPatch(typeof(WorldMapMineProto), MethodType.Constructor,
            new[] {
                typeof(EntityProto.ID),
                typeof(Proto.Str),
                typeof(ProductQuantity),
                typeof(Duration),
                typeof(Upoints),
                typeof(UpointsCategoryProto),
                typeof(EntityCosts),
                typeof(Func<int, EntityCosts>),
                typeof(int),
                typeof(Quantity),
                typeof(WorldMapEntityProto.Gfx),
                typeof(int),
                typeof(int),
                typeof(IEnumerable<Tag>),
            })]
        static IEnumerable<CodeInstruction> cookWorldMineLevelIncrementMultiplier(
            IEnumerable<CodeInstruction> instructions) {
            Dictionary<int, OpCode> matchTable = new Dictionary<int, OpCode>();
            matchTable[0] = OpCodes.Rem;
            matchTable[1] = OpCodes.Ldfld;
            matchTable[2] = OpCodes.Ldarg_0;
            matchTable[3] = OpCodes.Ldfld;
            matchTable[4] = OpCodes.Ldarg_0;
            int matches = 0;
            int totalMatch = matchTable.Count;

            var codes = new List<CodeInstruction>(instructions);
            for (int i = codes.Count - 1; i >= 0; i--) {
                if (matches >= totalMatch) {
                    break;
                }

                if (codes[i].opcode.Equals(matchTable[matches])) {
                    if (matches == totalMatch - 1) {
                        codes[i + 4].opcode = OpCodes.Add;
                        codes[i + 5].opcode = OpCodes.Brtrue_S;
                        break;
                    }

                    matches++;
                }
                else {
                    matches = 0;
                }
            }

            return codes.AsEnumerable();
        }

        [HarmonyPostfix]
        [HarmonyPatch(typeof(SettlementHousingModuleProto), MethodType.Constructor, new[] {
            typeof(StaticEntityProto.ID),
            typeof(Proto.Str),
            typeof(EntityLayout),
            typeof(EntityCosts),
            typeof(int),
            typeof(Upoints),
            typeof(ImmutableArray<KeyValuePair<ImmutableArray<PopNeedProto>, Percent>>),
            typeof(IReadOnlyDictionary<PopNeedProto, Percent>),
            typeof(Option<SettlementHousingModuleProto>),
            typeof(SettlementHousingModuleProto.Gfx),
        })]
        static void housingCapacityMultiplier(SettlementHousingModuleProto __instance) {
            Traverse.Create(__instance).Field<int>("Capacity").Value =
                (int)(__instance.Capacity * Main.housingCapacityMultiplier.Value);
        }


        [HarmonyPostfix]
        [HarmonyPatch(typeof(StackerProto), MethodType.Constructor, new[] {
            typeof(StaticEntityProto.ID),
            typeof(Proto.Str),
            typeof(EntityLayout),
            typeof(EntityCosts),
            typeof(Electricity),
            typeof(ThicknessTilesI),
            typeof(RelTile3i),
            typeof(Duration),
            typeof(Duration),
            typeof(StackerProto.Gfx),
            typeof(ThicknessTilesI),
            typeof(IEnumerable<Tag>),
        })]
        static void dumpDelayMultiplier(StackerProto __instance) {
            Duration newDelay =
                new Duration(Math.Max((int)(__instance.DumpDelay.Ticks * Main.dumpDelayMultiplier.Value), 1));
            Traverse.Create(__instance).Field("DumpDelay").SetValue(newDelay);
        }


        [HarmonyPrefix]
        [HarmonyPatch(typeof(TransportProto), MethodType.Constructor, new[] {
            typeof(StaticEntityProto.ID),
            typeof(Proto.Str),
            typeof(ThicknessTilesF),
            typeof(Quantity),
            typeof(RelTile1f),
            typeof(RelTile1f),
            typeof(RelTile1i),
            typeof(bool),
            typeof(bool),
            typeof(Option<TerrainTileSurfaceProto>),
            typeof(RelTile1i),
            typeof(IoPortShapeProto),
            typeof(Electricity),
            typeof(Percent),
            typeof(bool),
            typeof(bool),
            typeof(EntityCosts),
            typeof(RelTile1i),
            typeof(Duration),
            typeof(Option<TransportProto>),
            typeof(VirtualProductProto),
            typeof(Quantity),
            typeof(TransportProto.Gfx),
        })]
        static void transportSpeedMultiplier(ref StaticEntityProto.ID id, ref Proto.Str strings,
            ref ThicknessTilesF surfaceRelativeHeight, ref Quantity maxQuantityPerTransportedProduct,
            ref RelTile1f transportedProductsSpacing, ref RelTile1f speedPerTick, ref RelTile1i zStepLength,
            ref bool needsPillarsAtGround, ref bool canBeBuried,
            ref Option<TerrainTileSurfaceProto> tileSurfaceWhenOnGround, ref RelTile1i maxPillarSupportRadius,
            ref IoPortShapeProto portsShape, ref Electricity baseElectricityCost, ref Percent cornersSharpnessPercent,
            ref bool allowMixedProducts, ref bool isBuildable, ref EntityCosts costs, ref RelTile1i lengthPerCost,
            ref Duration constructionDurationPerProduct, ref Option<TransportProto> nextTier,
            ref VirtualProductProto maintenanceProduct, Quantity maintenancePerTile, ref TransportProto.Gfx graphics) {
            if (TransportPillarProto.MAX_PILLAR_HEIGHT.Value < 25)
                typeof(TransportPillarProto).GetField("MAX_PILLAR_HEIGHT").SetValue(null, new ThicknessTilesI(25));
            if (IoPort.MAX_TRANSFER_PER_TICK.Value < 10000)
                typeof(IoPort).GetField("MAX_TRANSFER_PER_TICK").SetValue(null, new Quantity(10000));

            maxQuantityPerTransportedProduct *= 1000;
            maxQuantityPerTransportedProduct = maxQuantityPerTransportedProduct.Min(new Quantity(10000));
            Console.WriteLine("maxQuantityPerTransportedProduct {0}", maxQuantityPerTransportedProduct);

            speedPerTick *= 10;
            transportedProductsSpacing = speedPerTick;
            zStepLength = RelTile1i.One;
            canBeBuried = true;
            maxPillarSupportRadius = new RelTile1i(8);
        }
    }
}
