/// <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);
        Features.FileManagerPlugin.RegisterControlledFile("server.properties", true, importSettingsFile, "Please import any changes so they are not overwritten by AMP.");
    },

    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 regex = /^(\w.+)=(.*)$/gm;
    let match = regex.exec(data);
    let modifiedLines = [];
    let failedLines = [];
    let ignoredLines = [];
    let success = 0;

    //AMP manages these settings
    const skipSettings = ["server-ip", "server-port", "server-portv6"];

    const allTaggedSettings = Object.values(currentSettings).filter(s => s.node.startsWith("MinecraftModule") && s.tag != "" && !skipSettings.includes(s.tag));
    //Turn into an object where setting.tag is the key and setting.node is the value

    const tagToNodeMap = Object.fromEntries(
        allTaggedSettings
            .filter(s => typeof s.tag === "string" && s.tag.length > 0 && typeof s.node === "string")
            .map(s => [s.tag, s.node])
    );

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

        if (tagToNodeMap.hasOwnProperty(key)) {
            const node = tagToNodeMap[key];
            if (!currentSettings[node]) {
                failedLines.push(match[0]);
                match = regex.exec(data);
                continue;
            }

            if (currentSettings[node].settingType === "Enum") {
                const enumSetting = currentSettings[node].enumValues().find(v => v.name.toLocaleLowerCase() == value);
                if (enumSetting != null) { compareValue = enumSetting.value.toString(); }
            }

            if (currentSettings[node].value().toString() != compareValue) {
                modifiedLines.push(match[0]);
                currentSettings[node].setTypedValue(compareValue);
            }
            success++;
        }
        else {
            ignoredLines.push(match[0]);
        }

        match = regex.exec(data);
    }

    let infoMessage = "";
    infoMessage += (modifiedLines.length > 0) ? "The following settings had changes that were imported:\r\n\r\n" + modifiedLines.join("\r\n") + "\r\n\r\n" : "";
    infoMessage += (ignoredLines.length > 0) ? "The following lines were ignored:\r\n\r\n" + ignoredLines.join("\r\n") + "\r\n\r\n" : "";
    infoMessage += (failedLines.length > 0) ? "The following lines could not be imported:\r\n\r\n" + failedLines.join("\r\n") : "";   
    const title = (failedLines.length > 0) ? "Some settings were not imported." : "";

    UI.ShowModalAsync("Import Complete", `${success} modified entries were successfully imported. ${title}`, UI.Icons.Info, UI.OKActionOnly, null, null, infoMessage);
}

function updateSettingVisibility(node, value) {
    if (node === "MinecraftModule.Java.UseContainerMemoryLimits") {
        const useMemLimit = parseBool(value);
        setSettingDisabled("MinecraftModule.Java.MaxHeapSizeMB", useMemLimit);
        setSettingDisabled("MinecraftModule.Java.MinHeapSizeMB", useMemLimit);
    }
    else 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();
                }
                else {
                    await API.MinecraftModule.RejectEULAAsync();
                }
                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._refreshing) return;
        self._refreshing = true;
        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);
        self._refreshing = false;
    };

    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();
    }
}