package com.shanebeestudios.skbee;

import ch.njol.skript.Skript;
import ch.njol.skript.registrations.Classes;
import ch.njol.skript.test.runner.TestMode;
import ch.njol.skript.util.Version;
import com.github.shanebeee.skr.Utils;
import com.shanebeestudios.skbee.api.bound.BoundConfig;
import com.shanebeestudios.skbee.api.fastboard.FastBoardManager;
import com.shanebeestudios.skbee.api.listener.EntityListener;
import com.shanebeestudios.skbee.api.listener.NBTListener;
import com.shanebeestudios.skbee.api.nbt.NBTApi;
import com.shanebeestudios.skbee.api.property.PropertyRegistry;
import com.github.shanebeee.skr.Registration;
import com.shanebeestudios.skbee.api.structure.StructureManager;
import com.shanebeestudios.skbee.api.util.LoggerBee;
import com.shanebeestudios.skbee.api.util.Util;
import com.shanebeestudios.skbee.config.Config;
import com.shanebeestudios.skbee.elements.advancement.AdvancementElementRegistration;
import com.shanebeestudios.skbee.elements.bossbar.BossbarElementRegistration;
import com.shanebeestudios.skbee.elements.bound.BoundElementRegistration;
import com.shanebeestudios.skbee.elements.damagesource.DamageSourceElementRegistration;
import com.shanebeestudios.skbee.elements.dialog.DialogElementRegestration;
import com.shanebeestudios.skbee.elements.fastboard.FastboardElementRegistration;
import com.shanebeestudios.skbee.elements.fishing.FishingElementRegistration;
import com.shanebeestudios.skbee.elements.gameevent.GameEventElementRegistration;
import com.shanebeestudios.skbee.elements.itemcomponent.ItemComponentElementRegistration;
import com.shanebeestudios.skbee.elements.nbt.NBTElementRegistration;
import com.shanebeestudios.skbee.elements.other.OtherElementRegistration;
import com.shanebeestudios.skbee.elements.particle.ParticleElementRegistration;
import com.shanebeestudios.skbee.elements.property.PropertyElementRegistration;
import com.shanebeestudios.skbee.elements.raytrace.RayTraceElementRegistration;
import com.shanebeestudios.skbee.elements.recipe.RecipeElementRegistration;
import com.shanebeestudios.skbee.elements.registry.RegistryElementRegistration;
import com.shanebeestudios.skbee.elements.scoreboard.ScoreboardElementRegistration;
import com.shanebeestudios.skbee.elements.statistic.StatisticElementRegistration;
import com.shanebeestudios.skbee.elements.structure.StructureElementRegistration;
import com.shanebeestudios.skbee.elements.switchcase.SwitchCaseElementRegistration;
import com.shanebeestudios.skbee.elements.testing.TestingElementRegistration;
import com.shanebeestudios.skbee.elements.text.TextElementRegistration;
import com.shanebeestudios.skbee.elements.tickmanager.TickManagerElementRegistration;
import com.shanebeestudios.skbee.elements.villager.VillagerElementRegistration;
import com.shanebeestudios.skbee.elements.virtualfurnace.VirtualFurnaceElementRegistration;
import com.shanebeestudios.skbee.elements.virtualfurnace.listener.VirtualFurnaceListener;
import com.shanebeestudios.skbee.elements.worldgen.WorldCreatorElementRegistration;
import com.shanebeestudios.skbee.api.worldgen.BeeWorldConfig;
import com.shanebeestudios.vf.api.VirtualFurnaceAPI;
import de.tr7zw.changeme.nbtapi.utils.MinecraftVersion;
import org.bukkit.Statistic;
import org.bukkit.boss.BossBar;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.scoreboard.Objective;

/**
 * @hidden
 */
public class AddonLoader {

    private final PluginManager pluginManager;
    private final SkBee plugin;
    private final Registration registration;
    private final Config config;
    private final Plugin skriptPlugin;

