/*********************************************************************
 * Shortcuts is Copyright (C) 2016-2018 Kyle Robbertze
 * African Institute for Mathematical Sciences, South Africa
 *
 * Shortcuts is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * Shortcuts is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Shortcuts.  If not, see <http://www.gnu.org/licenses/>.
 **********************************************************************/

import GLib from "gi://GLib";
import Gio from "gi://Gio";
import Shell from "gi://Shell";
import St from "gi://St";
import Cogl from "gi://Cogl";
import Meta from "gi://Meta";
import Clutter from "gi://Clutter";

import { Extension, gettext as _ } from "resource:///org/gnome/shell/extensions/extension.js";

import * as Main from "resource:///org/gnome/shell/ui/main.js";

export default class Shortcuts extends Extension {
    /*
     * Enables the plugin by adding listeners and icons as necessary
     */
    enable() {
        this._settings = this.getSettings();
        this._settingSignals = [];

        const settingsToConnect = [
            { key: "show-icon", method: this._toggleIcon },
            { key: "use-custom-shortcuts", method: this._setShortcutsFile },
            { key: "use-transparency", method: this._setTransparency },
            { key: "visibility", method: this._setTransparency },
            {
                key: "enable-appspecific-files",
                method: this._setEnableAppspecific,
            },
        ];

        for (const { key, method } of settingsToConnect) {
            this._settingSignals.push(this._settings.connect(`changed::${key}`, method.bind(this)));
        }
        this._isAdded = false;
        // eslint-disable-next-line no-unused-vars
        Main.overview._specialToggle = (evt) => {
            this._toggleShortcuts();
        };

        Main.wm.addKeybinding(
            "shortcuts-toggle-overview",
            this._settings,
            Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
            Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
            this._toggleShortcuts.bind(this)
        );

        // Initialize settings
        this._setEnableAppspecific();
        this._setTransparency();
        this._setShortcutsFile();
        this._toggleIcon();
    }

    /**
     * Removes all traces of the listeners and icons that the extension created
     */
    disable() {
        //Remove setting Signals
        for (const signal of this._settingSignals) {
            this._settings.disconnect(signal);
        }
        this._settingSignals = null;
        this._settings = null;
        if (this._isAdded) {
            Main.panel._rightBox.remove_child(this._button);
            this._button = null;
            this._isAdded = false;
        }
        Main.wm.removeKeybinding("shortcuts-toggle-overview");
        delete Main.overview._specialToggle;
        this._visible = false;
    }
    /**
     * Builds the pop-up and shows the shortcut description list
     */
    _toggleShortcuts() {
        if (this._visible) {
            // Hide popup
            this._hideShortcuts();
        } else {
            if (!this._stage) {
                // Show popup
                this._columns = this._settings.get_int("maxcolumns");
                this._stage = new St.BoxLayout({
                    style_class: "background-boxlayout",
                    orientation: Clutter.Orientation.VERTICAL,
                });
                this._main_panel = new St.BoxLayout({
                    style_class: "panel-boxlayout",
                    orientation: Clutter.Orientation.HORIZONTAL,
                });

                this._stage.add_child(this._main_panel);

                this._child1_panel = new St.BoxLayout({
                    style_class: "child-boxlayout",
                    orientation: Clutter.Orientation.VERTICAL,
                });
                this._child2_panel = new St.BoxLayout({
                    style_class: "child-boxlayout",
                    orientation: Clutter.Orientation.VERTICAL,
                });
                this._main_panel.add_child(this._child1_panel);
                this._main_panel.add_child(this._child2_panel);
                if (this._columns >= 3) {
                    this._child3_panel = new St.BoxLayout({
                        style_class: "child-boxlayout",
                        orientation: Clutter.Orientation.VERTICAL,
                    });
                    this._main_panel.add_child(this._child3_panel);
                }
                if (this._columns >= 4) {
                    this._child4_panel = new St.BoxLayout({
                        style_class: "child-boxlayout",
                        orientation: Clutter.Orientation.VERTICAL,
                    });
                    this._main_panel.add_child(this._child4_panel);
                }
                if (this._columns === 5) {
                    this._child5_panel = new St.BoxLayout({
                        style_class: "child-boxlayout",
                        orientation: Clutter.Orientation.VERTICAL,
                    });
                    this._main_panel.add_child(this._child4_panel);
                }
                this._readShortcuts();

                this._stage.add_child(
                    new St.Label({
                        style_class: "superkey-prompt",
                        text: _("The super key is the Windows key on most keyboards"),
                    })
                );

                Main.uiGroup.add_child(this._stage);
            }

            const color = new Cogl.Color({
                red: 10,
                blue: 10,
                green: 10,
                alpha: 255,
            });
            color.alpha = 255 * this._visibility;
            this._stage.set_background_color(color);

            const monitor = Main.layoutManager.primaryMonitor;

            this._stage.set_position(
                monitor.x + Math.floor(monitor.width / 2 - this._stage.width / 2),
                monitor.y + Math.floor(monitor.height / 2 - this._stage.height / 2)
            );
            this._visible = true;
        }
    }

