using Barotrauma;
using HarmonyLib;
using System.Reflection;
using System.Linq;
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System;
using Microsoft.Xna.Framework;
using System.ComponentModel;
using Barotrauma.Networking;

namespace BaroMod_sjx
{
	partial class ItemBoxImpl : ACsMod
	{
		const string harmony_id = "com.sjx.ItemIOFramework";
		/*
		const string box_identifier = "ItemBox";
		const float max_condition = 1.0f;
		const int item_count = 1024;
		const float increment = max_condition / item_count;
		*/
		private readonly Harmony harmony;

		public ItemBoxImpl()
		{
			harmony = new Harmony(harmony_id);
			harmony.PatchAll(Assembly.GetExecutingAssembly());
			Barotrauma.DebugConsole.AddWarning("Loaded ItemBox Impl");
		}

		public override void Stop()
		{
			harmony.UnpatchAll(harmony_id);
		}



		static Dictionary<Type, List<ItemComponent>> get_componentsByType(Item item)
		{ 
			return (AccessTools.Field(typeof(Item), "componentsByType").GetValue(item)! as Dictionary<Type, List<ItemComponent>>)!; 
		}

		[HarmonyPatch(typeof(Inventory))]
		class Patch_PutItem
		{
			static MethodBase TargetMethod()
			{
				Barotrauma.DebugConsole.AddWarning("Patch_PutItem TargetMethod");
				return AccessTools.Method(typeof(Inventory), "PutItem");
			}

			public class context
			{
				public Character user;
				public ConditionStorage target;
				public context(Character user, ConditionStorage target)
				{
					this.user = user;
					this.target = target;
				}
			}

			public static bool Prefix(Inventory __instance, Character user, int i, out context? __state)
			{
				__state = null;
				ConditionStorage? target = ConditionStorage.GetFromInventory(__instance);
				if (target != null && i == target.slotIndex)
				{
					__state = new context(user, target);
				}
				return true;
			}
			public static void Postfix(context? __state)
			{
				if (__state != null)
				{
					__state.target.OnPutItemDone(__state.user);
				}
			}
		}

		[HarmonyPatch(typeof(Inventory), nameof(Inventory.RemoveItem))]
		class Patch_RemoveItem
		{
			public static bool Prefix(Inventory __instance, out ConditionStorage? __state, Item item)
			{
				__state = null;
				// do not add items if sub is unloading or if removed for overflow.
				if (!Submarine.Unloading)
				{
					ConditionStorage? target = ConditionStorage.GetFromInventory(__instance);
					if (target != null)
					{
						if (target.GetSlot()?.Contains(item) ?? false)
						{
							if (target.flag_remove_no_spawn)
							{
								target.flag_remove_no_spawn = false;
							}
							else
							{
								target.QualityStacked = item.Quality;
								target.ConditionStacked = item.Condition;
								target.item_type = item.Prefab;
								__state = target;
							}
						}
					}
				}
				return true;
			}
			public static void Postfix(ConditionStorage? __state)
			{
				if (__state != null)
				{
					__state.OnRemoveItemDone();
				}
			}
		}

		[HarmonyPatch(typeof(Inventory))]
		class Patch_TrySwapping
		{
			static MethodBase TargetMethod()
			{
				return AccessTools.Method(typeof(Inventory), "TrySwapping");
			}

			public static bool Prefix(Inventory __instance, Item item, ref bool __result)
			{
				if (ConditionStorage.GetFromInventory(__instance) != null ||
					(item != null && item.ParentInventory != null && ConditionStorage.GetFromInventory(item.ParentInventory) != null))
				{
					__result = false;
					return false;
				}
				return true;
			}
		}

		[HarmonyPatch(typeof(Inventory))]
		class Patch_CreateNetworkEvent
		{
			static MethodBase TargetMethod()
			{
				return AccessTools.Method(typeof(Inventory), "CreateNetworkEvent");
			}