    public AddonLoader(SkBee plugin) {
        this.plugin = plugin;
        this.config = plugin.getPluginConfig();
        this.registration = new Registration("SkBee", true);
        Utils.setDebug(this.config.settings_debug);

        this.pluginManager = plugin.getServer().getPluginManager();
        MinecraftVersion.replaceLogger(LoggerBee.getLogger());
        this.skriptPlugin = pluginManager.getPlugin("Skript");
    }

    public Registration getRegistration() {
        return this.registration;
    }

    boolean canLoadPlugin() {
        if (this.skriptPlugin == null) {
            Util.logLoading("&cDependency Skript was not found, Skript elements cannot load.");
            return false;
        }
        if (!this.skriptPlugin.isEnabled()) {
            Util.logLoading("&cDependency Skript is not enabled, Skript elements cannot load.");
            Util.logLoading("&cThis could mean SkBee is being forced to load before Skript.");
            return false;
        }
        Version skriptVersion = Skript.getVersion();
        if (skriptVersion.isSmallerThan(new Version(2, 14, 999))) {
            Util.logLoading("&cDependency Skript outdated, Skript elements cannot load.");
            Util.logLoading("&eSkBee requires Skript 2.15+ but found Skript " + skriptVersion);
            return false;
        }
        if (!Skript.isAcceptRegistrations()) {
            // SkBee should be loading right after Skript, during Skript's registration period
            // If a plugin is delaying SkBee's loading, this causes issues with registrations and no longer works
            // We need to find the route of this issue, so far the only plugin I know that does this is PlugMan
            Util.logLoading("&cSkript is no longer accepting registrations, addons can no longer be loaded!");
            if (isPlugmanReloaded()) {
                Util.logLoading("&cIt appears you're running PlugMan.");
                Util.logLoading("&cIf you're trying to reload/enable SkBee with PlugMan.... you can't.");
                Util.logLoading("&ePlease restart your server!");
            } else {
                Util.logLoading("&cNo clue how this could happen.");
                Util.logLoading("&cSeems a plugin is delaying SkBee loading, which is after Skript stops accepting registrations.");
            }
            return false;
        }
        loadSkriptElements();
        return true;
    }

    private void loadSkriptElements() {
        // Load first as these are the base for many things
        loadOtherElements();
        loadNBTElements();

        // Load in alphabetical order (to make "/skbee info" easier to read)
        loadAdvancementElements();
        loadBossBarElements();
        loadBoundElements();
        loadDamageSourceElements();
        loadDialogElements();
        loadFastboardElements();
        loadFishingElements();
        loadGameEventElements();
        loadItemComponentElements();
        loadPropertyElements();
        loadRayTraceElements();
        loadRecipeElements();
        loadRegistryElements();
        loadScoreboardElements();
        loadStatisticElements();
        loadStructureElements();
        loadSwitchCaseElements();
        loadTextElements();
        loadTickManagerElements();
        loadVillagerElements();
        loadVirtualFurnaceElements();
        loadWorldCreatorElements();
        loadTestingElements();

        // Load elements into Skript
        this.registration.finalizeRegistration();

        // ELEMENT COUNT
        int typeCount = this.registration.getTypes().size();
        int structureCount = this.registration.getStructures().size();
        int eventCount = this.registration.getEvents().size();
        int sectionCount = this.registration.getSections().size();
        int effectCount = this.registration.getEffects().size();
        int expressionCount = this.registration.getExpressions().size();
        int conditionCount = this.registration.getConditions().size();
        int propertyCount = PropertyRegistry.properties().size();
        int total = eventCount + effectCount + expressionCount + conditionCount + sectionCount + typeCount + structureCount + propertyCount;

        Util.log("Loaded SkBee (%s) elements:", total);
        Util.log(" - %s types", typeCount);
        Util.log(" - %s structures", structureCount);
        Util.log(" - %s events", eventCount);
        Util.log(" - %s sections", sectionCount);
        Util.log(" - %s effects", effectCount);
        Util.log(" - %s expressions", expressionCount);
        Util.log(" - %s conditions", conditionCount);
        if (this.config.elements_property) {
            Util.log(" - %s properties", propertyCount);
        }

        if (this.config.runtime_disable_errors) {
            Util.logLoading("&eRuntime Errors have been disabled via config!");
        }
        if (this.config.runtime_disable_warnings) {
            Util.logLoading("&eRuntime Warnings have been disabled via config!");
        }
    }

