﻿using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using JetBrains.Annotations;
using UnityEngine;

namespace FiniteItemRepairs
{
    [UsedImplicitly]
    public class FiniteItemRepairsPatch
    {
        private const string MetaDataName = "DurabilityModifier";

        private static readonly PassiveEffects EnumDegradationMax = EnumUtils.Parse<PassiveEffects> (nameof (PassiveEffects.DegradationMax));
        private const string CustomNameDegradationMax = "DegradationMax";


        [HarmonyPatch(typeof(XUiC_RecipeStack))]
        [HarmonyPatch(nameof(XUiC_RecipeStack.outputStack))]
        public static class XUiC_RecipeStack_outputStack
        {
            private static readonly MethodInfo VanillaItemValueCloneMethod = AccessTools.Method(typeof(ItemValue), "Clone");
            private static readonly MethodInfo GetHandleDegradationMethod = SymbolExtensions.GetMethodInfo(() => HandleDegradation(null));

            [UsedImplicitly]
            private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
            {
                return new CodeMatcher(instructions)
                    .MatchForward(true,
                        new CodeMatch(OpCodes.Ldarg_0),
                        new CodeMatch(OpCodes.Ldfld),
                        new CodeMatch(OpCodes.Callvirt, VanillaItemValueCloneMethod)
                    ).InsertAndAdvance(
                        new CodeInstruction(OpCodes.Dup),
                        new CodeInstruction(OpCodes.Call, GetHandleDegradationMethod)
                    ).InstructionEnumeration();
            }

            private static void HandleDegradation(ItemValue outItem)
            {
                float currentDurabilityModifier = 1;
                if (outItem.HasMetadata(MetaDataName, TypedMetadataValue.TypeTag.Float))
                {
                    object metadata = outItem.GetMetadata(MetaDataName);
                    currentDurabilityModifier = (float)metadata;
                }

                currentDurabilityModifier = (1 - (1 - outItem.PercentUsesLeft) * Settings.Data.DegradationPercent) * currentDurabilityModifier;
                currentDurabilityModifier = Mathf.Max(currentDurabilityModifier, 0.01f);

                outItem.SetMetadata(MetaDataName, currentDurabilityModifier, TypedMetadataValue.TypeTag.Float);
            }
        }


        [HarmonyPatch(typeof(ItemValue))]
        [HarmonyPatch(nameof(ItemValue.MaxUseTimes))]
        [HarmonyPatch(MethodType.Getter)]
        public static class ItemValue_MaxUseTimes
        {
            [UsedImplicitly]
            private static void Postfix(ref int __result, ItemValue __instance)
            {
                __result = ModMaxUseTimes(__result, __instance);
            }
        }

        [HarmonyPatch(typeof(XUiM_ItemStack))]
        [HarmonyPatch(nameof(XUiM_ItemStack.GetStatItemValueTextWithCompareInfo))]
        public static class XUiM_ItemStack_GetStatItemValueTextWithCompareInfo
        {
            [UsedImplicitly]
            private static void Prefix (DisplayInfoEntry infoEntry)
            {
                if (infoEntry.StatType != EnumDegradationMax)
                {
                    return;
                }

                infoEntry.CustomName = CustomNameDegradationMax;
            }
        }

        [HarmonyPatch(typeof(XUiM_ItemStack))]
        [HarmonyPatch(nameof(XUiM_ItemStack.GetStatItemValueTextWithModColoring))]
        public static class XUiM_ItemStack_GetStatItemValueTextWithModColoring
        {
            [UsedImplicitly]
            private static void Prefix (DisplayInfoEntry infoEntry)
            {
                if (infoEntry.StatType != EnumDegradationMax)
                {
                    return;
                }

                infoEntry.CustomName = CustomNameDegradationMax;
            }
        }

        [HarmonyPatch(typeof(XUiM_ItemStack))]
        [HarmonyPatch(nameof(XUiM_ItemStack.GetStatItemValueTextWithModInfo))]
        public static class XUiM_ItemStack_GetStatItemValueTextWithModInfo
        {
            [UsedImplicitly]
            private static void Prefix (DisplayInfoEntry infoEntry)
            {
                if (infoEntry.StatType != EnumDegradationMax)
                {
                    return;
                }

                infoEntry.CustomName = CustomNameDegradationMax;
            }
        }

        [HarmonyPatch(typeof(XUiM_ItemStack))]
        [HarmonyPatch(nameof(XUiM_ItemStack.GetCustomValue))]
        public static class XUiM_ItemStack_GetCustomValue
        {
            [UsedImplicitly]
            private static bool Prefix (ref float __result, DisplayInfoEntry entry, ItemValue itemValue, bool useMods)
            {
                if (entry.StatType != EnumDegradationMax)
                {
                    return true;
                }

                __result = DegradationMaxMod(entry.StatType, itemValue, null, entry.tags, useMods, 0);
                return false;
            }
        }

        private static float DegradationMaxMod (PassiveEffects statType, ItemValue itemValue, EntityPlayer player, FastTags<TagGroup.Global> tags, bool useMods, float value) {
            if (statType != EnumDegradationMax) {
                return value;
            }
        
            value = EffectManager.GetValue(PassiveEffects.DegradationMax, itemValue, 0, player, tags: tags, calcEquipment: false, calcHoldingItem: false, calcProgression: false, calcBuffs: false, useMods: useMods);
            value = ModMaxUseTimes ((int)value, itemValue);
            return value;
        }
        
        private static int ModMaxUseTimes(int value, ItemValue iv)
        {
            if (value <= 0)
            {
                return value;
            }

            if (!iv.HasMetadata(MetaDataName, TypedMetadataValue.TypeTag.Float))
            {
                return value;
            }

            object metadata = iv.GetMetadata(MetaDataName);
            float currentDurabilityModifier = (float)metadata;
            value = Mathf.RoundToInt(value * currentDurabilityModifier);
            value = Mathf.Max(value, 1);
            return value;
        }
    }
}