package net.knarcraft.blacksmith.config;

import net.citizensnpcs.api.util.DataKey;
import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.trait.BlacksmithTrait;
import net.knarcraft.blacksmith.util.ConfigHelper;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;

/**
 * A class which keeps track of all Blacksmith settings/config values for one NPC
 */
public class NPCSettings {

    private final List<Material> reforgeAbleItems = new ArrayList<>();
    private final Map<NPCSetting, Object> currentValues = new HashMap<>();
    private final GlobalSettings globalSettings;

    /**
     * Instantiates a new "Settings" object
     */
    public NPCSettings(GlobalSettings globalSettings) {
        this.globalSettings = globalSettings;
    }

    /**
     * Loads variables from the given data key
     *
     * @param key <p>The data key to load variables from</p>
     */
    public void loadVariables(DataKey key) {
        for (NPCSetting setting : NPCSetting.values()) {
            if (key.keyExists(setting.getChildPath())) {
                currentValues.put(setting, key.getRaw(setting.getChildPath()));
            }
        }
        //Updates the list of reforge-able items/materials
        updateReforgeAbleItems();
    }

    /**
     * Saves variables to the given data key
     *
     * @param key <p>The data key to save variables to</p>
     */
    public void saveVariables(DataKey key) {
        for (NPCSetting setting : NPCSetting.values()) {
            key.setRaw(setting.getChildPath(), currentValues.get(setting));
        }
    }

    /**
     * Changes one setting to the given value
     *
     * @param setting  <p>The setting to change</p>
     * @param newValue <p>The new value of the setting</p>
     */
    public void changeSetting(NPCSetting setting, Object newValue) {
        if (setting == NPCSetting.REFORGE_ABLE_ITEMS) {
            newValue = replaceReforgeAblePlaceholders(newValue);
            currentValues.put(setting, newValue);
            updateReforgeAbleItems();
        } else {
            currentValues.put(setting, newValue);
        }
    }

    /**
     * Gets the raw current value of a setting
     *
     * @param setting <p>The setting to get the value of</p>
     * @return <p>The current value of the setting</p>
     */
    public Object getRawValue(NPCSetting setting) {
        return currentValues.get(setting);
    }

    /**
     * Gets the message to display when the blacksmith is busy with another player
     *
     * @return <p>The busy with player message</p>
     */
    public String getBusyWithPlayerMessage() {
        return asString(NPCSetting.BUSY_WITH_PLAYER_MESSAGE);
    }

    /**
     * Gets the message to display when the blacksmith is busy with reforging an item
     *
     * @return <p>The busy reforging message</p>
     */
    public String getBusyReforgingMessage() {
        return asString(NPCSetting.BUSY_WITH_REFORGE_MESSAGE);
    }

    /**
     * Gets the message to use for displaying an item's cost
     *
     * @return <p>The message to use for displaying item cost</p>
     */
    public String getCostMessage() {
        return asString(NPCSetting.COST_MESSAGE);
    }

    /**
     * Gets the message to display when a blacksmith has been given an invalid item
     *
     * @return <p>The invalid item message</p>
     */
    public String getInvalidItemMessage() {
        return asString(NPCSetting.INVALID_ITEM_MESSAGE);
    }

    /**
     * Gets the message to display when a blacksmith starts reforging an item
     *
     * @return <p>The start reforge message</p>
     */
    public String getStartReforgeMessage() {
        return asString(NPCSetting.START_REFORGE_MESSAGE);
    }

    /**
     * Gets the message to display when a blacksmith has successfully repaired an item
     *
     * @return <p>The reforge success message</p>
     */
    public String getSuccessMessage() {
        return asString(NPCSetting.SUCCESS_MESSAGE);
    }

    /**
     * Gets the message to display when a blacksmith has failed to repair an item
     *
     * @return <p>The reforge fail message</p>
     */
    public String getFailMessage() {
        return asString(NPCSetting.FAIL_MESSAGE);
    }

    /**
     * Gets the message to display when a player cannot afford re-forging an item
     *
     * @return <p>The insufficient funds message</p>
     */
    public String getInsufficientFundsMessage() {
        return asString(NPCSetting.INSUFFICIENT_FUNDS_MESSAGE);
    }

    /**
     * Gets the message to display when a blacksmith is still affected by a cool-down
     *
     * @return <p>The cool down unexpired message</p>
     */
    public String getCoolDownUnexpiredMessage() {
        return asString(NPCSetting.COOL_DOWN_UNEXPIRED_MESSAGE);
    }

    /**
     * Gets the message to display when a player has changed the item they're trying to reforge
     *
     * @return <p>The item changed message</p>
     */
    public String getItemChangedMessage() {
        return asString(NPCSetting.ITEM_UNEXPECTEDLY_CHANGED_MESSAGE);
    }

    /**
     * Gets all items reforge-able by this NPC
     *
     * <p>If this is not empty, only the items specified can be repaired by this NPC.</p>
     *
     * @return <p>All items reforge-able by this NPC</p>
     */
    public List<Material> getReforgeAbleItems() {
        return new ArrayList<>(reforgeAbleItems);
    }

    /**
     * Gets the minimum delay used to wait for a re-forge to finish.
     *
     * @return <p>The minimum reforge delay</p>
     */
    public int getMinReforgeDelay() {
        return asInt(NPCSetting.MIN_REFORGE_DELAY);
    }

