/// <reference path="..\..\GSMyAdmin\WebRoot\Scripts\UI.js" />
/// <reference path="..\..\GSMyAdmin\WebRoot\Scripts\API.js" />
/// <reference path="..\..\GSMyAdmin\WebRoot\Scripts\Common.js" />

/* jshint undef: true, unused: false */
/* global API,UI,PluginHandler */
/* eslint eqeqeq: "off", curly: "error", require-atomic-updates: "off" */

this.plugin = {
    PreInit: function () {
        //Called prior to the plugins initialisation, before the tabs are loaded.
        //This method must not invoke any module/plugin specific API calls.
    },

    PostInit: function () {
        //The tabs have been loaded. You should wire up any event handlers here.
        UI.AddNotificationRemovedCallback(notificationEnded);
        UI.SetCustomConsoleMessageProcesssor(consoleLineProcessor);

        $("#tab_MinecraftModule_Plugins").scroll(viewScrolled);

        resetWizardHandlers();
    },

    AMPDataLoaded: function () {
        Features.FileManagerPlugin.RegisterContextMenuHandler("properties", "Import Configuration", importSettingsFile);
        Features.FileManagerPlugin.RegisterContextMenuHandler("jar", "Set as startup jar", updateJarFile);
    },

    Reset: function () {

    },

    StartupFailure: handleFailure,

    SettingChanged: updateSettingVisibility
};

this.tabs = [
    {
        File: "BukkitPlugins.html", //URL to fetch the tab contents from. Relative to the plugin WebRoot directory.
        ExternalTab: false,   //If True, 'File' is treated as an absolute URL to allow contents to be loaded from elsewhere.
        //Note that the appropriate CORS headers are required on the hosting server to allow this.
        ShortName: "Plugins",    //Name used for the element. Prefixed with tab_PLUGINNAME_
        Name: "Plugins",     //Display name for the tab.
        Icon: "widgets",             //Icon to show in the tab.
        BodyClass: "noPaddingTab",
        Category: "Settings",
        Click: function () { pluginsVM.init(); },
        RequiredPermission: "Minecraft.PluginManagement.BrowsePluginRepository"
    }
];

this.stylesheet = "CSS/Stylesheet.css";    //Styles for tab-specific styles

//Your modules private code goes here.

const pluginsVM = new PluginBrowserVM();

function updateJarFile(file) {
    currentSettings["MinecraftModule.Minecraft.ServerJAR"].value(file.name);
}

