/* systemMenu.js
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

/* exported SystemMenuButton */

const { Gio, GLib, GObject, Shell, St } = imports.gi;

const ExtensionUtils = imports.misc.extensionUtils;
const LoginManager = imports.misc.loginManager;
const Util = imports.misc.util;

const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const QuickSettings = imports.ui.quickSettings;

const _ = ExtensionUtils.gettext;

const BASIC_SETTINGS = [
    'ca.desrt.dconf-editor.desktop',    // dconf-editor
    'org.gnome.Extensions.desktop',     // gnome-shell-extension-prefs
    'org.gnome.Software.desktop',       // gnome-software
    'org.gnome.tweaks.desktop',         // gnome-tweaks
    'synaptic.desktop',                 // synaptic
    'system-config-printer.desktop',    // system-config-printer
    'users.desktop',                    // gnome-system-tools
]

const GNOME_TERMINAL = 'org.gnome.Terminal.desktop';

const KEY_DISABLE_RESTART_BUTTONS = 'disable-restart-buttons';
const KEY_SHOW_BASIC_SETTINGS = 'show-basic-settings';
const KEY_SHOW_GNOME_TERMINAL = 'show-gnome-terminal';
const KEY_SHOW_HIBERNATE = 'show-hibernate';
const KEY_SHOW_HYBRID_SLEEP = 'show-hybrid-sleep';
const KEY_SHOW_SUSPEND_THEN_HIBERNATE = 'show-suspend-then-hibernate';
const KEY_SHOW_SYSTEMMENU = 'show-systemmenu';

const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen';

const N_QUICK_SETTINGS_COLUMNS = 2;

const SYSTEMMENU_ICON = 1;

class PopupHibernationMenuSection extends PopupMenu.PopupMenuSection {
    constructor(settings) {
        super();
        this._settings = settings;
        this._settings.connectObject(
            'changed::%s'.format(KEY_SHOW_HIBERNATE), () => this._updateHibernation(),
            'changed::%s'.format(KEY_SHOW_HYBRID_SLEEP), () => this._updateHibernation(),
            'changed::%s'.format(KEY_SHOW_SUSPEND_THEN_HIBERNATE), () => this._updateHibernation(), this);
        this._loginManager = LoginManager.getLoginManager();
        this._loginScreenSettings = new Gio.Settings({ schema_id: LOGIN_SCREEN_SCHEMA });
        this._itemHibernate = new PopupMenu.PopupMenuItem(_('Hibernate'));
        this._itemHibernate.connect('activate', () => {
            this._onHibernation('Hibernate');
        });
        this.addMenuItem(this._itemHibernate);
        this._itemHybridSleep = new PopupMenu.PopupMenuItem(_('Hybrid Sleep'));
        this._itemHybridSleep.connect('activate', () => {
            this._onHibernation('HybridSleep');
        });
        this.addMenuItem(this._itemHybridSleep);
        this._itemSuspendThenHibernate = new PopupMenu.PopupMenuItem(_('Suspend Then Hibernate'));
        this._itemSuspendThenHibernate.connect('activate', () => {
            this._onHibernation('SuspendThenHibernate');
        });
        this.addMenuItem(this._itemSuspendThenHibernate);
        Main.panel.statusArea.quickSettings.menu.connectObject('open-state-changed', (menu, open) => {
            if (!open) return;
            this._updateHibernation();
        }, this);
    }

    _canHibernation(value, asyncCallback) {
        if (this._loginManager._proxy) {
            this._loginManager._proxy.call(value, null, Gio.DBusCallFlags.NONE, -1, null, (proxy, asyncResult) => {
                try {
                    let result = proxy.call_finish(asyncResult).deep_unpack();
                    asyncCallback(!['no', 'na'].includes(result[0]));
                } catch (e) {
                    asyncCallback(false);
                }
            });
        } else {
            GLib.idle_add(() => {
                asyncCallback(false);
                return false;
            });
        }
    }