    /**
     * Gets the maximum delay used to wait for a re-forge to finish
     *
     * @return <p>The maximum reforge delay</p>
     */
    public int getMaxReforgeDelay() {
        return asInt(NPCSetting.MAX_REFORGE_DELAY);
    }

    /**
     * Gets the cool-down between each reforge
     *
     * @return <p>The reforge cool-down</p>
     */
    public int getReforgeCoolDown() {
        return asInt(NPCSetting.REFORGE_COOL_DOWN);
    }

    /**
     * Gets the chance to fail a re-forge
     *
     * @return <p>The fail chance</p>
     */
    public int getFailChance() {
        return asInt(NPCSetting.FAIL_CHANCE);
    }

    /**
     * Gets the chance for adding an extra enchantment to an item
     *
     * @return <p>The extra enchantment chance</p>
     */
    public int getExtraEnchantmentChance() {
        return asInt(NPCSetting.EXTRA_ENCHANTMENT_CHANCE);
    }

    /**
     * Gets the max number of enchantment to add to an item
     *
     * @return <p>The maximum enchantments</p>
     */
    public int getMaxEnchantments() {
        return asInt(NPCSetting.MAX_ENCHANTMENTS);
    }

    /**
     * Gets whether an item should be dropped on the ground instead of being given to the player
     *
     * @return <p>Whether to drop reforged items on the ground</p>
     */
    public boolean getDropItem() {
        return asBoolean(NPCSetting.DROP_ITEM);
    }

    /**
     * Gets whether to disable the reforge-cool-down
     *
     * @return <p>Whether to disable the reforge-cool-down</p>
     */
    public boolean getDisableCoolDown() {
        return asBoolean(NPCSetting.DISABLE_COOL_DOWN);
    }

    /**
     * Gets whether to disable the delay between starting reforging and the re-forge finishing
     *
     * @return <p>Whether to disable the reforge delay</p>
     */
    public boolean getDisableDelay() {
        return asBoolean(NPCSetting.DISABLE_DELAY);
    }

    /**
     * Gets the given value as a boolean
     *
     * <p>This will throw an exception if used for a non-boolean value</p>
     *
     * @param setting <p>The setting to get the value of</p>
     * @return <p>The value of the given setting as a boolean</p>
     */
    private boolean asBoolean(NPCSetting setting) {
        return ConfigHelper.asBoolean(getValue(setting));
    }

    /**
     * Gets the given value as an integer
     *
     * <p>This will throw an exception if used for a non-integer setting</p>
     *
     * @param setting <p>The setting to get the value of</p>
     * @return <p>The value of the given setting as an integer</p>
     */
    private int asInt(NPCSetting setting) {
        return ConfigHelper.asInt(getValue(setting));
    }

    /**
     * Gets the string value of the given setting
     *
     * @param setting <p>The setting to get the value of</p>
     * @return <p>The value of the given setting as a string</p>
     */
    private String asString(NPCSetting setting) {
        return getValue(setting).toString();
    }

    /**
     * Gets the value of a setting, using the default if not set
     *
     * @param setting <p>The setting to get the value of</p>
     * @return <p>The current value</p>
     */
    private Object getValue(NPCSetting setting) {
        Object value = currentValues.get(setting);
        //If not set, use the default value from the config.yml file
        if (value == null) {
            Map<NPCSetting, Object> defaultNPCSettings = globalSettings.getDefaultNPCSettings();
            if (defaultNPCSettings.containsKey(setting)) {
                value = defaultNPCSettings.get(setting);
            }
        }
        //If not set in config.yml, use the default value from the enum
        if (value == null) {
            value = setting.getDefaultValue();
        }
        return value;
    }

    /**
     * Replaces placeholders in the given reforge-able value
     *
     * @param value <p>The value specified by a user</p>
     * @return <p>The value with placeholders replaced</p>
     */
    private Object replaceReforgeAblePlaceholders(Object value) {
        if (value instanceof String string) {
            String[] list = string.split(",");
            List<String> replaced = new ArrayList<>(list.length);
            for (String item : list) {
                replaced.add(SmithPreset.replacePlaceholder(item));
            }
            return String.join(",", replaced);
        } else if (value instanceof String[] stringList) {
            List<String> replaced = new ArrayList<>(stringList.length);
            for (String item : stringList) {
                replaced.add(SmithPreset.replacePlaceholder(item));
            }
            return replaced.toArray();
        } else {
            throw new IllegalArgumentException("Unexpected object type encountered!");
        }
    }

    /**
     * Updates the reforge-able items according to the current value of the setting
     */
    private void updateReforgeAbleItems() {
        this.reforgeAbleItems.clear();
        String newReforgeAbleItems = (String) currentValues.get(NPCSetting.REFORGE_ABLE_ITEMS);
        if (newReforgeAbleItems == null) {
            return;
        }

        for (String item : newReforgeAbleItems.split(",")) {
            if (item == null || item.equalsIgnoreCase("null")) {
                continue;
            }
            Material material = Material.matchMaterial(item.replace('-', '_'));
            if (material != null && BlacksmithTrait.isRepairable(new ItemStack(material, 1))) {
                this.reforgeAbleItems.add(material);
            } else {
                BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, "Unable to verify " + item +
                        " as a valid reforge-able item");
            }
        }
    }

}