package tc.oc.commons.bukkit.item;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.Nullable;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.attribute.Attribute;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.material.MaterialData;
import org.bukkit.potion.Potion;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Consumer;

public class ItemUtils {
    private ItemUtils() {}

    public static void addEnchantments(ItemMeta meta, Map<Enchantment, Integer> enchantments) {
        for(Map.Entry<Enchantment, Integer> enchantment : enchantments.entrySet()) {
            if(meta.getEnchantLevel(enchantment.getKey()) < enchantment.getValue()) {
                meta.addEnchant(enchantment.getKey(), enchantment.getValue(), true);
            }
        }
    }

    public static void addPotionEffects(ItemStack stack, List<PotionEffect> newEffects) {
        if(stack.getType() == Material.POTION && !newEffects.isEmpty()) {
            PotionMeta meta = (PotionMeta) stack.getItemMeta();

            Set<PotionEffect> defaultEffects = new HashSet<>(Potion.fromItemStack(stack).getEffects());
            Collection<PotionEffect> existingEffects;

            if(meta.hasCustomEffects()) {
                existingEffects = meta.getCustomEffects();
            } else {
                existingEffects = defaultEffects;
            }

            Map<PotionEffectType, PotionEffect> effectsByType = new HashMap<>();
            for(PotionEffect effect : existingEffects) {
                effectsByType.put(effect.getType(), effect);
            }

            for(PotionEffect newEffect : newEffects) {
                PotionEffect oldEffect = effectsByType.get(newEffect.getType());
                if(oldEffect == null ||
                   oldEffect.getAmplifier() < newEffect.getAmplifier() ||
                   (oldEffect.getAmplifier() == newEffect.getAmplifier() && oldEffect.getDuration() < newEffect.getDuration())) {

                    effectsByType.put(newEffect.getType(), newEffect);
                }
            }

            if(defaultEffects.equals(ImmutableSet.copyOf(effectsByType.values()))) {
                meta.clearCustomEffects();
            } else {
                for(PotionEffect effect : effectsByType.values()) {
                    meta.addCustomEffect(effect, true);
                }
            }

            stack.setItemMeta(meta);
        }
    }

    public static ImmutableMaterialSet unionMaterials(ImmutableMaterialSet a, ImmutableMaterialSet b) {
        if(a.containsAll(b)) return a;
        if(b.containsAll(a)) return b;
        return ImmutableMaterialSet.of(Sets.union(a, b));
    }

    public static int amount(@Nullable ItemStack stack) {
        return stack == null || stack.getType() == Material.AIR ? 0 : stack.getAmount();
    }

    public static int addAmount(ItemStack stack, int delta) {
        final int amount = stack.getAmount() + delta;
        stack.setAmount(amount);
        return amount;
    }

    public static boolean isNothing(@Nullable ItemStack stack) {
        return amount(stack) == 0;
    }

    public static Optional<ItemStack> something(@Nullable ItemStack stack) {
        return isNothing(stack) ? Optional.empty() : Optional.of(stack);
    }

    private static final String[] TOOLS = {"axe", "hoe", "spade"};

    public static boolean isTool(ItemStack stack) {
        return isTool(stack.getData());
    }

    public static boolean isTool(MaterialData item) {
        return Stream.of(TOOLS).anyMatch(query -> item.getItemType().name().toLowerCase().contains(query));
    }

    public static boolean isArmor(ItemStack stack) {
        return isArmor(stack.getData());
    }

    public static boolean isArmor(MaterialData item) {
        return !Bukkit.getItemFactory().getAttributeModifiers(item, Attribute.GENERIC_ARMOR).isEmpty();
    }

    public static boolean isWeapon(ItemStack stack) {
        return isWeapon(stack.getData());
    }

    public static boolean isWeapon(MaterialData item) {
        return !Bukkit.getItemFactory().getAttributeModifiers(item, Attribute.GENERIC_ATTACK_DAMAGE).isEmpty();
    }

    public static Optional<ItemMeta> tryMeta(ItemStack item) {
        return item.hasItemMeta() ? Optional.of(item.getItemMeta())
                                  : Optional.empty();
    }

    public static void updateMeta(ItemStack item, Consumer<ItemMeta> mutator) {
        final ItemMeta meta = item.getItemMeta();
        mutator.accept(meta);
        item.setItemMeta(meta);
    }

    public static void updateMetaIfPresent(@Nullable ItemStack item, Consumer<ItemMeta> mutator) {
        if(item != null && item.hasItemMeta()) {
            updateMeta(item, mutator);
        }
    }
}