    _onHibernation(value) {
        if (this._loginManager._proxy) {
            this._loginManager._proxy.call(value, GLib.Variant.new('(b)', [true]), Gio.DBusCallFlags.NONE, -1, null, null);
        } else {
            this._loginManager.emit('prepare-for-sleep', true);
            this._loginManager.emit('prepare-for-sleep', false);
        }
        Main.overview.hide();
        Main.panel.closeQuickSettings();
    }

    _updateHibernation() {
        let disableRestartButtons = this._loginScreenSettings.get_boolean(KEY_DISABLE_RESTART_BUTTONS);
        let disabled = Main.sessionMode.isLocked || (Main.sessionMode.isGreeter && disableRestartButtons);
        this._canHibernation('CanHibernate', (result) => {
            this._itemHibernate.visible = result && !disabled && this._settings.get_boolean(KEY_SHOW_HIBERNATE);
        });
        this._canHibernation('CanHybridSleep', (result) => {
            this._itemHybridSleep.visible = result && !disabled && this._settings.get_boolean(KEY_SHOW_HYBRID_SLEEP);
        });
        this._canHibernation('CanSuspendThenHibernate', (result) => {
            this._itemSuspendThenHibernate.visible = result && !disabled && this._settings.get_boolean(KEY_SHOW_SUSPEND_THEN_HIBERNATE);
        });
        this.visible = this._itemHibernate.visible || this._itemHybridSleep.visible || this._itemSuspendThenHibernate.visible;
    }

    destroy() {
        Main.panel.statusArea.quickSettings.menu.disconnectObject(this);
        this._settings.disconnectObject(this);
        super.destroy();
    }
}

const QuickPreferencesSettingsItem = GObject.registerClass(
class QuickPreferencesSettingsItem extends QuickSettings.QuickSettingsItem {
    _init(uuid) {
        super._init({
            style_class: 'icon-button',
            can_focus: true,
            icon_name: 'preferences-other-symbolic',
            accessible_name: _('Settings Panel Manager'),
        });
        this.connect('clicked', () => {
            Main.overview.hide();
            Main.panel.closeQuickSettings();
            Util.spawn([ 'gnome-extensions', 'prefs', uuid ]);
        });
    }
});

const QuickTerminalSettingsItem = GObject.registerClass(
class QuickTerminalSettingsItem extends QuickSettings.QuickSettingsItem {
    _init(settings) {
        super._init({
            style_class: 'icon-button',
            can_focus: true,
            child: new St.Icon(),
        });
        this._settings = settings;
        this._settings.connectObject('changed::%s'.format(KEY_SHOW_GNOME_TERMINAL), () => this._onChangedShowTerminal(), this);
        this._appSystem = Shell.AppSystem.get_default();
        this._appSystem.connectObject('installed-changed', () => this._onInstalledChanged(), this);
        this._onInstalledChanged();
        this.connect('clicked', () => {
            Main.overview.hide();
            Main.panel.closeQuickSettings();
            this._terminalApp.activate();
        });
        this.connect('destroy', () => {
            this._appSystem.disconnectObject(this);
            this._settings.disconnectObject(this);
        });
    }

    _onChangedShowTerminal() {
        let showTerminal = this._settings.get_boolean(KEY_SHOW_GNOME_TERMINAL);
        this.visible = this._terminalApp !== null && showTerminal;
    }

    _onInstalledChanged() {
        this._terminalApp = this._appSystem.lookup_app(GNOME_TERMINAL);
        this.child.gicon = this._terminalApp?.get_icon() ?? null;
        this.accessible_name = this._terminalApp?.get_name() ?? null;
        this._onChangedShowTerminal();
    }
});