			public static bool Prefix(Inventory __instance, out ConditionStorage? __state)
			{
				__state = null;
				if (GameMain.NetworkMember != null)
				{
					__state = ConditionStorage.GetFromInventory(__instance);
				}
				return true;
			}

			public static void Postfix(ConditionStorage? __state)
			{
				if (__state != null)
				{
					__state.SyncItemCount();
				}
			}
		}
	}

	partial class ConditionStorage : ItemComponent
	{
		private readonly struct EventData : IEventData
		{
			public readonly int ItemCount;

			public EventData(int ItemCount)
			{
				this.ItemCount = ItemCount;
			}
		}

		[Serialize(0, IsPropertySaveable.No, description: "Index of the stacking slot in same item's ItemContainer component")]
		public int slotIndex { get; private set; }

		[Serialize(true, IsPropertySaveable.No, description: "Shows count and percentage of stacking item")]
		public bool showCount { get; private set; }

		[Serialize(1024, IsPropertySaveable.No, description: "Maximum number of items stacked within")]
		public int maxItemCount { get; private set; }

		[Serialize(true, IsPropertySaveable.No, description: "Shows icon of stacking item")]
		public bool showIcon { get; private set; }

		[Serialize(0.6f, IsPropertySaveable.No, description: "icon scale compared to full")]
		public float iconScale { get; private set; }

		[Serialize(0.0f, IsPropertySaveable.No, description: "shift x of icon")]
		public float iconShiftX { get; private set; }

		[Serialize(0.1f, IsPropertySaveable.No, description: "shift y of icon, down is positive")]
		public float iconShiftY { get; private set; }

		[Editable(minValue: 0, maxValue: int.MaxValue), Serialize(0, IsPropertySaveable.Yes, description: "Current item count")]
		// camel case needed for save compatibility
		public int currentItemCount
		{
			get => _currentItemCount;
			// assume set by 
			set
			{
				SetItemCount(value, false);
			}
		}