    /**
     * gets the panel to which the next shortcut should be added based on the list progress value (2 columns).
     *
     * @param {*} listProgress list progress value between 0 and 1
     * @returns {*} panel to which the next shortcut should be added 1 or 2 columns
     */
    _getPanel2columns(listProgress) {
        return listProgress < 0.5 ? this._child1_panel : this._child2_panel;
    }
    /**
     * gets the panel to which the next shortcut should be added based on the list progress value (3 columns).
     *
     * @param {*} listProgress list progress value between 0 and 1
     * @returns {*} panel to which the next shortcut should be added 1,2 or 3 columns
     */
    _getPanel3columns(listProgress) {
        const panels = [this._child1_panel, this._child2_panel, this._child3_panel];
        const index = Math.min(Math.floor(listProgress * 3), 2);
        return panels[index];
    }
    /**
     * gets the panel to which the next shortcut should be added based on the list progress value (4 columns).
     *
     * @param {*} listProgress list progress value between 0 and 1
     * @returns {*} panel to which the next shortcut should be added 1,2,3 or 4 columns
     */
    _getPanel4columns(listProgress) {
        const panels = [this._child1_panel, this._child2_panel, this._child3_panel, this._child4_panel];
        const index = Math.min(Math.floor(listProgress * 4), 3);
        return panels[index];
    }
    /**
     * gets the panel to which the next shortcut should be added based on the list progress value (5 columns).
     *
     * @param {*} listProgress list progress value between 0 and 1
     * @returns {*} panel to which the next shortcut should be added 1,2,3,4 or 5 columns
     */
    _getPanel5columns(listProgress) {
        const panels = [
            this._child1_panel,
            this._child2_panel,
            this._child3_panel,
            this._child4_panel,
            this._child5_panel,
        ];
        const index = Math.min(Math.floor(listProgress * 5), 4);
        return panels[index];
    }

    /**
     * gets the panel to which the next shortcut should be added based on the list progress value.
     *
     * @param {*} listProgress list progress value between 0 and 1
     * @returns {*} panel to which the next shortcut should be added
     */
    _getPanel(listProgress) {
        switch (this._columns) {
            case 2:
                return this._getPanel2columns(listProgress);
            case 3:
                return this._getPanel3columns(listProgress);
            case 4:
                return this._getPanel4columns(listProgress);
            case 5:
                return this._getPanel5columns(listProgress);
            default:
                Main.notifyError(_("Invalid number of columns"));
                this.getLogger().error(_("Invalid number of columns"));
                return null;
        }
    }

    /**
     * Description placeholder
     *
     * @param {*} APP_ID application ID for which to get the shortcuts file
     * @returns {string} path to the app-specific shortcuts file
     */
    _getAppIDjson(APP_ID) {
        const systempath = "/etc/" + this.uuid + "/" + APP_ID;
        const userpath = GLib.get_user_config_dir() + "/" + this.uuid + "/" + APP_ID;
        if (GLib.file_test(userpath, GLib.FileTest.EXISTS)) {
            return userpath;
        }
        if (GLib.file_test(systempath, GLib.FileTest.EXISTS)) {
            return systempath;
        }
        const msg = _("Application spedific Shortcuts file not found: '%s'").format(APP_ID);
        this.getLogger().log(msg);
        return "";
    }
    /**
     * Reads the shortcuts from a file specified in the settings. If this is not
     * there then it defaults to the shortcuts file provided by the extension.
     */
    _readShortcuts() {
        let SHORTCUTS_FILE = this._settings.get_boolean("use-custom-shortcuts")
            ? this._settings.get_string("shortcuts-file")
            : this.dir.get_child("shortcuts.json").get_path();
        if (this._appspecific) {
            const focusApp = Shell.WindowTracker.get_default().focus_app;
            if (focusApp) {
                const APP_ID = focusApp.id;
                const APP_JSON = this._getAppIDjson(APP_ID.slice(0, -8) + ".json");
                if (APP_JSON) {
                    SHORTCUTS_FILE = APP_JSON;
                }
            }
        }
        if (!GLib.file_test(SHORTCUTS_FILE, GLib.FileTest.EXISTS)) {
            const msg = _("Shortcuts file not found: '%s'").format(SHORTCUTS_FILE);
            Main.notifyError(msg);
            this.getLogger().error(msg);
            return;
        }
        const file = Gio.file_new_for_path(SHORTCUTS_FILE);
        const [result, contents] = file.load_contents(null);
        if (!result) {
            const msg = _("Unable to read file: '%s'").format(SHORTCUTS_FILE);
            Main.notifyError(msg);
            this.getLogger().error(msg);
            return;
        }

        const shortcuts = JSON.parse(new TextDecoder().decode(contents));
        let shortcutLength = shortcuts.length;
        for (const scelement of shortcuts) {
            shortcutLength += scelement.shortcuts.length;
        }

        let listProgress = 0;
        for (const i_element of shortcuts) {
            listProgress += (i_element.shortcuts.length * 1) / shortcutLength;
            const panel = this._getPanel(listProgress);
            panel.add_child(
                new St.Label({
                    style_class: "shortcut-section",
                    text: _(i_element.name),
                })
            );
            for (const j_element of i_element.shortcuts) {
                const item_panel = new St.BoxLayout({
                    style_class: "item-boxlayout",
                    orientation: Clutter.Orientation.HORIZONTAL,
                });
                const key = _(j_element.key);
                const description = _(j_element.description);
                item_panel.add_child(
                    new St.Label({
                        style_class: "shortcut-key-label",
                        text: key,
                    })
                );
                item_panel.add_child(
                    new St.Label({
                        style_class: "shortcut-description-label",
                        text: description,
                    })
                );
                panel.add_child(item_panel);
            }
        }
    }