async function importSettingsFile(file) {
    const data = await file.getContentsAsync();

    const settingMap = {
        "level-seed": "MinecraftSettings.WorldSeed",
        "level-type": "MinecraftSettings.WorldType",
        "view-distance": "MinecraftSettings.ViewDistance",
        "level-name": "MinecraftSettings.LevelName",
        "generator-settings": "MinecraftSettings.WorldGenSettings",
        "generate-structures": "MinecraftSettings.GenStructures",
        "allow-nether": "MinecraftSettings.EnableNether",
        "resource-pack": "MinecraftSettings.ResourcePackURL",
        "resource-pack-sha1": "MinecraftSettings.ResourcePackSHA1",
        "use-native-transport": "MinecraftSettings.UseNativeTransport",
        "prevent-proxy-connections": "MinecraftSettings.PreventProxy",
        "motd": "MinecraftSettings.ServerMOTD",
        "snooper-enabled": "MinecraftSettings.AllowAnalytics",
        "max-world-size": "MinecraftSettings.MaxWorldSize",
        "network-compression-threshold": "MinecraftSettings.NetworkCompressionThreshold",
        "require-resource-pack": "MinecraftSettings.RequireResourcePack",
        "sync-chunk-writes": "MinecraftSettings.SyncChunkWrites",
        "hide-online-players": "MinecraftSettings.HideOnlinePlayers",
        "simulation-distance": "MinecraftSettings.SimulationDistance",
        "rate-limit": "MinecraftSettings.RateLimit",
        "entity-broadcast-range-percentage": "MinecraftSettings.EntityBroadcastRange",
        "resource-pack-prompt": "MinecraftSettings.ResourcePackPrompt",
        "enable-jmx-monitoring": "MinecraftSettings.EnableJMX",
        "announce-player-achievements": "MinecraftSettings.AnnouncePlayerAchievements",

        "max-players": "LimitsSettings.MaxPlayers",
        "max-chained-neighbor-updates": "LimitsSettings.MaxChainedNeigbourUpdates",

        "force-gamemode": "GameSettings.ForceGameMode",
        "gamemode": "GameSettings.GameMode",
        "difficulty": "GameSettings.Difficulty",
        "op-permission-level": "GameSettings.OpPermissions",
        "function-permission-level": "GameSettings.FuncPermissions",
        "spawn-protection": "GameSettings.SpawnProtectionRadius",
        "hardcore": "GameSettings.HardcoreMode",
        "spawn-monsters": "GameSettings.EnableMonsters",
        "spawn-animals": "GameSettings.EnableAnimals",
        "spawn-npcs": "GameSettings.EnableNPCs",
        "pvp": "GameSettings.EnablePVPCombat",
        "allow-flight": "GameSettings.AllowFlight",
        "enable-command-block": "GameSettings.AllowCommandBlocks",
        "player-idle-timeout": "GameSettings.TimeoutMins",
        "enforce-whitelist": "GameSettings.Whitelist",
        "white-list": "GameSettings.Whitelist",
        "max-tick-time": "GameSettings.MaxTickTime",
        "max-build-height": "GameSettings.MaxBuildHeight"
    };

    const regex = /^(\w.+)=(.*)$/gm;
    let match = regex.exec(data);
    let failedLines = [];
    let success = 0;
    let settingUpdates = {};

    while (match != null) {
        const key = match[1], value = match[2];

        if (settingMap.hasOwnProperty(key)) {
            const node = "MinecraftModule." + settingMap[key];
            settingUpdates[node] = value;
            success++;
        }
        else {
            failedLines.push(match[0]);
        }

        match = regex.exec(data);
    }

    await API.Core.SetConfigs(settingUpdates);

    const failureMessage = (failedLines.length > 0) ? "The following lines could not be imported:\r\n\r\n" + failedLines.join("\r\n") : "";   

    UI.ShowModalAsync("Import Complete", `${success} entries were successfully imported. See advanced details for information on any failures.`, UI.Icons.Info, UI.OKActionOnly, null, null, failureMessage);
}