		void SetItemCount(int value, bool is_network_event = false)
		{
			if (is_network_event || GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
			// authoritative number. will need to send to client later if server.
			{
				if (value != _currentItemCount)
				{
					OnCountActualChanged();
				}
			}
			// predicted number. need to be reset later
			else
			{
				if (value != _currentItemCount)
				{
					OnCountPredictionChanged();
				}
			}
			IsActive = true;
			_currentItemCount = value;
		}

		public ItemInventory itemInventory => Item.OwnInventory;
		public ItemContainer itemContainer => Item.GetComponent<ItemContainer>();

		public int _currentItemCount;

		// replace setting parent container hack, so that harpoon guns work correctly
		public bool flag_remove_no_spawn;

		partial void OnCountActualChanged();
		partial void OnCountPredictionChanged();


		[Editable, Serialize("", IsPropertySaveable.Yes, description: "current stacked item")]
		public Identifier ItemIdentifier
		{
			get
			{
				return item_type?.Identifier ?? "";
			}
			set
			{
				if (value.IsEmpty)
				{
					item_type = null;
				}
				else
				{
					item_type = ItemPrefab.Find("", value.ToIdentifier());
				}
			}
		}

		public ItemPrefab? item_type;

		[Editable(MinValueInt = 0, MaxValueInt = Quality.MaxQuality), Serialize(0, IsPropertySaveable.Yes, description: "current stacked item quality")]
		public int QualityStacked { get; set; }

		[Editable, Serialize(float.NaN, IsPropertySaveable.Yes, description: "current stacked item condition")]
		public float ConditionStacked { get; set; }



		public ConditionStorage(Item item, ContentXElement element) : base(item, element) { }

		public bool IsFull => currentItemCount >= maxItemCount;
		public bool IsEmpty() => currentItemCount <= 0;

		public void SyncItemCount()
		{
#if SERVER
			Item.CreateServerEvent(this, new EventData(currentItemCount));
#endif
		}

		public override void Update(float deltaTime, Camera cam)
		{
			base.Update(deltaTime, cam);
			SyncItemCount();
			IsActive = false;
		}

		public static int SlotPreserveCount(ItemPrefab prefab, Inventory inventory, ItemContainer container, int slot_index)
		{
			int resolved_stack_size = Math.Min(Math.Min(prefab.GetMaxStackSize(inventory), container.GetMaxStackSize(slot_index)), Inventory.MaxPossibleStackSize);
			if (resolved_stack_size <= 1)
			{
				return 1;
			}
			else
			{
				return resolved_stack_size - 1;
			}
		}

		public static ConditionStorage? GetFromInventory(Inventory inventory)
		{
			if (inventory.Owner is Item parentItem)
			{
				return parentItem.GetComponent<ConditionStorage>();
			}
			else
			{
				return null;
			}
		}

		public Inventory.ItemSlot? GetSlot()
		{
			Inventory.ItemSlot[] slots = (AccessTools.Field(typeof(Inventory), "slots").GetValue(itemInventory)! as Inventory.ItemSlot[])!;
			if (slotIndex >= slots.Length)
			{
				DebugConsole.LogError($"ConditionStorage of {Item.Prefab.Identifier} specified index {slotIndex} out of {slots.Length}!");
				return null;
			}
			return slots[slotIndex];
		}

		public void OnPutItemDone(Character user)
		{
			ItemContainer container = itemContainer;
			Inventory.ItemSlot target_slot;
			{
				Inventory.ItemSlot[] slots = (AccessTools.Field(typeof(Inventory), "slots").GetValue(itemInventory)! as Inventory.ItemSlot[])!;
				if (slotIndex >= slots.Length)
				{
					DebugConsole.LogError($"ConditionStorage of {Item.Prefab.Identifier} specified index {slotIndex} out of {slots.Length}!");
					return;
				}
				target_slot = slots[slotIndex];
			}

			if (target_slot.Items.Any())
			{
				QualityStacked = target_slot.Items.First().Quality;
				ConditionStacked = target_slot.Items.First().Condition;
				item_type = target_slot.Items.First().Prefab;
				if (!IsFull)
				{
					//bool edited = false;	
					int preserve = SlotPreserveCount(target_slot.Items.First().Prefab, itemInventory, container, slotIndex);
					var it = target_slot.Items.ToArray().AsEnumerable().GetEnumerator();
					while (it.MoveNext() && !IsFull)
					{
						if (preserve > 0)
						{
							preserve--;
						}
						else if (Entity.Spawner != null)
						{
							// client cannot despawn items, single player needs to despawn
							Entity.Spawner.AddItemToRemoveQueue(it.Current);
							SetItemCount(currentItemCount + 1);
							flag_remove_no_spawn = true;
							itemInventory.RemoveItem(it.Current);
							break;
						}
					}
				}
			}
		}

		public void OnRemoveItemDone()
		{
			Inventory.ItemSlot target_slot;
			{
				Inventory.ItemSlot[] slots = (AccessTools.Field(typeof(Inventory), "slots").GetValue(itemInventory)! as Inventory.ItemSlot[])!;
				if (slotIndex >= slots.Length)
				{
					DebugConsole.LogError($"ConditionStorage of {(itemInventory.Owner as Item)!.Prefab.Identifier} specified index {slotIndex} out of {slots.Length}!");
					return;
				}
				target_slot = slots[slotIndex];
			}

			int preserve = SlotPreserveCount(item_type!, itemInventory, itemContainer, slotIndex);
			int spawn_count = preserve - target_slot.Items.Count;
			int can_spawn = Math.Min(spawn_count, currentItemCount);

			// other may be queued, so spawn only one
			if (can_spawn > 0)
			{
				if (Entity.Spawner != null)
				{
					SetItemCount(currentItemCount - 1);

					Item.Spawner.AddItemToSpawnQueue(item_type, itemInventory,
							ConditionStacked, QualityStacked, spawnIfInventoryFull: true);
				}
			}
		}
	}
}