    private void loadAdvancementElements() {
        if (!this.config.elements_advancement) {
            Util.logLoading("&5Advancement Elements &cdisabled via config");
            return;
        }
        try {
            AdvancementElementRegistration.register(this.registration);
            Util.logLoading("&5Advancement Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Advancement", ex);
        }
    }

    private void loadBossBarElements() {
        if (!this.config.elements_boss_bar) {
            Util.logLoading("&5BossBar Elements &cdisabled via config");
            return;
        }
        if (Classes.getClassInfoNoError("bossbar") != null || Classes.getExactClassInfo(BossBar.class) != null) {
            Util.logLoading("&5BossBar Elements &cdisabled");
            Util.logLoading("&7It appears another Skript addon may have registered BossBar syntax.");
            Util.logLoading("&7To use SkBee BossBars, please remove the addon which has registered BossBars already.");
            return;
        }
        try {
            BossbarElementRegistration.register(this.registration);
            Util.logLoading("&5BossBar Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("BossBar", ex);
        }

    }

    private void loadBoundElements() {
        if (!this.config.elements_bound) {
            Util.logLoading("&5Bound Elements &cdisabled via config");
            return;
        }
        try {
            this.plugin.boundConfig = new BoundConfig(this.plugin);
            BoundElementRegistration.register(this.registration);
            Util.logLoading("&5Bound Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Bound", ex);
        }
    }

    private void loadDamageSourceElements() {
        if (!this.config.elements_damage_source) {
            Util.logLoading("&5Damage Source Elements &cdisabled via config");
            return;
        }
        try {
            DamageSourceElementRegistration.register(this.registration);
            Util.logLoading("&5Damage Source Elements &asuccessfully loaded");
            Util.log("&7 - Do note these elements are in Skript as 'Experimental'");
            Util.log("&7 - If issues arise, disable this feature and use Skript's elements instead");
        } catch (Exception ex) {
            logFailure("Damage Source", ex);
        }
    }

    private void loadDialogElements() {
        if (!this.config.elements_dialog) {
            Util.logLoading("&5Dialog elements &cdisabled via config");
            return;
        }
        DialogElementRegestration.register(this.registration);
        Util.logLoading("&5Dialog Elements &asuccessfully loaded");
    }

    private void loadGameEventElements() {
        if (!this.config.elements_game_event) {
            Util.logLoading("&5Game Event Elements &cdisabled via config");
            return;
        }
        try {
            GameEventElementRegistration.register(this.registration);
            Util.logLoading("&5Game Event Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Game Event", ex);
        }

    }

    private void loadFastboardElements() {
        if (!this.config.elements_fastboard) {
            Util.logLoading("&5Fastboard Elements &cdisabled via config");
            return;
        }
        try {
            this.pluginManager.registerEvents(new FastBoardManager(this.plugin, true), this.plugin);
            FastboardElementRegistration.register(this.registration);
            Util.logLoading("&5Fastboard&7[&bAdventure&7] &5Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Fastboard", ex);
        }
    }

    private void loadFishingElements() {
        if (!this.config.elements_fishing) {
            Util.logLoading("&5Fishing Elements &cdisabled via config");
            return;
        }
        try {
            FishingElementRegistration.register(this.registration);
            Util.logLoading("&5Fishing Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Fishing", ex);
        }
    }

    private void loadItemComponentElements() {
        if (!this.config.elements_item_component) {
            Util.logLoading("&5Item Component Elements &cdisabled via config");
            return;
        }
        try {
            ItemComponentElementRegistration.register(this.registration);
            Util.logLoading("&5Item Component Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Item Component", ex);
        }
    }

    private void loadNBTElements() {
        if (!this.config.elements_nbt) {
            Util.logLoading("&5NBT Elements &cdisabled via config");
            return;
        }
        NBTApi.initializeAPI(this.config);
        if (!NBTApi.isEnabled()) {
            String ver = Skript.getMinecraftVersion().toString();
            Util.logLoading("&5NBT Elements &cDISABLED!");
            Util.logLoading(" - Your server version [&b" + ver + "&7] is not currently supported by the NBT-API");
            Util.logLoading(" - This is not a bug!");
            Util.logLoading(" - NBT elements will resume once the API is updated to work with [&b" + ver + "&7]");
            return;
        }
        try {
            NBTElementRegistration.register(this.registration);
            new NBTListener(this.plugin);
            Util.logLoading("&5NBT Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("NBT", ex);
        }
    }

    private void loadOtherElements() {
        this.pluginManager.registerEvents(new EntityListener(), this.plugin);
        OtherElementRegistration.register(this.registration);
        // We're not going to add a config value for ONE effect
        ParticleElementRegistration.register(this.registration);
    }

    private void loadPropertyElements() {
        if (!this.config.elements_property) {
            Util.logLoading("&5Property elements &cdisabled via config");
            return;
        }
        PropertyElementRegistration.register(this.registration);
        Util.logLoading("&5Property Elements &asuccessfully loaded");
    }

    private void loadRayTraceElements() {
        if (!this.config.elements_raytrace) {
            Util.logLoading("&5RayTrace Elements &cdisabled via config");
            return;
        }
        try {
            RayTraceElementRegistration.register(this.registration);
            Util.logLoading("&5RayTrace Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("RayTrace", ex);
        }
    }

    private void loadRecipeElements() {
        if (!this.config.elements_recipe) {
            Util.logLoading("&5Recipe Elements &cdisabled via config");
            return;
        }
        try {
            RecipeElementRegistration.register(this.registration);
            Util.logLoading("&5Recipe Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Recipe", ex);
        }
    }

    private void loadRegistryElements() {
        try {
            RegistryElementRegistration.register(this.registration);
            Util.logLoading("&5Registry Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Registry", ex);
        }
    }

    private void loadScoreboardElements() {
        if (!this.config.elements_scoreboard) {
            Util.logLoading("&5Scoreboard Elements &cdisabled via config");
            return;
        }
        if (Classes.getClassInfoNoError("objective") != null || Classes.getExactClassInfo(Objective.class) != null) {
            Util.logLoading("&5Scoreboard Elements &cdisabled");
            Util.logLoading("&7It appears another Skript addon may have registered Scoreboard syntax.");
            Util.logLoading("&7To use SkBee Scoreboards, please remove the addon which has registered Scoreboard already.");
            return;
        }
        if (Util.IS_RUNNING_FOLIA) {
            Util.logLoading("&5Scoreboard Elements &cdisabled &7(&eCurrently not supported on Folia&7)");
            return;
        }
        try {
            ScoreboardElementRegistration.register(this.registration);
            Util.logLoading("&5Scoreboard Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Scoreboard", ex);
        }
    }

    private void loadStatisticElements() {
        if (!this.config.elements_statistic) {
            Util.logLoading("&5Statistic Elements &cdisabled via config");
            return;
        }
        if (Classes.getClassInfoNoError("statistic") != null || Classes.getExactClassInfo(Statistic.class) != null) {
            Util.logLoading("&5Statistic Elements &cdisabled");
            Util.logLoading("&7It appears another Skript addon may have registered Statistic syntax.");
            Util.logLoading("&7To use SkBee Statistics, please remove the addon which has registered Statistic already.");
            return;
        }
        try {
            StatisticElementRegistration.register(this.registration);
            Util.logLoading("&5Statistic Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Statistic", ex);
        }
    }

    private void loadStructureElements() {
        if (!this.config.elements_structure) {
            Util.logLoading("&5Structure Elements &cdisabled via config");
            return;
        }

        this.plugin.structureManager = new StructureManager();
        try {
            StructureElementRegistration.register(this.registration);
            Util.logLoading("&5Structure Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Structure", ex);
        }
    }

    private void loadSwitchCaseElements() {
        if (!this.config.elements_switch_case) {
            Util.logLoading("&5SwitchCase Elements &cdisabled via config");
            return;
        }
        try {
            SwitchCaseElementRegistration.register(this.registration);
            Util.logLoading("&5SwitchCase Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("SwitchCase", ex);
        }
    }

    private void loadTestingElements() {
        if (!TestMode.ENABLED) return;
        try {
            TestingElementRegistration.register(this.registration);
            Util.logLoading("&5Testing Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Testing", ex);
        }
    }

    private void loadTextElements() {
        if (Classes.getClassInfoNoError("textcomp") != null) {
            Util.logLoading("&5Text Component Elements &cdisabled");
            Util.logLoading("&7It appears another Skript addon may have registered Text Component syntax.");
            Util.logLoading("&7To use SkBee Text Components, please remove the addon which has registered Text Components already.");
            return;
        }
        try {
            TextElementRegistration.register(this.registration);
            Util.logLoading("&5Text Component Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Text Component", ex);
        }
    }

    private void loadTickManagerElements() {
        if (!this.config.elements_tick_manager) {
            Util.logLoading("&5Tick Manager Elements &cdisabled via config");
            return;
        }
        try {
            TickManagerElementRegistration.register(this.registration);
            Util.logLoading("&5Tick Manager Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Tick Manager", ex);
        }
    }

    private void loadVillagerElements() {
        if (!this.config.elements_villager) {
            Util.logLoading("&5Villager Elements &cdisabled via config");
            return;
        }
        try {
            VillagerElementRegistration.register(this.registration);
            Util.logLoading("&5Villager Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Villager", ex);
        }
    }

    private void loadVirtualFurnaceElements() {
        if (!this.config.elements_virtual_furnace) {
            Util.logLoading("&5Virtual Furnace Elements &cdisabled via config");
            return;
        }
        try {
            this.plugin.virtualFurnaceAPI = new VirtualFurnaceAPI(this.plugin, true);
            pluginManager.registerEvents(new VirtualFurnaceListener(), this.plugin);
            VirtualFurnaceElementRegistration.register(this.registration);
            Util.logLoading("&5Virtual Furnace Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("Virtual Furnace", ex);
        }
    }

    private void loadWorldCreatorElements() {
        if (!this.config.elements_world_creator) {
            Util.logLoading("&5World Creator Elements &cdisabled via config");
            return;
        }
        if (Util.IS_RUNNING_FOLIA) {
            Util.logLoading("&5World Creator Elements &cdisabled &7(&eCurrently not supported on Folia&7)");
            return;
        }
        try {
            this.plugin.beeWorldConfig = new BeeWorldConfig(this.plugin);
            WorldCreatorElementRegistration.register(this.registration);
            Util.logLoading("&5World Creator Elements &asuccessfully loaded");
        } catch (Exception ex) {
            logFailure("World Creator", ex);
        }
    }

    @SuppressWarnings("ThrowableNotThrown")
    private void logFailure(String element, Exception ex) {
        Throwable cause = ex.getCause();
        String message = cause != null ? cause.getMessage() : ex.getMessage();
        Skript.exception(ex);
        Util.logLoading("&e%s Elements &7failed to load due to &r'&c%s&r'", element, message);
    }

    private boolean isPlugmanReloaded() {
        for (StackTraceElement stackTraceElement : Thread.currentThread().getStackTrace()) {
            if (stackTraceElement.toString().contains("rylinaux.plugman.command.")) {
                return true;
            }
        }
        return false;
    }

}