function updateSettingVisibility(node, value) {
    if (node === "MinecraftModule.Minecraft.ServerType") {
        const hideNodes = ["MinecraftModule.Minecraft.FTBModpack", "MinecraftModule.Minecraft.FTBModpackNew", "MinecraftModule.Minecraft.SpecificVersion", "MinecraftModule.Minecraft.SpecificForgeVersion", "MinecraftModule.Minecraft.SpecificNeoForgeVersion",
            "MinecraftModule.Minecraft.SpecificSpongeVersion", "MinecraftModule.Minecraft.SpecificSpigotVersion", "MinecraftModule.Minecraft.SpecificPaperVersion",
            "MinecraftModule.Minecraft.CustomStartupString", "MinecraftModule.Minecraft.CustomNormalizerRegex", "MinecraftModule.Minecraft.CustomNormalizerReplacement",
            "MinecraftModule.Minecraft.SpecificPurpurVersion", "MinecraftModule.Minecraft.FabricMCVersion", "MinecraftModule.Minecraft.FabricLoaderVersion", "MinecraftModule.Minecraft.FabricInstallerVersion", "MinecraftModule.Minecraft.SpecificBedrockVersion"];

        const showNodes = ["MinecraftModule.Game.SpawnProtectionRadius"];

        for (const element of hideNodes) {
            let n = element;
            setSettingVisibility(n, false);
        }

        for (const element of showNodes) {
            let node = element;
            setSettingVisibility(node, true);
        }

        switch (parseInt(value)) {
            case 10:
                setSettingVisibility("MinecraftModule.Minecraft.SpecificVersion", true);
                break;
            case 20:
            case 30:
                setSettingVisibility("MinecraftModule.Minecraft.SpecificSpigotVersion", true);
                break;
            case 31:
                setSettingVisibility("MinecraftModule.Minecraft.SpecificPaperVersion", true);
                break;
            case 32:
                setSettingVisibility("MinecraftModule.Minecraft.SpecificPurpurVersion", true);
                break;
            case 33:
                setSettingVisibility("MinecraftModule.Minecraft.FabricMCVersion", true);
                setSettingVisibility("MinecraftModule.Minecraft.FabricLoaderVersion", true);
                setSettingVisibility("MinecraftModule.Minecraft.FabricInstallerVersion", true);
                break;
            case 50:
                setSettingVisibility("MinecraftModule.Minecraft.SpecificForgeVersion", true);
                break;
            case 51:
                setSettingVisibility("MinecraftModule.Minecraft.SpecificNeoForgeVersion", true);
                break;
            case 120:
                setSettingVisibility("MinecraftModule.Minecraft.FTBModpack", true);
                break;
            case 122:
                setSettingVisibility("MinecraftModule.Minecraft.FTBModpackNew", true);
                break;
            case 60:
                setSettingVisibility("MinecraftModule.Minecraft.SpecificSpongeVersion", true);
                break;
            case 140: //Proxies
            case 150:
                break;
            case 210: //Bedrock
                setSettingVisibility("MinecraftModule.Game.SpawnProtectionRadius", false);
                setSettingVisibility("MinecraftModule.Minecraft.SpecificBedrockVersion", true);
                break;
            case 999:
                setSettingVisibility("MinecraftModule.Minecraft.CustomStartupString", true);
                setSettingVisibility("MinecraftModule.Minecraft.CustomNormalizerRegex", true);
                setSettingVisibility("MinecraftModule.Minecraft.CustomNormalizerReplacement", true);
                break;
        }
    }
}

const failureReasons = {
    EULA: "EULA"
};

async function handleFailure() {
    const result = await API.MinecraftModule.GetFailureReasonAsync();

    switch (result) {
        case failureReasons.EULA:
            {
                const acceptEULAResult = await UI.ShowModalAsync("Minecraft Server EULA",
                    {
                        text: "You must accept the Mojang EULA before you may run the Minecraft server.",
                        subtitle: "By clicking 'accept' you indicate that you have read and accept the terms of the linked agreement."
                    }, UI.Icons.Info,
                    [
                        new UI.ModalAction("Accept", true, "bgGreen"),
                        new UI.ModalAction("Decline", false, "bgRed")
                    ], "Minecraft End User Licence Agreement", "https://account.mojang.com/documents/minecraft_eula");

                if (acceptEULAResult === true) {
                    await API.MinecraftModule.AcceptEULAAsync();
                    await API.Core.StartAsync();
                }
                break;
            }
        default:
            UI.ShowModalAsync("Unable to start server.", result == "" ? "No specific reason was given. See logs for further information." : result, UI.Icons.Exclamation, UI.OKActionOnly);
    }
}

function consoleLineProcessor(element) {
    //MB: If we end up with a 3rd or more expression, put them in an array and loop through them
    //instead of just adding more elseifs.
    const matchTextComponent = /TextComponent\{text='(.+?)',.+\}/;
    const matchGenericReason = /TranslatableComponent\{key='disconnect\.genericReason', args=\[(.+?)\],.+\}/;
    let text = $(element).text();

    if (text.match(matchTextComponent)) {
        text = text.replace(matchTextComponent, "$1");
        element.text(text);
        return true;
    }
    else if (text.match(matchGenericReason)) {
        text = text.replace(matchGenericReason, "$1");
        element.text(text);
        return true;
    }

    return false;
}

let BukgetCategoriesLoaded = false;

