/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2020 Crypto Morin
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
 * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.cryptomorin.xseries.unused;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.bukkit.NamespacedKey;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Cross-version enchantment support with multiple aliases.
 * Uses EssentialsX enchantment list for aliases.
 * This class will not be updated.
 * <p>
 * Enchantment levels do not start from 0, they start from 1
 * EssentialsX Enchantment: https://github.com/Bukkit/Bukkit/blob/master/src/main/java/org/bukkit/enchantments/Enchantment.java
 * Enchantment: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/enchantments/Enchantment.html
 * Enchanting: https://minecraft.gamepedia.com/Enchanting
 *
 * @author Crypto Morin
 * @version 1.0.0
 * @see Enchantment
 */
@SuppressWarnings("deprecation")
public final class ZEnchantment {
    public static final ImmutableMap<String, Enchantment> ENCHANTMENTS;
    public static final ImmutableMap<String, Enchantment> ALIASENCHANTMENTS;

    /**
     * Java Edition 1.13/Flattening Update
     * https://minecraft.gamepedia.com/Java_Edition_1.13/Flattening
     */
    private static final boolean ISFLAT;
    private static final Pattern SPACE = Pattern.compile("  +");

    static {
        ImmutableMap.Builder<String, Enchantment> builder = ImmutableMap.builder();
        ImmutableMap.Builder<String, Enchantment> aliasBuilder = ImmutableMap.builder();

        builder.put("alldamage", Enchantment.DAMAGE_ALL);
        builder.put("sharpness", Enchantment.DAMAGE_ALL);
        aliasBuilder.put("alldmg", Enchantment.DAMAGE_ALL);
        aliasBuilder.put("sharp", Enchantment.DAMAGE_ALL);
        aliasBuilder.put("dal", Enchantment.DAMAGE_ALL);

        builder.put("ardmg", Enchantment.DAMAGE_ARTHROPODS);
        builder.put("baneofarthropods", Enchantment.DAMAGE_ARTHROPODS);
        aliasBuilder.put("baneofarthropod", Enchantment.DAMAGE_ARTHROPODS);
        aliasBuilder.put("arthropod", Enchantment.DAMAGE_ARTHROPODS);
        aliasBuilder.put("dar", Enchantment.DAMAGE_ARTHROPODS);

        builder.put("undeaddamage", Enchantment.DAMAGE_UNDEAD);
        builder.put("smite", Enchantment.DAMAGE_UNDEAD);
        aliasBuilder.put("du", Enchantment.DAMAGE_UNDEAD);

        builder.put("digspeed", Enchantment.DIG_SPEED);
        builder.put("efficiency", Enchantment.DIG_SPEED);
        aliasBuilder.put("minespeed", Enchantment.DIG_SPEED);
        aliasBuilder.put("cutspeed", Enchantment.DIG_SPEED);
        aliasBuilder.put("ds", Enchantment.DIG_SPEED);
        aliasBuilder.put("eff", Enchantment.DIG_SPEED);

        builder.put("durability", Enchantment.DURABILITY);
        builder.put("unbreaking", Enchantment.DURABILITY);
        aliasBuilder.put("dura", Enchantment.DURABILITY);
        aliasBuilder.put("d", Enchantment.DURABILITY);

        builder.put("thorns", Enchantment.THORNS);
        builder.put("highcrit", Enchantment.THORNS);
        aliasBuilder.put("thorn", Enchantment.THORNS);
        aliasBuilder.put("highercrit", Enchantment.THORNS);
        aliasBuilder.put("t", Enchantment.THORNS);

        builder.put("fireaspect", Enchantment.FIRE_ASPECT);
        builder.put("fire", Enchantment.FIRE_ASPECT);
        aliasBuilder.put("meleefire", Enchantment.FIRE_ASPECT);
        aliasBuilder.put("meleeflame", Enchantment.FIRE_ASPECT);
        aliasBuilder.put("fa", Enchantment.FIRE_ASPECT);

        builder.put("knockback", Enchantment.KNOCKBACK);
        aliasBuilder.put("kback", Enchantment.KNOCKBACK);
        aliasBuilder.put("kb", Enchantment.KNOCKBACK);
        aliasBuilder.put("k", Enchantment.KNOCKBACK);

        builder.put("fortune", Enchantment.LOOT_BONUS_BLOCKS);
        aliasBuilder.put("blockslootbonus", Enchantment.LOOT_BONUS_BLOCKS);
        aliasBuilder.put("fort", Enchantment.LOOT_BONUS_BLOCKS);
        aliasBuilder.put("lbb", Enchantment.LOOT_BONUS_BLOCKS);

        builder.put("mobloot", Enchantment.LOOT_BONUS_MOBS);
        builder.put("looting", Enchantment.LOOT_BONUS_MOBS);
        aliasBuilder.put("mobslootbonus", Enchantment.LOOT_BONUS_MOBS);
        aliasBuilder.put("lbm", Enchantment.LOOT_BONUS_MOBS);

        builder.put("breath", Enchantment.OXYGEN);
        builder.put("respiration", Enchantment.OXYGEN);
        aliasBuilder.put("breathing", Enchantment.OXYGEN);
        aliasBuilder.put("oxygen", Enchantment.OXYGEN);
        aliasBuilder.put("o", Enchantment.OXYGEN);

        builder.put("protection", Enchantment.PROTECTION_ENVIRONMENTAL);
        builder.put("protect", Enchantment.PROTECTION_ENVIRONMENTAL);
        aliasBuilder.put("prot", Enchantment.PROTECTION_ENVIRONMENTAL);
        aliasBuilder.put("p", Enchantment.PROTECTION_ENVIRONMENTAL);

        builder.put("blastprotect", Enchantment.PROTECTION_EXPLOSIONS);
        aliasBuilder.put("explosionsprotection", Enchantment.PROTECTION_EXPLOSIONS);
        aliasBuilder.put("explosionprotection", Enchantment.PROTECTION_EXPLOSIONS);
        aliasBuilder.put("expprot", Enchantment.PROTECTION_EXPLOSIONS);
        aliasBuilder.put("blastprotection", Enchantment.PROTECTION_EXPLOSIONS);
        aliasBuilder.put("bprotection", Enchantment.PROTECTION_EXPLOSIONS);
        aliasBuilder.put("bprotect", Enchantment.PROTECTION_EXPLOSIONS);
        aliasBuilder.put("pe", Enchantment.PROTECTION_EXPLOSIONS);

        builder.put("fallprot", Enchantment.PROTECTION_FALL);
        builder.put("featherfall", Enchantment.PROTECTION_FALL);
        aliasBuilder.put("fallprotection", Enchantment.PROTECTION_FALL);
        aliasBuilder.put("featherfalling", Enchantment.PROTECTION_FALL);
        aliasBuilder.put("pfa", Enchantment.PROTECTION_FALL);

        builder.put("fireprot", Enchantment.PROTECTION_FIRE);
        builder.put("fireprotect", Enchantment.PROTECTION_FIRE);
        aliasBuilder.put("fireprotection", Enchantment.PROTECTION_FIRE);
        aliasBuilder.put("flameprotection", Enchantment.PROTECTION_FIRE);
        aliasBuilder.put("flameprotect", Enchantment.PROTECTION_FIRE);
        aliasBuilder.put("flameprot", Enchantment.PROTECTION_FIRE);
        aliasBuilder.put("pf", Enchantment.PROTECTION_FIRE);

        builder.put("projectileprotection", Enchantment.PROTECTION_PROJECTILE);
        builder.put("projprot", Enchantment.PROTECTION_PROJECTILE);
        aliasBuilder.put("pp", Enchantment.PROTECTION_PROJECTILE);

        builder.put("silktouch", Enchantment.SILK_TOUCH);
        aliasBuilder.put("softtouch", Enchantment.SILK_TOUCH);
        aliasBuilder.put("st", Enchantment.SILK_TOUCH);

        builder.put("waterworker", Enchantment.WATER_WORKER);
        builder.put("aquaaffinity", Enchantment.WATER_WORKER);
        aliasBuilder.put("watermine", Enchantment.WATER_WORKER);
        aliasBuilder.put("ww", Enchantment.WATER_WORKER);

        aliasBuilder.put("firearrow", Enchantment.ARROW_FIRE);
        builder.put("flame", Enchantment.ARROW_FIRE);
        builder.put("flamearrow", Enchantment.ARROW_FIRE);
        aliasBuilder.put("af", Enchantment.ARROW_FIRE);

        builder.put("arrowdamage", Enchantment.ARROW_DAMAGE);
        builder.put("power", Enchantment.ARROW_DAMAGE);
        aliasBuilder.put("arrowpower", Enchantment.ARROW_DAMAGE);
        aliasBuilder.put("ad", Enchantment.ARROW_DAMAGE);

        builder.put("arrowknockback", Enchantment.ARROW_KNOCKBACK);
        aliasBuilder.put("arrowkb", Enchantment.ARROW_KNOCKBACK);
        builder.put("punch", Enchantment.ARROW_KNOCKBACK);
        aliasBuilder.put("arrowpunch", Enchantment.ARROW_KNOCKBACK);
        aliasBuilder.put("ak", Enchantment.ARROW_KNOCKBACK);

        aliasBuilder.put("infinitearrows", Enchantment.ARROW_INFINITE);
        builder.put("infarrows", Enchantment.ARROW_INFINITE);
        builder.put("infinity", Enchantment.ARROW_INFINITE);
        aliasBuilder.put("infinite", Enchantment.ARROW_INFINITE);
        aliasBuilder.put("unlimited", Enchantment.ARROW_INFINITE);
        aliasBuilder.put("unlimitedarrows", Enchantment.ARROW_INFINITE);
        aliasBuilder.put("ai", Enchantment.ARROW_INFINITE);

        builder.put("luck", Enchantment.LUCK);
        aliasBuilder.put("luckofsea", Enchantment.LUCK);
        aliasBuilder.put("luckofseas", Enchantment.LUCK);
        aliasBuilder.put("rodluck", Enchantment.LUCK);

        builder.put("lure", Enchantment.LURE);
        aliasBuilder.put("rodlure", Enchantment.LURE);

        // 1.8
        try {
            Enchantment depthStrider = Enchantment.getByName("DEPTH_STRIDER");
            if (depthStrider != null) {
                builder.put("depthstrider", depthStrider);
                aliasBuilder.put("depth", depthStrider);
                aliasBuilder.put("strider", depthStrider);
            }
        } catch (IllegalArgumentException ignored) {
        }

        // 1.9
        try {
            Enchantment frostWalker = Enchantment.getByName("FROST_WALKER");
            if (frostWalker != null) {
                builder.put("frostwalker", frostWalker);
                aliasBuilder.put("frost", frostWalker);
                aliasBuilder.put("walker", frostWalker);
            }

            Enchantment mending = Enchantment.getByName("MENDING");
            if (mending != null) builder.put("mending", mending);
        } catch (IllegalArgumentException ignored) {
        }

        // 1.11
        try {
            Enchantment bindingCurse = Enchantment.getByName("BINDING_CURSE");
            if (bindingCurse != null) {
                builder.put("bindingcurse", bindingCurse);
                aliasBuilder.put("bindcurse", bindingCurse);
                aliasBuilder.put("binding", bindingCurse);
                aliasBuilder.put("bind", bindingCurse);
            }

            Enchantment vanishingCurse = Enchantment.getByName("VANISHING_CURSE");
            if (vanishingCurse != null) {
                builder.put("vanishingcurse", vanishingCurse);
                aliasBuilder.put("vanishcurse", vanishingCurse);
                aliasBuilder.put("vanishing", vanishingCurse);
                aliasBuilder.put("vanish", vanishingCurse);
            }

            Enchantment sweeping = Enchantment.getByName("SWEEPING_EDGE");
            if (sweeping != null) {
                builder.put("sweepingedge", sweeping);
                aliasBuilder.put("sweepedge", sweeping);
                aliasBuilder.put("sweeping", sweeping);
            }
        } catch (IllegalArgumentException ignored) {
        }


        // Flattening
        boolean flat;
        try {
            Class<?> namespacedKeyClass = Class.forName("org.bukkit.NamespacedKey");
            Class<?> enchantmentClass = Class.forName("org.bukkit.enchantments.Enchantment");
            enchantmentClass.getDeclaredMethod("getByKey", namespacedKeyClass);
            flat = true;
        } catch (ClassNotFoundException | NoSuchMethodException ex) {
            flat = false;
        }
        ISFLAT = flat;

        if (ISFLAT) {
            // 1.13
            Enchantment loyalty = Enchantment.getByName("LOYALTY");
            if (loyalty != null) {
                builder.put("loyalty", loyalty);
                aliasBuilder
                        .put("loyal", loyalty)
                        .put("return", loyalty);
            }

            Enchantment impaling = Enchantment.getByName("IMPALING");
            if (impaling != null) {
                builder.put("impaling", impaling);
                aliasBuilder
                        .put("impale", impaling)
                        .put("oceandamage", impaling)
                        .put("oceandmg", impaling);
            }

            Enchantment riptide = Enchantment.getByName("RIPTIDE");
            if (riptide != null) {
                builder.put("riptide", riptide);
                aliasBuilder.put("rip", riptide);
                aliasBuilder.put("tide", riptide);
                aliasBuilder.put("launch", riptide);
            }

            Enchantment channelling = Enchantment.getByName("CHANNELING");
            if (channelling != null) {
                builder.put("channelling", channelling);
                aliasBuilder.put("chanelling", channelling);
                aliasBuilder.put("channeling", channelling);
                aliasBuilder.put("chaneling", channelling);
                aliasBuilder.put("channel", channelling);
            }

            // 1.14
            try {
                Enchantment multishot = Enchantment.getByName("MULTISHOT");
                if (multishot != null) {
                    builder.put("multishot", multishot);
                    aliasBuilder.put("tripleshot", multishot);
                }

                Enchantment quickCharge = Enchantment.getByName("QUICK_CHARGE");
                if (quickCharge != null) {
                    builder.put("quickcharge", quickCharge);
                    aliasBuilder.put("quickdraw", quickCharge);
                    aliasBuilder.put("fastcharge", quickCharge);
                    aliasBuilder.put("fastdraw", quickCharge);
                }

                Enchantment piercing = Enchantment.getByName("PIERCING");
                if (piercing != null) builder.put("piercing", piercing);
            } catch (IllegalArgumentException ignored) {
            }
        }

        ENCHANTMENTS = builder.build();
        ALIASENCHANTMENTS = aliasBuilder.build();
    }