const SystemBasicSettingsIndicator = GObject.registerClass(
class SystemBasicSettingsIndicator extends QuickSettings.SystemIndicator {
    _init(settings) {
        super._init();
        this._basicSettings = new QuickSettings.QuickMenuToggle({
            label: _('Basic Settings'),
            iconName: 'preferences-system-symbolic',
        });
        this._basicSettings.menu.setHeader('preferences-system-symbolic', _('Basic Settings'));
        this._basicSettings.connect('clicked', () => this._basicSettings.menu.open());
        this.quickSettingsItems.push(this._basicSettings);
        this._settings = settings;
        this._settings.connectObject('changed::%s'.format(KEY_SHOW_BASIC_SETTINGS), () => this._onChangedShowBasicSettings(), this);
        this._appSystem = Shell.AppSystem.get_default();
        this._appSystem.connectObject('installed-changed', () => this._reloadBasicSettings(), this);
        this._reloadBasicSettings();
        this.connect('destroy', () => {
            this._appSystem.disconnectObject(this);
            this._settings.disconnectObject(this);
        });
    }

    _onChangedShowBasicSettings() {
        let showBasicSettings = this._settings.get_boolean(KEY_SHOW_BASIC_SETTINGS);
        this._basicSettings.visible = this._basicSettings.menu.numMenuItems > 0 && showBasicSettings;
    }

    _reloadBasicSettings() {
        this._basicSettings.menu.removeAll();
        let appList = [];
        BASIC_SETTINGS.forEach(desktopFile => {
            let app = this._appSystem.lookup_app(desktopFile);
            if (app !== null) appList.push(app);
        }, this);
        appList.sort((a, b) => {
            return a.compare_by_name(b);
        });
        appList.forEach(app => {
            let item = new PopupMenu.PopupMenuItem(app.get_name());
            item.connect('activate', () => {
                app.activate();
                Main.overview.hide();
                Main.panel.closeQuickSettings();
            });
            this._basicSettings.menu.addMenuItem(item);
        });
        this._onChangedShowBasicSettings();
    }
});

var SystemMenuButton = class {
    constructor(uuid, settings) {
        this._currentChild = Main.panel.statusArea.quickSettings._indicators;
        this._icon = new St.Icon({
            style_class: 'system-status-icon',
            icon_name: 'system-shutdown-symbolic',
        });
        this._settings = settings;
        this._settings.connectObject('changed::%s'.format(KEY_SHOW_SYSTEMMENU), () => this._onChangedShowSystemMenu(), this);
        this._onChangedShowSystemMenu();
        this._preferencesItem = new QuickPreferencesSettingsItem(uuid);
        Main.panel.statusArea.quickSettings._system._systemItem.child.insert_child_below(this._preferencesItem, Main.panel.statusArea.quickSettings._system._systemItem._desktopSpacer);
        this._terminalItem = new QuickTerminalSettingsItem(settings);
        Main.panel.statusArea.quickSettings._system._systemItem.child.insert_child_above(this._terminalItem, Main.panel.statusArea.quickSettings._system._systemItem._desktopSpacer);
        this._hibernationSection = new PopupHibernationMenuSection(settings);
        Main.panel.statusArea.quickSettings._system._systemItem.menu.addMenuItem(this._hibernationSection, 1);
        this._basicSettings = new SystemBasicSettingsIndicator(settings);
        Main.panel.statusArea.quickSettings._indicators.add_child(this._basicSettings);
        Main.panel.statusArea.quickSettings._addItems(this._basicSettings.quickSettingsItems, N_QUICK_SETTINGS_COLUMNS);
    }

    _onChangedShowSystemMenu() {
        let showSystemMenu = this._settings.get_enum(KEY_SHOW_SYSTEMMENU);
        if (showSystemMenu === SYSTEMMENU_ICON) {
            this._setChild(this._icon);
        } else {
            this._setChild(Main.panel.statusArea.quickSettings._indicators);
        }
    }

    _setChild(child) {
        if (!child || child === this._currentChild) return;
        let current = Main.panel.statusArea.quickSettings.get_children()[0];
        Main.panel.statusArea.quickSettings.remove_child(current);
        Main.panel.statusArea.quickSettings.add_child(child);
        this._currentChild = child;
    }

    destroy() {
        this._basicSettings.quickSettingsItems.forEach(item => item.destroy());
        this._basicSettings.destroy();
        this._hibernationSection.destroy();
        this._terminalItem.destroy();
        this._preferencesItem.destroy();
        this._settings.disconnectObject(this);
        this._setChild(Main.panel.statusArea.quickSettings._indicators);
        this._icon.destroy();
    }
};
