using System;
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.Shipyard;
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.Recipes;
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.SaveGame;
using Mafi.Core.Terrain;
using Mafi.Core.Vehicles.Excavators;
using Mafi.Core.World.Entities;

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

		[HarmonyPrefix]
		[HarmonyPatch(typeof(ExcavatorProtoBuilder.ExcavatorProtoBuilderState), "SetMinedThicknessByDistanceMeters")]
		static void excavatorMiningAreaMultiplier(ref float[] thicknessMeters) {
			for (int i = 0; i < thicknessMeters.Length; i++) {
				if (Main.debugMode.Value)
					Console.WriteLine("IndustrialCyka: Old mining area {0}", thicknessMeters[i]);
				thicknessMeters[i] *= Main.excavatorMiningCapacityMultiplier.Value;
				if (Main.debugMode.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) {
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Old min mining distance {0}, max mining distance {1}",
					minMiningDistance, maxMiningDistance);
			minMiningDistance = new RelTile1i((int)(minMiningDistance.Value * Main.excavatorMinReachMultiplier.Value));
			maxMiningDistance = new RelTile1i((int)(maxMiningDistance.Value * Main.excavatorMaxReachMultiplier.Value));
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: New min mining distance {0}, max mining distance {1}",
					minMiningDistance, maxMiningDistance);
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(TruckProtoBuilder.TruckProtoBuilderState), "SetCapacity")]
		static void truckCapacityMultiplier(ref int quantity) {
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Old truck capacity {0}", quantity);
			quantity *= Main.truckCapacityMultiplier.Value;
			if (Main.debugMode.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) {
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Old ship capacity {0}", __instance.Capacity);
			__instance.Capacity = new Quantity(__instance.Capacity.Value * Main.cargoShipCapacityMultiplier.Value);
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: New ship capacity {0}", __instance.Capacity);
		}

		// 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) {
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Old storage capacity {0}", __instance.Capacity);
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Old storage transfer limit {0}", __instance.TransferLimit);
			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));

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

		[HarmonyPrefix]
		[HarmonyPatch(typeof(UpointsManager), "GenerateUnity")]
		static void unityGenerationMultiplier(Proto.ID categoryId, ref Upoints generated) {
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Old generated {0} unity", generated.Value);
			Fix32 multi = Fix32.FromFloat(Main.unityGenerationMultiplier.Value);
			generated = new Upoints(generated.Value * multi);
			if (Main.debugMode.Value)
				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;
			}

			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Old buffer capacity {0}", newCapacity);
			Quantity newNewCapacity = new Quantity((int)(newCapacity.Value * Main.bufferCapacityMultiplier.Value));
			newCapacity = newNewCapacity;
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: New buffer capacity {0}", newCapacity);
		}

		[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) {
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Old depot transfer speed {0}", __instance.QuantityPerExchange);
			Traverse traverse = Traverse.Create(__instance);

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

			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: New depot transfer speed {0}", __instance.QuantityPerExchange);
		}

		[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) {
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Old world mine speed {0}",
					__instance.ProducedProductPerStep.Quantity);
			ProductQuantity newProductQuantity = new ProductQuantity(__instance.ProducedProductPerStep.Product,
				new Quantity((int)(__instance.ProducedProductPerStep.Quantity.Value *
				                   Main.worldMineSpeedMultiplier.Value)));

			Traverse.Create(__instance)
				.Field("ProducedProductPerStep")
				.SetValue(newProductQuantity);
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: New world mine speed {0}",
					__instance.ProducedProductPerStep.Quantity);
		}

		[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;

			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Old max level {0}, new max level {1}", maxLevel, 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) {
						if (Main.debugMode.Value)
							Console.WriteLine("IndustrialCyka: Patching world mine level increment");
						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) {
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Old housing capacity {0}", __instance.Capacity);
			Traverse.Create(__instance).Field<int>("Capacity").Value =
				(int)(__instance.Capacity * Main.housingCapacityMultiplier.Value);
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: New housing capacity {0}", __instance.Capacity);
		}

		[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) {
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Old dumpDelay {0}", __instance.DumpDelay);
			Duration newDelay =
				new Duration(Math.Max((int)(__instance.DumpDelay.Ticks * Main.dumpDelayMultiplier.Value), 1));
			Traverse.Create(__instance).Field("DumpDelay").SetValue(newDelay);
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: New dumpDelay {0}", __instance.DumpDelay);
		}

		[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));

			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Old maxQuantityPerTransportedProduct {0}",
					maxQuantityPerTransportedProduct);
			maxQuantityPerTransportedProduct *= 1000;
			maxQuantityPerTransportedProduct = maxQuantityPerTransportedProduct.Min(new Quantity(10000));
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: New maxQuantityPerTransportedProduct {0}",
					maxQuantityPerTransportedProduct);

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

		[HarmonyPostfix]
		[HarmonyPatch(typeof(ShipyardProto), MethodType.Constructor, new[] {
			typeof(StaticEntityProto.ID),
			typeof(Proto.Str),
			typeof(EntityLayout),
			typeof(EntityCosts),
			typeof(bool),
			typeof(Quantity),
			typeof(Option<ShipyardProto>),
			typeof(ImmutableArray<ImmutableArray<RectangleTerrainArea2iRelative>>),
			typeof(StackerProto.Gfx),
			typeof(ImmutableArray<String>),
			typeof(bool),
		})]
		static void shipyardCargoMultiplier(ShipyardProto __instance) {
			Traverse.Create(__instance).Field("CargoCapacity").SetValue(new Quantity(
				(int)(__instance.CargoCapacity.Value * Main.shipyardCargoMultiplier.Value)));
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(SaveManager), "isTimeForAutoSave")]
		static void autosaveInterval(ref bool __result, SaveManager __instance) {
			if (Main.autosaveInterval.Value <= 0) {
				__result = false;
				return;
			}

			var trav = Traverse.Create(__instance);
			var lastSaveTime = trav.Field("m_lastSaveTime").GetValue<long>();
			var num = (Environment.TickCount - lastSaveTime).Abs() / 1000L;
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Autosave interval: {0}", num);
			__result = num > Main.autosaveInterval.Value;
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(MachineProto), nameof(MachineProto.AddRecipe))]
		static void machineProtoAddRecipe(MachineProto __instance, ref RecipeProto recipe) {
			Console.WriteLine("Recipe outputs for {0}:", __instance.Id);
			recipe.AllOutputs.ForEach(o => {
				var id = o.Product.Id.ToString();
				Console.WriteLine("\tIndustrialCyka: Recipe output {0} x {1}", id, o.Quantity);
				if (!Main.recipeMultipliersDict.ContainsKey(id)) {
					if (Main.debugMode.Value)
						Console.WriteLine("IndustrialCyka: Recipe output {0} not found in multipliers, nothing to do", id);
					return;
				}
				var multiplier = Main.recipeMultipliersDict[id];
				var trav = Traverse.Create(o);
				trav.Field("Quantity").SetValue(new Quantity(trav.Field("Quantity").GetValue<Quantity>().Value * multiplier));
				if (Main.debugMode.Value)
					Console.WriteLine("IndustrialCyka: Recipe output {0} changed to {1}", id, trav.Field("Quantity").GetValue<Quantity>());
			});
		}
	}

	[HarmonyPatch(typeof(Excavator), "MineMixedAt")]
	public class MineMixedAtPatch {
		public static Dictionary<ExcavatorProto, Quantity>
			originalCapacity = new Dictionary<ExcavatorProto, Quantity>();

		public static Dictionary<ExcavatorProto, ImmutableArray<ThicknessTilesF>> originalMinedThicknessByDistance =
			new Dictionary<ExcavatorProto, ImmutableArray<ThicknessTilesF>>();

		public static Dictionary<ExcavatorProto.Timings, int> originalMineTileIterations =
			new Dictionary<ExcavatorProto.Timings, int>();

		public static Dictionary<ExcavatorProto, RelTile1i> originalMaxMiningDistance =
			new Dictionary<ExcavatorProto, RelTile1i>();

		static void Prefix(ref Excavator __instance) {
			if (!originalCapacity.ContainsKey(__instance.Prototype)) {
				originalCapacity.Add(__instance.Prototype, __instance.Prototype.Capacity);
			}

			var trav = Traverse.Create(__instance.Prototype);
			trav.Field("Capacity")
				.SetValue(new Quantity(originalCapacity[__instance.Prototype].Value *
				                       Main.excavatorCapacityMultiplier.Value));
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Excavator capacity: {0}", __instance.Prototype.Capacity.Value);

			if (!originalMinedThicknessByDistance.ContainsKey(__instance.Prototype)) {
				originalMinedThicknessByDistance.Add(__instance.Prototype,
					trav.Field("MinedThicknessByDistance").GetValue<ImmutableArray<ThicknessTilesF>>());
			}

			trav.Field("MinedThicknessByDistance")
				.SetValue(originalMinedThicknessByDistance[__instance.Prototype]
					.SelectMany(t => Enumerable.Repeat(t, Main.excavatorMiningAreaMultiplier.Value))
					.Select(t =>
						new ThicknessTilesF((int)(t.Value.ToFloat() * Main.excavatorMiningCapacityMultiplier.Value)))
					.ToImmutableArray());
			if (Main.debugMode.Value)
				originalMinedThicknessByDistance[__instance.Prototype].ForEach(t => Console.WriteLine(t.Value));
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Excavator mined thickness: {0}",
					__instance.Prototype.MinedThicknessByDistance);

			if (!originalMaxMiningDistance.ContainsKey(__instance.Prototype)) {
				originalMaxMiningDistance.Add(__instance.Prototype,
					trav.Field("MaxMiningDistance").GetValue<RelTile1i>());
			}

			var value = originalMaxMiningDistance[__instance.Prototype];
			var newValueMiningDistance = new RelTile1i((int)(value.Value * Main.excavatorMaxReachMultiplier.Value));
			trav.Field("MaxMiningDistance")
				.SetValue(newValueMiningDistance);
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Excavator max mining distance: {0}",
					__instance.Prototype.MaxMiningDistance);

			var trav2 = Traverse.Create(__instance.Prototype.MineTimings);
			if (!originalMineTileIterations.ContainsKey(__instance.Prototype.MineTimings)) {
				originalMineTileIterations.Add(__instance.Prototype.MineTimings,
					trav2.Field("MineTileIterations").GetValue<int>());
			}

			var newValue = (int)(originalMineTileIterations[__instance.Prototype.MineTimings] *
			                     Main.excavatorMineTileIterationsMultiplier.Value);
			trav2.Field("MineTileIterations").SetValue(newValue);
			trav2.Field("PrepareToMineDuration").SetValue(new Duration(1));
			trav2.Field("MineDuration").SetValue(new Duration(1));
			trav2.Field("PrepareToDumpDuration").SetValue(new Duration(1));
			trav2.Field("DumpDuration").SetValue(new Duration(1));
			if (Main.debugMode.Value)
				Console.WriteLine("IndustrialCyka: Excavator mine tile iterations 2: {0}",
					trav2.Field("MineTileIterations").GetValue<int>());
		}
	}
}