    /**
     * Gets an enchantment from Vanilla and bukkit names.
     * There are also some aliases available.
     *
     * @param enchant the name of the enchantment.
     * @return an enchantment.
     * @since 1.0.0
     */
    @Nullable
    public static Optional<Enchantment> getByName(@Nonnull String enchant) {
        Validate.notEmpty(enchant, "Enchantment name cannot be null or empty");
        Enchantment enchantment = null;
        enchant = StringUtils.deleteWhitespace(StringUtils.lowerCase(enchant, Locale.ENGLISH));

        if (ISFLAT) enchantment = Enchantment.getByKey(NamespacedKey.minecraft(enchant)); // 1.13+ only
        if (enchantment == null) enchantment = Enchantment.getByName(enchant);
        if (enchantment == null) enchantment = ENCHANTMENTS.get(enchant);
        if (enchantment == null) enchantment = ALIASENCHANTMENTS.get(enchant);

        return Optional.ofNullable(enchantment);
    }

    /**
     * Adds an unsafe enchantment to the given item from a string.
     * <p>
     * <blockquote><pre>
     *    ItemStack item = ...;
     *    addEnchantFromString(item, "unbreaking, 10");
     *    addEnchantFromString(item, "mending");
     * </pre></blockquote>
     * <p>
     * Note that if you set your item's meta {@link ItemStack#setItemMeta(ItemMeta)} the enchantment
     * will be removed.
     * You need to use {@link ItemMeta#addEnchant(Enchantment, int, boolean)} instead.
     * You can use the {@link #getByName(String)} method in this case.
     *
     * @param item        the item to add the enchantment to.
     * @param enchantment the enchantment string containing the enchantment name and level (optional)
     * @return an enchanted {@link ItemStack}.
     * @since 1.0.0
     */
    @Nonnull
    public static ItemStack addEnchantFromString(@Nonnull ItemStack item, @Nonnull String enchantment) {
        Objects.requireNonNull(item, "Cannot add enchantment to null ItemStack");
        Validate.notEmpty(enchantment, "Cannot add null or empty enchantment to item");

        String[] split = enchantment.contains(",") ? StringUtils.deleteWhitespace(enchantment).split(",") : SPACE.matcher(enchantment).replaceAll(" ").split(" ");
        Optional<Enchantment> enchant = getByName(split[0]);
        if (!enchant.isPresent()) return item;

        int lvl = 1;
        if (split.length > 1) lvl = Integer.parseInt(split[1]);

        item.addUnsafeEnchantment(enchant.get(), lvl);
        return item;
    }

    @Nonnull
    public static ImmutableList<String> getEnchantments() {
        return ENCHANTMENTS.keySet().asList();
    }

    @Nonnull
    public static ImmutableList<String> getEnchantmentAliases() {
        return ALIASENCHANTMENTS.keySet().asList();
    }

    /**
     * Includes both {@link #getEnchantments()} and {@link #getEnchantmentAliases()}<br>
     * Should be saved in memory if you want to use it multiple times.
     *
     * @return concatenation of the main and the aliases lists.
     * @since 1.0.0
     */
    @Nonnull
    public static List<String> getAllEnchantmentNames() {
        return Stream.concat(ALIASENCHANTMENTS.keySet().stream(), ENCHANTMENTS.keySet().stream()).collect(Collectors.toList());
    }
}