function PluginBrowserVM() {
    const self = this;
    this.categories = ko.observableArray(); //of pluginCategoryVM
    this.plugins = ko.observableArray(); //of pluginInfoVM

    this.selectedCategory = ko.observable(null);
    this.respCategory = ko.observable(null);
    this.categoriesLoaded = ko.observable(false);
    this.moreResultsAvailable = ko.observable(false);
    this.searchVisible = ko.observable(false);
    this.topSearchVisible = ko.observable(false);
    this.searchQuery = ko.observable("");
    this.lastPage = 0;
    this.installedPlugins = {};
    this.installQueue = {};
    this.needsUpdatePlugins = {};
    this.waitingForScroll = false;

    this.showThirdParty = ko.observable(!parseBool(localStorage.thirdPartySpiget));
    this.skipThirdParty = ko.observable(false);

    this.searchVM = new PluginCategoryVM();
    this.searchVM.special = "Search";
    this.searchVM.name = "Search";
    this.searchVM.vm = self;
    this.searchVM.selected(true);
    this.selectedCategory(this.searchVM);

    const popularVM = new PluginCategoryVM();
    popularVM.special = "Popular";
    popularVM.name = "Popular Plugins";
    popularVM.vm = self;

    const installedVM = new PluginCategoryVM();
    installedVM.special = "Installed";
    installedVM.name = "Installed Plugins";
    installedVM.vm = self;

    //var pendingUpdateVM = new pluginCategoryVM();
    //pendingUpdateVM.special = "PendingUpdate";
    //pendingUpdateVM.name = "Updates Available";
    //pendingUpdateVM.vm = self;

    this.respCategory.subscribe(function (newValue) {
        if (newValue == null) { return; }
        newValue.click();
    });

    this.init = async function () {
        if (self.categoriesLoaded() === true) { return; }

        UI.ApplyVMBinding(pluginsVM, document.getElementById("tab_MinecraftModule_Plugins"));

        const data = await API.MinecraftModule.BukGetCategoriesAsync();
        const mapped = ko.quickmap.to(PluginCategoryVM, data, false, { vm: self });

        self.categories.push(self.searchVM);
        self.categories.push(popularVM);
        self.categories.push(installedVM);
        ko.utils.arrayPushAll(self.categories, mapped);
        await self.refreshInstalledPlugins();

        self.categoriesLoaded(true);
        self.searchVisible(true);
    };

    this.refreshInstalledPlugins = async function () {
        self.installedPlugins = await API.MinecraftModule.BukGetInstalledPluginsAsync();
        self.installedPluginKeys = {};

        for (const plugin of self.installedPlugins) {
            self.installedPluginKeys[plugin.id] = plugin;
        }
    };

    this.searchPlugins = async function () {
        self.topSearchVisible(true);
        self.searchVisible(false);
        self.lastPage = 1;
        const source = await API.MinecraftModule.BukGetSearchAsync(self.searchQuery(), self.lastPage, pageSize);
        self.plugins.removeAll();
        ko.utils.arrayPushAll(self.plugins, ko.quickmap.to(pluginInfoVM, source));
    };

    this.feelingLucky = async function () {
        await self.searchPlugins();
        if (self.plugins.length > 0) {
            self.plugins()[0].install();
        }
    }

    this.scrollPage = async function () {
        if (self.selectedCategory().special != null) { return; }
        if (self.waitingForScroll) { return; }

        self.waitingForScroll = true;
        self.lastPage++;
        let newResults = [];
        switch (self.selectedCategory().special) {
            case "Search":
                newResults = await API.MinecraftModule.BukGetSearch(self.searchQuery(), self.lastPage, pageSize);
                break;
            case null:
                newResults = await API.MinecraftModule.BukGetPluginsForCategoryAsync(self.selectedCategory().id, self.lastPage, pageSize);
                break;
            default:
                self.lastPage--;
                self.waitingForScroll = false;
                return;
        }

        if (newResults.length == 0) { return; }

        ko.utils.arrayPushAll(self.plugins, ko.quickmap.to(pluginInfoVM, newResults));
        self.moreResultsAvailable(newResults.length === pageSize);
        self.waitingForScroll = false;
    };

    this.pluginIsInstalled = function (pluginId) {
        return self.installedPluginKeys[pluginId] != undefined;
    };

    this.ackThirdParty = function () {
        self.showThirdParty(false);
        localStorage.thirdPartySpiget = self.skipThirdParty();
    };
}

