/* extension.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 LegacyTrayExtension */

import Clutter from 'gi://Clutter';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Shell from 'gi://Shell';
import St from 'gi://St';

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

import * as CtrlAltTab from 'resource:///org/gnome/shell/ui/ctrlAltTab.js';
import * as Layout from 'resource:///org/gnome/shell/ui/layout.js';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as Overview from 'resource:///org/gnome/shell/ui/overview.js';

import * as OverviewControls from './overviewControls.js';

const STANDARD_TRAY_ICON_IMPLEMENTATIONS = {
    'bluetooth-applet': 'bluetooth',
    'gnome-volume-control-applet': 'volume', // renamed to gnome-sound-applet
                                             // when moved to control center
    'gnome-sound-applet': 'volume',
    'nm-applet': 'network',
    'gnome-power-manager': 'battery',
    'keyboard': 'keyboard',
    'a11y-keyboard': 'a11y',
    'kbd-scrolllock': 'keyboard',
    'kbd-numlock': 'keyboard',
    'kbd-capslock': 'keyboard',
    'ibus-ui-gtk': 'keyboard'
};

// Offset of the original position from the bottom-right corner
const CONCEALED_WIDTH = 3;
const TEMP_REVEAL_TIME = 2;

const LegacyTray = GObject.registerClass(
class LegacyTray extends St.Widget {
    _init() {
        super._init({
            name: 'LegacyTray',
            clip_to_allocation: true,
            layout_manager: new Clutter.BinLayout(),
        });
        this.connect('destroy', () => this._onDestroy());
        let constraint = new Layout.MonitorConstraint({
            primary: true,
            work_area: true,
        });
        this.add_constraint(constraint);
        this._slideLayout = new OverviewControls.SlideLayout();
        this._slideLayout.slideDirection = OverviewControls.SlideDirection.LEFT;
        this._slider = new St.Widget({
            x_expand: true,
            y_expand: true,
            x_align: Clutter.ActorAlign.START,
            y_align: Clutter.ActorAlign.END,
            layout_manager: this._slideLayout,
        });
        this.add_child(this._slider);
        this._box = new St.BoxLayout({ style_class: 'legacy-tray' });
        this._slider.add_child(this._box);
        this._concealHandle = new St.Button({
            style_class: 'legacy-tray-handle',
            /* translators: 'Hide' is a verb */
            accessible_name: _('Hide tray'),
            can_focus: true,
        });
        this._concealHandle.child = new St.Icon({ icon_name: 'go-previous-symbolic' });
        this._box.add_child(this._concealHandle);
        this._iconBox = new St.BoxLayout({ style_class: 'legacy-tray-icon-box' });
        this._box.add_child(this._iconBox);
        this._revealHandle = new St.Button({ style_class: 'legacy-tray-handle' });
        this._revealHandle.child = new St.Icon({ icon_name: 'go-next-symbolic' });
        this._box.add_child(this._revealHandle);
        this._revealHandle.bind_property('visible', this._concealHandle, 'visible', GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.INVERT_BOOLEAN);
        this._revealHandle.connect('notify::visible', this._sync.bind(this));
        this._revealHandle.connect('notify::hover', this._sync.bind(this));
        this._revealHandle.connect('clicked', () => {
            this._concealHandle.show();
        });
        this._concealHandle.connect('clicked', () => {
            this._revealHandle.show();
        });
        Main.layoutManager.addChrome(this, { affectsInputRegion: false });
        Main.layoutManager.trackChrome(this._slider, { affectsInputRegion: true });
        Main.ctrlAltTabManager.addGroup(this, _('Status Icons'), 'focus-legacy-systray-symbolic', {
            sortGroup: CtrlAltTab.SortGroup.BOTTOM,
        });
        this._trayManager = new Shell.TrayManager();
        this._trayManagerChangedId = [
            this._trayManager.connect('tray-icon-added', this._onTrayIconAdded.bind(this)),
            this._trayManager.connect('tray-icon-removed', this._onTrayIconRemoved.bind(this)),
        ];
        this._trayManager.manage_screen(Main.panel);
        this._overviewChangedId = [
            Main.overview.connect('showing', () => {
                this._slider.remove_all_transitions();
                this._slider.ease({
                    opacity: 0,
                    duration: Overview.ANIMATION_TIME,
                    mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                });
            }),
            Main.overview.connect('shown', this._sync.bind(this)),
            Main.overview.connect('hiding', () => {
                this._sync();
                this._slider.remove_all_transitions();
                this._slider.ease({
                    opacity: 255,
                    duration: Overview.ANIMATION_TIME,
                    mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                });
            }),
        ];
        this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', this._sync.bind(this));
        this._inFullscreenChangedId = global.display.connect('in-fullscreen-changed', this._sync.bind(this));
        this._updatedId = Main.sessionMode.connect('updated', this._sync.bind(this));
        this._sync();
    }

    _onDestroy() {
        if (this._concealHandleTimeoutId) GLib.source_remove(this._concealHandleTimeoutId);
        Main.sessionMode.disconnect(this._updatedId);
        global.display.disconnect(this._inFullscreenChangedId);
        Main.layoutManager.disconnect(this._monitorsChangedId);
        this._overviewChangedId.forEach(id => {
            Main.overview.disconnect(id);
        });
        this._trayManager.unmanage_screen();
        this._trayManagerChangedId.forEach(id => {
            this._trayManager.disconnect(id);
        });
        this._trayManager = null;
        Main.ctrlAltTabManager.removeGroup(this);
        Main.layoutManager.untrackChrome(this._slider);
        Main.layoutManager.removeChrome(this);
    }

    _onTrayIconAdded(tm, icon) {
        let wmClass = icon.wm_class ? icon.wm_class.toLowerCase() : '';
        if (STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass] !== undefined) return;
        icon.set({
            height: 24,
            width: 24,
            x_align: Clutter.ActorAlign.CENTER,
            y_align: Clutter.ActorAlign.CENTER,
        });
        let button = new St.Button({
            child: icon,
            style_class: 'legacy-tray-icon',
            button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO | St.ButtonMask.THREE,
            can_focus: true,
            x_expand: true,
            y_expand: true,
        });
        let app = Shell.WindowTracker.get_default().get_app_from_pid(icon.pid);
        if (!app) app = Shell.AppSystem.get_default().lookup_startup_wmclass(wmClass);
        if (!app) app = Shell.AppSystem.get_default().lookup_desktop_wmclass(wmClass);
        button.accessible_name = app ? app.get_name() : icon.title;
        button.connect('clicked', () => {
            icon.click(Clutter.get_current_event());
        });
        button.connect('key-press-event', () => {
            icon.click(Clutter.get_current_event());
            return Clutter.EVENT_PROPAGATE;
        });
        button.connect('key-focus-in', () => {
            this._concealHandle.show();
        });
        this._iconBox.add_child(button);
        if (!this._concealHandle.visible) {
            this._concealHandle.show();
            this._concealHandleTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, TEMP_REVEAL_TIME, () => {
                this._concealHandleTimeoutId = 0;
                this._concealHandle.hide();
                return GLib.SOURCE_REMOVE;
            });
        }
    }

    _onTrayIconRemoved(tm, icon) {
        if (!this.contains(icon)) return;
        icon.get_parent().destroy();
        this._sync();
    }

    _sync() {
        let allowed = Main.sessionMode.hasNotifications;
        let hasIcons = this._iconBox.get_n_children() > 0;
        let inOverview = Main.overview.visible && !Main.overview.animationInProgress;
        let inFullscreen = Main.layoutManager.primaryMonitor.inFullscreen;
        this.visible = allowed && hasIcons && !inOverview && !inFullscreen;
        if (!hasIcons) this._concealHandle.hide();
        let targetSlide;
        if (this._concealHandle.visible) {
            targetSlide = 1.0;
        } else if (!hasIcons) {
            targetSlide = 0.0;
        } else {
            let [, boxWidth] = this._box.get_preferred_width(-1);
            let [, handleWidth] = this._revealHandle.get_preferred_width(-1);
            if (this._revealHandle.hover) {
                targetSlide = handleWidth / boxWidth;
            } else {
                targetSlide = CONCEALED_WIDTH / boxWidth;
            }
        }
        this._slideLayout.slide_x = targetSlide;
    }
});

export default class LegacyTrayExtension extends Extension {
    _onEnable() {
        this._legacyTray = new LegacyTray();
    }

    enable() {
        if (Main.layoutManager._startingUp) {
            this._startupComplete = Main.layoutManager.connect('startup-complete', () => {
                this._onEnable();
                Main.layoutManager.disconnect(this._startupComplete);
            });
        } else {
            this._onEnable();
        }
    }

    disable() {
        this._legacyTray.destroy();
        this._legacyTray = null;
    }
}
