package net.knarcraft.blacksmith.command;

import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.npc.NPC;
import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.Setting;
import net.knarcraft.blacksmith.config.SettingValueType;
import net.knarcraft.blacksmith.config.Settings;
import net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage;
import net.knarcraft.blacksmith.trait.CustomTrait;
import net.knarcraft.blacksmith.util.InputParsingHelper;
import net.knarcraft.blacksmith.util.TypeValidationHelper;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.logging.Level;

import static net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage.getCurrentValueMessage;
import static net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage.getValueChangedMessage;

/**
 * A generic implementation of the edit command
 *
 * @param <K> <p>The type of trait to edit</p>
 * @param <L> <p>The type of setting the trait uses</p>
 */
public abstract class EditCommand<K extends CustomTrait<L>, L extends Setting> implements CommandExecutor {

    protected final Class<K> traitClass;

    /**
     * Instantiates a new edit command
     *
     * @param traitClass <p>The type of trait this command edits</p>
     */
    public EditCommand(Class<K> traitClass) {
        this.traitClass = traitClass;
    }

    /**
     * Gets the setting corresponding to the given input
     *
     * @param input <p>The input given by the user</p>
     * @return <p>The corresponding setting, or null if not recognized</p>
     */
    public abstract L getSetting(String input);

    /**
     * Gets the global settings to use
     *
     * @return <p>The global settings</p>
     */
    public abstract @NotNull Settings<L> getGlobalSettings();

    @Override
    public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String s,
                             @NotNull String[] args) {
        NPC npc = CitizensAPI.getDefaultNPCSelector().getSelected(sender);
        if (npc == null || !npc.hasTrait(traitClass)) {
            BlacksmithPlugin.getStringFormatter().displayErrorMessage(sender,
                    BlacksmithTranslatableMessage.NO_NPC_SELECTED);
            return true;
        }

        if (args.length < 1) {
            return false;
        }

        K trait = npc.getTraitNullable(traitClass);
        if (trait == null) {
            return false;
        }

        L setting = getSetting(args[0]);
        if (setting != null) {
            String newValue = args.length < 2 ? null : args[1];
            //This makes sure all arguments are treated as a sentence
            if (setting.getValueType() == SettingValueType.STRING && args.length > 2) {
                newValue = String.join(" ", Arrays.asList(args).subList(1, args.length));
            }
            Settings<L> settings = getGlobalSettings();
            return displayOrChangeNPCSetting(trait, setting, settings, newValue, sender);
        } else {
            return false;
        }
    }

    /**
     * Changes the given NPC setting, or displays the current value if a new value isn't specified
     *
     * @param trait          <p>The trait belonging to the selected NPC</p>
     * @param setting        <p>The NPC setting to change</p>
     * @param globalSettings <p>The global settings to get default values from</p>
     * @param newValue       <p>The value to change the setting to</p>
     * @param sender         <p>The command sender to notify about results</p>
     * @return <p>True if everything went successfully</p>
     */
    private boolean displayOrChangeNPCSetting(@NotNull CustomTrait<L> trait,
                                              @NotNull L setting,
                                              @NotNull Settings<L> globalSettings,
                                              @Nullable String newValue,
                                              @NotNull CommandSender sender) {
        if (newValue == null) {
            //Display the current value of the setting
            displayNPCSetting(trait, setting, globalSettings, sender);
        } else {
            //If an empty value or null, clear the value instead of changing it
            if (InputParsingHelper.isEmpty(newValue)) {
                newValue = null;
            } else {
                //Abort if an invalid value is given
                boolean isValidType = TypeValidationHelper.isValid(setting.getValueType(), newValue, sender);
                if (!isValidType) {
                    return false;
                }
                newValue = ChatColor.translateAlternateColorCodes('&', newValue);
            }

            //Change the setting
            Settings<L> settings = trait.getTraitSettings();
            if (settings == null) {
                BlacksmithPlugin.getInstance().getLogger().log(Level.SEVERE, "Settings for a CustomTrait has not " +
                        "been initialized! Please inform the developer!");
                return false;
            }
            settings.changeValue(setting, newValue);
            BlacksmithPlugin.getStringFormatter().displaySuccessMessage(sender,
                    getValueChangedMessage(setting.getCommandName(), newValue));
            //Save the changes immediately to prevent data loss on server crash
            CitizensAPI.getNPCRegistry().saveToStore();
        }
        return true;
    }

    /**
     * Displays the current value of the given NPC setting
     *
     * @param trait          <p>The trait of the NPC to get the value from</p>
     * @param setting        <p>The NPC setting to see the value of</p>
     * @param globalSettings <p>The global settings to get default values from</p>
     * @param sender         <p>The command sender to display the value to</p>
     */
    private void displayNPCSetting(@NotNull CustomTrait<L> trait,
                                   @NotNull L setting,
                                   @NotNull Settings<L> globalSettings,
                                   @NotNull CommandSender sender) {
        Settings<L> settings = trait.getTraitSettings();
        if (settings == null) {
            BlacksmithPlugin.getInstance().getLogger().log(Level.SEVERE, "Settings for a CustomTrait has not " +
                    "been initialized! Please inform the developer!");
            return;
        }

        StringFormatter formatter = BlacksmithPlugin.getStringFormatter();

        // Display the description for how this setting is used
        formatter.displaySuccessMessage(sender, setting.getDescription());

        String rawValue = String.valueOf(settings.getRawValue(setting));
        if (InputParsingHelper.isEmpty(rawValue)) {
            //Display the default value, if no custom value has been specified
            rawValue = String.valueOf(globalSettings.getRawValue(setting));
            formatter.displayNeutralMessage(sender,
                    getCurrentValueMessage(setting.getCommandName(), rawValue));
        } else {
            //Add a marker if the value has been customized
            String marker = BlacksmithPlugin.getStringFormatter().getUnformattedMessage(
                    BlacksmithTranslatableMessage.SETTING_OVERRIDDEN_MARKER);
            formatter.displayNeutralMessage(sender,
                    getCurrentValueMessage(setting.getCommandName(), rawValue) + marker);
        }
        if (setting.isMessage()) {
            sender.sendMessage(BlacksmithTranslatableMessage.getRawValueMessage(
                    rawValue.replace(ChatColor.COLOR_CHAR, '&')));
        }
    }

}