function PluginCategoryVM() {
    const self = this;
    this.id = -1;
    this.name = "";
    this.special = null;
    this.selected = ko.observable(false);
    this.vm = null;

    this.click = async function () {
        let source = null;

        const selected = self.vm.selectedCategory();
        if (selected != null) {
            selected.selected(false);
        }
        self.vm.selectedCategory(self);
        self.selected(true);

        self.vm.searchVisible(false);

        $("#tab_MinecraftModule_Plugins").scrollTop(0);
        self.vm.plugins.removeAll();

        if (self.id > -1) {
            self.vm.lastPage = 1;
            self.vm.moreResultsAvailable(true);
            source = await API.MinecraftModule.BukGetPluginsForCategoryAsync(self.id, self.vm.lastPage, pageSize);
            self.vm.moreResultsAvailable(source.length === pageSize);
        } else if (self.special != null) {
            self.vm.lastPage = 1;
            self.vm.moreResultsAvailable(false);

            switch (self.special) {
                case "Popular":
                    source = await API.MinecraftModule.BukGetPopularPluginsAsync();
                    break;
                case "Installed":
                    source = self.vm.installedPlugins;
                    break;
                case "PendingUpdate":
                    source = self.vm.needsUpdatePlugins;
                    break;
                case "Search":
                    self.vm.topSearchVisible(false);
                    self.vm.searchVisible(true);
                    self.vm.plugins.removeAll();
                    return;
            }
        }

        ko.utils.arrayPushAll(self.vm.plugins, ko.quickmap.to(pluginInfoVM, source));
    };
}

function pluginInfoVM() {
    const self = this;
    this.id = ko.observable(0);
    this.name = ko.observable("");
    this.tag = ko.observable("");
    this.downloads = 0;
    this.version = ko.observable(null);
    this.icon = ko.observable(null);
    this.PluginFilename = ko.observable("");

    this.imageURI = ko.computed(function () {
        const icon = self.icon();
        return icon == null || icon.data == undefined || icon.data === "" ? "/Plugins/MinecraftModule/Images/NoImage.png" : "data:image/jpg;base64," + icon.data;
    });
    this.discussionURI = ko.computed(function () { return `https://www.spigotmc.org/resources/${self.id()}`; });
    this.downloadURI = ko.computed(function () { return `https://www.spigotmc.org/resources/${self.id()}/download?version=${self.version()}`; });
    this.installed = ko.computed(function () { return pluginsVM.pluginIsInstalled(self.id()); });

    this.install = async function () {
        pluginsVM.installedPlugins[self.id()] = this;
        const result = await API.MinecraftModule.BukGetInstallUpdatePluginAsync(self.id());
        pluginsVM.installQueue[result.Id] = self;
        pluginsVM.refreshInstalledPlugins();
    };

    this.uninstall = async function () {
        const confirmResult = await UI.ShowModalAsync("Confirm Plugin Uninstallation", `Are you sure you wish to uninstall the ${self.name()} plugin?`, UI.Icons.Question, [
            new UI.ModalAction("Uninstall", true, "bgRed"),
            new UI.ModalAction("Cancel", false)
        ]);

        if (confirmResult === true) {
            await API.MinecraftModule.BukGetRemovePluginAsync(self.id);
            pluginsVM.refreshInstalledPlugins();
        }
    };
}

let pageSize = 30;

function notificationEnded(id) {
    if (pluginsVM.installQueue[id] != undefined) {
        const plugin = pluginsVM.installQueue[id];
        plugin.installed(true);
        pluginsVM.installQueue[id] = undefined;
        pluginsVM.refreshInstalledPlugins();
    }
}

function viewScrolled() {
    const thisEl = $(this);
    if (thisEl.scrollTop() > thisEl[0].scrollHeight - 1200 && pluginsVM.moreResultsAvailable() && !pluginsVM.waitingForScroll) {
        pluginsVM.scrollPage();
    }
}