    /**
     * Removes the actors used to make the pop-up describing the shortcuts.
     */
    _hideShortcuts() {
        this._main_panel.remove_child(this._child1_panel);
        this._main_panel.remove_child(this._child2_panel);
        if (this._columns >= 3) {
            this._main_panel.remove_child(this._child3_panel);
        }
        if (this._columns >= 4) {
            this._main_panel.remove_child(this._child4_panel);
        }
        if (this._columns === 5) {
            this._main_panel.remove_child(this._child5_panel);
        }
        this._stage.remove_child(this._main_panel);
        Main.uiGroup.remove_child(this._stage);
        this._child1_panel = null;
        this._child2_panel = null;
        this._child3_panel = null;
        this._child4_panel = null;
        this._child5_panel = null;
        this._main_panel = null;
        this._stage = null;
        this._visible = false;
    }

    /*
     * Shows or hides the icon in the right box of the top panel as the user
     * changes the setting
     */
    _toggleIcon() {
        const SHOW_ICON = this._settings.get_boolean("show-icon");
        if (!SHOW_ICON) {
            if (this._isAdded) {
                Main.panel._rightBox.remove_child(this._button);
                this._isAdded = false;
            }
            return;
        }
        if (!this._isAdded) {
            this._button = new St.Bin({
                style_class: "panel-button",
                reactive: true,
                can_focus: true,
                x_expand: true,
                y_expand: false,
                track_hover: true,
            });
            const icon = new St.Icon({
                icon_name: "preferences-desktop-keyboard-shortcuts-symbolic",
                style_class: "system-status-icon",
            });

            this._button.set_child(icon);
            this._button.connect("button-press-event", this._onButtonPress.bind(this));

            Main.panel._rightBox.insert_child_at_index(this._button, 0);
            this._isAdded = true;
        }
    }

    /**
     * handles the button press events on the icon in the top panel.
     */
    _onButtonPress(actor, event) {
        const button = event.get_button();
        if (button === Clutter.BUTTON_MIDDLE) {
            // Middle click
            this.openPreferences();
        } else {
            // Left click and right click
            this._toggleShortcuts();
        }
        return Clutter.EVENT_STOP;
    }

    /**
     * Updates the shortcut file location when it is changed in the settings
     */
    _setShortcutsFile() {
        if (!this._settings.get_boolean("use-custom-shortcuts")) {
            this._settings.set_string("shortcuts-file", this.dir.get_child("shortcuts.json").get_path());
        }
    }
    /**
     * Sets the transparency of the shortcuts pop-up based on the settings
     */
    _setTransparency() {
        if (this._settings.get_boolean("use-transparency")) {
            this._visibility = this._settings.get_int("visibility") / 100;
        } else {
            this._visibility = 1;
        }
    }
    /**
     * Sets whether the extension should use app-specific shortcuts files
     * and creates the directory for them if it does.
     */
    _setEnableAppspecific() {
        this._appspecific = this._settings.get_boolean("enable-appspecific-files");
        if (this._appspecific) {
            const path = GLib.get_user_config_dir() + "/" + this.uuid;
            const exitCode = GLib.mkdir_with_parents(path, 493);
            if (exitCode === -1) {
                const msg = _("Failed to create directory: '%s'").format(path);
                this.getLogger().log(msg);
            }
        }
    }
}
