package com.gmail.nossr50.util.skills;

import static com.gmail.nossr50.util.ItemMetadataUtils.isLegacyAbilityTool;
import static com.gmail.nossr50.util.ItemMetadataUtils.isSuperAbilityBoosted;
import static com.gmail.nossr50.util.ItemMetadataUtils.removeBonusDigSpeedOnSuperAbilityTool;
import static com.gmail.nossr50.util.PotionEffectUtil.getHastePotionEffectType;

import com.gmail.nossr50.config.HiddenConfig;
import com.gmail.nossr50.datatypes.experience.XPGainReason;
import com.gmail.nossr50.datatypes.experience.XPGainSource;
import com.gmail.nossr50.datatypes.interactions.NotificationType;
import com.gmail.nossr50.datatypes.player.McMMOPlayer;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import com.gmail.nossr50.datatypes.skills.SubSkillType;
import com.gmail.nossr50.datatypes.skills.SuperAbilityType;
import com.gmail.nossr50.locale.LocaleLoader;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.ItemMetadataUtils;
import com.gmail.nossr50.util.ItemUtils;
import com.gmail.nossr50.util.Misc;
import com.gmail.nossr50.util.Permissions;
import com.gmail.nossr50.util.player.NotificationManager;
import com.gmail.nossr50.util.player.UserManager;
import com.gmail.nossr50.util.text.StringUtils;
import java.util.Iterator;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.ShapedRecipe;
import org.bukkit.inventory.ShapelessRecipe;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.potion.PotionEffect;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public final class SkillUtils {
    /**
     * This is a static utility class, therefore we don't want any instances of this class. Making
     * the constructor private prevents accidents like that.
     */
    private SkillUtils() {
    }

    public static void applyXpGain(McMMOPlayer mmoPlayer, PrimarySkillType skill, float xp,
            XPGainReason xpGainReason) {
        mmoPlayer.beginXpGain(skill, xp, xpGainReason, XPGainSource.SELF);
    }

    public static void applyXpGain(McMMOPlayer mmoPlayer, PrimarySkillType skill, float xp,
            XPGainReason xpGainReason, XPGainSource xpGainSource) {
        mmoPlayer.beginXpGain(skill, xp, xpGainReason, xpGainSource);
    }

    /*
     * Skill Stat Calculations
     */

    public static String[] calculateLengthDisplayValues(Player player, float skillValue,
            PrimarySkillType skill) {
        int maxLength = mcMMO.p.getSkillTools()
                .getSuperAbilityMaxLength(mcMMO.p.getSkillTools().getSuperAbility(skill));
        int abilityLengthVar = mcMMO.p.getAdvancedConfig().getAbilityLength();
        int abilityLengthCap = mcMMO.p.getAdvancedConfig().getAbilityLengthCap();

        int length;

        if (abilityLengthCap > 0) {
            length = (int) Math.min(abilityLengthCap, 2 + (skillValue / abilityLengthVar));
        } else {
            length = 2 + (int) (skillValue / abilityLengthVar);
        }

        int enduranceLength = PerksUtils.handleActivationPerks(player, length, maxLength);

        if (maxLength != 0) {
            length = Math.min(length, maxLength);
        }

        return new String[]{String.valueOf(length), String.valueOf(enduranceLength)};
    }

    /*
     * Others
     */

    public static int handleFoodSkills(Player player, int eventFoodLevel,
            SubSkillType subSkillType) {
        int curRank = RankUtils.getRank(player, subSkillType);

        int currentFoodLevel = player.getFoodLevel();
        int foodChange = eventFoodLevel - currentFoodLevel;

        foodChange += curRank;

        return currentFoodLevel + foodChange;
    }

    /**
     * Calculate the time remaining until the cooldown expires.
     *
     * @param deactivatedTimeStamp Time of deactivation
     * @param cooldown The length of the cooldown
     * @param player The Player to check for cooldown perks
     * @return the number of seconds remaining before the cooldown expires
     */
    public static int calculateTimeLeft(long deactivatedTimeStamp, int cooldown, Player player) {
        return (int) (((deactivatedTimeStamp + (PerksUtils.handleCooldownPerks(player, cooldown)
                * Misc.TIME_CONVERSION_FACTOR)) - System.currentTimeMillis())
                / Misc.TIME_CONVERSION_FACTOR);
    }

    /**
     * Check if the cooldown has expired. This does NOT account for cooldown perks!
     *
     * @param deactivatedTimeStamp Time of deactivation in seconds
     * @param cooldown The length of the cooldown in seconds
     * @return true if the cooldown is expired
     */
    public static boolean cooldownExpired(long deactivatedTimeStamp, int cooldown) {
        return System.currentTimeMillis()
                >= (deactivatedTimeStamp + cooldown) * Misc.TIME_CONVERSION_FACTOR;
    }

    /**
     * Checks if the given string represents a valid skill
     *
     * @param skillName The name of the skill to check
     * @return true if this is a valid skill, false otherwise
     */
    public static boolean isSkill(String skillName) {
        return mcMMO.p.getGeneralConfig().getLocale().equalsIgnoreCase("en_US") ?
                mcMMO.p.getSkillTools().matchSkill(skillName) != null : isLocalizedSkill(skillName);
    }

    public static void sendSkillMessage(Player player, NotificationType notificationType,
            String key) {
        Location location = player.getLocation();

        for (Player otherPlayer : player.getWorld().getPlayers()) {
            if (otherPlayer != player && Misc.isNear(location, otherPlayer.getLocation(),
                    Misc.SKILL_MESSAGE_MAX_SENDING_DISTANCE)) {
                NotificationManager.sendNearbyPlayersInformation(otherPlayer, notificationType, key,
                        player.getName());
            }
        }
    }

    public static void handleAbilitySpeedIncrease(Player player) {
        if (HiddenConfig.getInstance().useEnchantmentBuffs()) {
            ItemStack heldItem = player.getInventory().getItemInMainHand();

            if (heldItem == null) {
                return;
            }

            if (!ItemUtils.canBeSuperAbilityDigBoosted(heldItem)) {
                return;
            }

            int originalDigSpeed = heldItem.getEnchantmentLevel(
                    mcMMO.p.getEnchantmentMapper().getEfficiency());
            ItemUtils.addDigSpeedToItem(heldItem,
                    heldItem.getEnchantmentLevel(mcMMO.p.getEnchantmentMapper().getEfficiency()));

            //1.13.2+ will have persistent metadata for this item
            ItemMetadataUtils.setSuperAbilityBoostedItem(heldItem, originalDigSpeed);
        } else {
            int duration = 0;
            int amplifier = 0;

            if (player.hasPotionEffect(getHastePotionEffectType())) {
                for (PotionEffect effect : player.getActivePotionEffects()) {
                    if (effect.getType() == getHastePotionEffectType()) {
                        duration = effect.getDuration();
                        amplifier = effect.getAmplifier();
                        break;
                    }
                }
            }

            final McMMOPlayer mmoPlayer = UserManager.getPlayer(player);

            //Not Loaded
            if (mmoPlayer == null) {
                return;
            }

            PrimarySkillType skill = mmoPlayer.getAbilityMode(SuperAbilityType.SUPER_BREAKER)
                    ? PrimarySkillType.MINING : PrimarySkillType.EXCAVATION;

            int abilityLengthVar = mcMMO.p.getAdvancedConfig().getAbilityLength();
            int abilityLengthCap = mcMMO.p.getAdvancedConfig().getAbilityLengthCap();

            int ticks;

            if (abilityLengthCap > 0) {
                ticks = PerksUtils.handleActivationPerks(player, Math.min(abilityLengthCap,
                                2 + (mmoPlayer.getSkillLevel(skill) / abilityLengthVar)),
                        mcMMO.p.getSkillTools().getSuperAbilityMaxLength(
                                mcMMO.p.getSkillTools().getSuperAbility(skill)))
                        * Misc.TICK_CONVERSION_FACTOR;
            } else {
                ticks = PerksUtils.handleActivationPerks(player,
                        2 + ((mmoPlayer.getSkillLevel(skill)) / abilityLengthVar),
                        mcMMO.p.getSkillTools().getSuperAbilityMaxLength(
                                mcMMO.p.getSkillTools().getSuperAbility(skill)))
                        * Misc.TICK_CONVERSION_FACTOR;
            }

            PotionEffect abilityBuff = new PotionEffect(getHastePotionEffectType(),
                    duration + ticks, amplifier + 10);
            player.addPotionEffect(abilityBuff, true);
        }
    }

    public static void removeAbilityBoostsFromInventory(@NotNull Player player) {
        for (ItemStack itemStack : player.getInventory().getContents()) {
            removeAbilityBuff(itemStack);
        }
    }

    public static void removeAbilityBuff(@Nullable ItemStack itemStack) {
        if (itemStack == null) {
            return;
        }

        if (!ItemUtils.canBeSuperAbilityDigBoosted(itemStack)) {
            return;
        }

        //1.13.2+ will have persistent metadata for this itemStack
        if (isLegacyAbilityTool(itemStack)) {
            ItemMeta itemMeta = itemStack.getItemMeta();

            if (itemMeta != null) {
                // This is safe to call without prior checks.
                itemMeta.removeEnchant(mcMMO.p.getEnchantmentMapper().getEfficiency());

                itemStack.setItemMeta(itemMeta);
                ItemUtils.removeAbilityLore(itemStack);
            }
        }

        if (isSuperAbilityBoosted(itemStack)) {
            removeBonusDigSpeedOnSuperAbilityTool(itemStack);
        }
    }

    public static void handleDurabilityChange(ItemStack itemStack, int durabilityModifier) {
        handleDurabilityChange(itemStack, durabilityModifier, 1.0);
    }

    /**
     * Modify the durability of an ItemStack, using Tools specific formula for unbreaking enchant
     * damage reduction
     *
     * @param itemStack The ItemStack which durability should be modified
     * @param durabilityModifier the amount to modify the durability by
     * @param maxDamageModifier the amount to adjust the max damage by
     */
    public static void handleDurabilityChange(ItemStack itemStack, double durabilityModifier,
            double maxDamageModifier) {
        if (itemStack.hasItemMeta() && itemStack.getItemMeta().isUnbreakable()) {
            return;
        }

        Material type = itemStack.getType();
        short maxDurability =
                mcMMO.getRepairableManager().isRepairable(type) ? mcMMO.getRepairableManager()
                        .getRepairable(type).getMaximumDurability() : type.getMaxDurability();
        durabilityModifier = (int) Math.min(durabilityModifier / (
                        itemStack.getEnchantmentLevel(mcMMO.p.getEnchantmentMapper().getUnbreaking()) + 1),
                maxDurability * maxDamageModifier);

        itemStack.setDurability(
                (short) Math.min(itemStack.getDurability() + durabilityModifier, maxDurability));
    }

    private static boolean isLocalizedSkill(String skillName) {
        for (PrimarySkillType skill : PrimarySkillType.values()) {
            if (skillName.equalsIgnoreCase(LocaleLoader.getString(
                    StringUtils.getCapitalized(skill.toString()) + ".SkillName"))) {
                return true;
            }
        }

        return false;
    }


    /**
     * Modify the durability of an ItemStack, using Armor specific formula for unbreaking enchant
     * damage reduction
     *
     * @param itemStack The ItemStack which durability should be modified
     * @param durabilityModifier the amount to modify the durability by
     * @param maxDamageModifier the amount to adjust the max damage by
     */
    public static void handleArmorDurabilityChange(ItemStack itemStack, double durabilityModifier,
            double maxDamageModifier) {
        if (itemStack.hasItemMeta() && itemStack.getItemMeta().isUnbreakable()) {
            return;
        }

        Material type = itemStack.getType();
        short maxDurability =
                mcMMO.getRepairableManager().isRepairable(type) ? mcMMO.getRepairableManager()
                        .getRepairable(type).getMaximumDurability() : type.getMaxDurability();
        durabilityModifier = (int) Math.min(durabilityModifier * (0.6 + 0.4 / (
                        itemStack.getEnchantmentLevel(mcMMO.p.getEnchantmentMapper().getUnbreaking()) + 1)),
                maxDurability * maxDamageModifier);

        itemStack.setDurability(
                (short) Math.min(itemStack.getDurability() + durabilityModifier, maxDurability));
    }

    @Nullable
    public static Material getRepairAndSalvageItem(@NotNull ItemStack inHand) {
        if (ItemUtils.isPrismarineTool(inHand)) {
            return Material.PRISMARINE_CRYSTALS;
        } else if (ItemUtils.isNetheriteTool(inHand) || ItemUtils.isNetheriteArmor(inHand)) {
            return Material.NETHERITE_SCRAP;
        } else if (ItemUtils.isDiamondTool(inHand) || ItemUtils.isDiamondArmor(inHand)) {
            return Material.DIAMOND;
        } else if (ItemUtils.isGoldTool(inHand) || ItemUtils.isGoldArmor(inHand)) {
            return Material.GOLD_INGOT;
        } else if (ItemUtils.isIronTool(inHand) || ItemUtils.isIronArmor(inHand)) {
            return Material.IRON_INGOT;
        } else if (ItemUtils.isStoneTool(inHand)) {
            return Material.COBBLESTONE;
        } else if (ItemUtils.isWoodTool(inHand)) {
            return Material.OAK_WOOD;
        } else if (ItemUtils.isLeatherArmor(inHand)) {
            return Material.LEATHER;
        } else if (ItemUtils.isStringTool(inHand)) {
            return Material.STRING;
        } else {
            return null;
        }
    }

    public static int getRepairAndSalvageQuantities(ItemStack item) {
        return getRepairAndSalvageQuantities(item.getType(), getRepairAndSalvageItem(item));
    }

    public static int getRepairAndSalvageQuantities(Material itemMaterial,
            Material recipeMaterial) {
        int quantity = 0;

        if (mcMMO.getMaterialMapStore().isPrismarineTool(itemMaterial)) {
            return 16;
        }

        if (mcMMO.getMaterialMapStore().isNetheriteTool(itemMaterial)
                || mcMMO.getMaterialMapStore().isNetheriteArmor(itemMaterial)) {
            //One netherite bar requires 4 netherite scraps
            return 4;
        }

        for (Iterator<? extends Recipe> recipeIterator = Bukkit.getServer().recipeIterator();
                recipeIterator.hasNext(); ) {
            Recipe bukkitRecipe = recipeIterator.next();

            if (bukkitRecipe.getResult().getType() != itemMaterial) {
                continue;
            }

            if (bukkitRecipe instanceof ShapelessRecipe) {
                for (ItemStack ingredient : ((ShapelessRecipe) bukkitRecipe).getIngredientList()) {
                    if (ingredient != null
                            && (recipeMaterial == null || ingredient.getType() == recipeMaterial)
                            && (ingredient.getType() == recipeMaterial)) {
                        quantity += ingredient.getAmount();
                    }
                }
            } else if (bukkitRecipe instanceof ShapedRecipe) {
                for (ItemStack ingredient : ((ShapedRecipe) bukkitRecipe).getIngredientMap()
                        .values()) {
                    if (ingredient != null
                            && (recipeMaterial == null || ingredient.getType() == recipeMaterial)
                            && (ingredient.getType() == recipeMaterial)) {
                        quantity += ingredient.getAmount();
                    }
                }
            }
        }

        return quantity;
    }

    /**
     * Checks if a player can use a skill
     *
     * @param player target player
     * @param subSkillType target subskill
     * @return true if the player has permission and has the skill unlocked
     */
    public static boolean canUseSubskill(Player player, @NotNull SubSkillType subSkillType) {
        return Permissions.isSubSkillEnabled(player, subSkillType) && RankUtils.hasUnlockedSubskill(
                player, subSkillType);
    }
}
