/* 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 init */

const { Clutter, GObject, St } = imports.gi;

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

const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;

const _ = ExtensionUtils.gettext;
const Me = ExtensionUtils.getCurrentExtension();

const { SensorManager } = Me.imports.sensors;

const KEY_REFRESH_INTERVAL = 'refresh-interval';
const KEY_SHOW_FAHRENHEIT = 'show-fahrenheit';
const KEY_SHOW_TEMPERATURE = 'show-temperature';

const TEMPERATURE_AVERAGE = 'average';
const TEMPERATURE_HIGHEST = 'highest';

function getTemperatureAsString(temperature, showFahrenheit) {
    if (!temperature || temperature === 0) return 'N/A';
    if (showFahrenheit) {
        return Math.round(temperature * 1.8 + 32).toFixed(0)+' \u00b0F';
    } else {
        return Math.round(temperature).toFixed(1)+' \u00b0C';
    }
}

const PopupTemperatureMenuItem = GObject.registerClass(
class PopupTemperatureMenuItem extends PopupMenu.PopupBaseMenuItem {
    _init(text, icon, params) {
        super._init(params);
        if (icon) {
            this.icon = new St.Icon({
                style_class: 'popup-menu-icon',
                icon_name: icon,
            });
            this.add_child(this.icon);
        }
        this.label = new St.Label({
            text: text,
            x_expand: true,
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this.add_child(this.label);
        this.label_actor = this.label;
    }
});

const PopupTemperatureInfoMenuItem = GObject.registerClass(
class PopupTemperatureInfoMenuItem extends PopupTemperatureMenuItem {
    _init(settings, text, icon, params) {
        super._init(text, icon, params);
        this._statusLabel = new St.Label({
            style_class: 'popup-status-menu-item',
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this.add_child(this._statusLabel);
        this._settings = settings;
        this._settings.connectObject(
            'changed::%s'.format(KEY_SHOW_FAHRENHEIT), () => this._onChangedShowFahrenheit(),
            'changed::%s'.format(KEY_SHOW_TEMPERATURE), () => this._onChangedShowTemperature(), this);
    }

    _onChangedShowFahrenheit() {
        if (!this._statusLabel) return;
        let showFahrenheit = this._settings.get_boolean(KEY_SHOW_FAHRENHEIT)
        this._statusLabel.set_text(getTemperatureAsString(this._status, showFahrenheit));
    }

    _onChangedShowTemperature() {
        if (!this._id) return;
        let showTemperature = this._settings.get_strv(KEY_SHOW_TEMPERATURE);
        this._selected = showTemperature.indexOf(this._id) >= 0;
        this.setOrnament(this._selected ? PopupMenu.Ornament.CHECK : PopupMenu.Ornament.NONE);
        this.reactive = this._selected ? false : true;
    }

    activate(event) {
        if (this._id) this._settings.set_strv(KEY_SHOW_TEMPERATURE, [ this._id ]);
        super.activate(event);
    }

    destroy() {
        this._settings.disconnectObject(this);
        super.destroy();
    }

    setId(id) {
        this._id = id;
        this._onChangedShowTemperature();
    }

    setStatus(status) {
        this._status = status;
        this._onChangedShowFahrenheit();
    }
});

const PopupTemperatureSubMenuMenuItem = GObject.registerClass(
class PopupTemperatureSubMenuMenuItem extends PopupMenu.PopupSubMenuMenuItem {
    _init(settings, text, icon) {
        super._init(text, true);
        this.icon.icon_name = icon;
        this._statusLabel = new St.Label({
            style_class: 'popup-status-menu-item',
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this.actor.insert_child_at_index(this._statusLabel, this.actor.get_n_children() - 1);
        this._settings = settings;
        this._settings.connectObject('changed::%s'.format(KEY_SHOW_FAHRENHEIT), () => this._onChangedShowFahrenheit(), this);
    }

    _onChangedShowFahrenheit() {
        let showFahrenheit = this._settings.get_boolean(KEY_SHOW_FAHRENHEIT);
        this._statusLabel.set_text(getTemperatureAsString(this._status, showFahrenheit));
    }

    addMenuItem(text, status) {
        let item = new PopupTemperatureInfoMenuItem(this._settings, text, null, { reactive: false });
        item.setStatus(status);
        this.menu.addMenuItem(item);
    }

    destroy() {
        this._settings.disconnectObject(this);
        super.destroy();
    }

    setStatus(status) {
        this._status = status;
        this._onChangedShowFahrenheit();
    }
});

const PopupTemperatureSensorSubMenuMenuItem = GObject.registerClass(
class PopupTemperatureSensorSubMenuMenuItem extends PopupTemperatureSubMenuMenuItem {
    _init(settings, sensor, text, icon) {
        super._init(settings, text, icon);
        this._sensor = sensor;
        this._sensor.connectObject('updated::sensor', () => this._onUpdatedSensor(), this);
        this._onUpdatedSensor();
    }

    _onUpdatedSensor() {
        this.menu.removeAll();
        this._sensor.children.forEach(child => {
            this.addMenuItem(child.label, child.current);
        });
        this.setStatus(this._sensor.highest);
        this.actor.visible = this.menu.numMenuItems > 0;
    }

    destroy() {
        this._sensor.disconnectObject(this);
        super.destroy();
    }
});

class PopupTemperatureSensorMenuSection extends PopupMenu.PopupMenuSection {
    constructor(settings, sensor) {
        super();
        this._settings = settings;
        this._sensor = sensor;
        this._sensor.connectObject('updated::sensor', () => this._onUpdatedSensor(), this);
        this._onUpdatedSensor();
    }

    _onUpdatedSensor() {
        this.removeAll();
        this._sensor.children.forEach(child => {
            let item;
            if (child.maximum || child.critical) {
                item = new PopupTemperatureSubMenuMenuItem(this._settings, child.label, child.warning ? 'dialog-warning' : 'utilities-system-monitor');
                item.setStatus(child.current);
                item.addMenuItem(_('Current temperature'), child.current);
                if (child.maximum) item.addMenuItem(_('Maximum temperature'), child.maximum);
                if (child.critical) item.addMenuItem(_('Critical temperature'), child.critical);
            } else {
                item = new PopupTemperatureInfoMenuItem(this._settings, child.label, 'utilities-system-monitor', { reactive: false });
                item.setStatus(child.current);
            }
            this.addMenuItem(item);
        });
        this.actor.visible = this.numMenuItems > 0;
    }

    destroy() {
        this._sensor.disconnectObject(this);
        super.destroy();
    }
}

const TemperatureMenuButton = GObject.registerClass(
class TemperatureMenuButton extends PanelMenu.Button {
    _init(uuid, settings) {
        super._init(0.5, _('Temperature'), false);
        this.menu.actor.add_style_class_name('temperature-menu');
        this._hbox = new St.BoxLayout({ style_class: 'panel-status-indicators-box' });
        this._icon = new St.Icon({
            style_class: 'system-status-icon',
            icon_name: 'view-refresh-symbolic',
        });
        this._hbox.add_child(this._icon);
        this._label = new St.Label({
            text: '...',
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this._hbox.add_child(this._label);
        this.add_child(this._hbox);
        this._sensorManager = new SensorManager();
        this._itemNotAvailable = new PopupTemperatureMenuItem(_('No supported temperature sensors available'), 'dialog-warning', {
            can_focus: false,
            reactive: false,
        });
        this.menu.addMenuItem(this._itemNotAvailable);
        this._itemSensorHWM = new PopupTemperatureSensorMenuSection(settings, this._sensorManager.sensor['hwm']);
        this.menu.addMenuItem(this._itemSensorHWM);
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this._itemSensorVGA = new PopupTemperatureSensorSubMenuMenuItem(settings, this._sensorManager.sensor['vga'], _('Graphics Cards'), 'video-display');
        this.menu.addMenuItem(this._itemSensorVGA);
        this._itemSensorHDD = new PopupTemperatureSensorSubMenuMenuItem(settings, this._sensorManager.sensor['hdd'], _('Hard disk drives'), 'drive-harddisk');
        this.menu.addMenuItem(this._itemSensorHDD);
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this._itemAverage = new PopupTemperatureInfoMenuItem(settings, _('Average temperature'));
        this._itemAverage.setId(TEMPERATURE_AVERAGE);
        this.menu.addMenuItem(this._itemAverage);
        this._itemHighest = new PopupTemperatureInfoMenuItem(settings, _('Highest temperature'));
        this._itemHighest.setId(TEMPERATURE_HIGHEST);
        this.menu.addMenuItem(this._itemHighest);
        this._itemActions = new PopupMenu.PopupBaseMenuItem({
            can_focus: false,
            reactive: false,
        });
        this._itemActions.setOrnament(PopupMenu.Ornament.HIDDEN);
        this._actionSettings = this._createActionButton('preferences-other-symbolic', _('Settings'));
        this._actionSettings.connect('clicked', this._onClickedSettings.bind(this, uuid));
        this._itemActions.add_child(this._actionSettings);
        this._itemActions.add_child(new St.Bin({ x_expand: true }));
        this._actionRefresh = this._createActionButton('view-refresh-symbolic', _('Refresh'));
        this._actionRefresh.connect('clicked', this._onChangedRefreshInterval.bind(this));
        this._itemActions.add_child(this._actionRefresh);
        this.menu.addMenuItem(this._itemActions);
        this._settings = settings;
        this._settings.connectObject(
            'changed::%s'.format(KEY_REFRESH_INTERVAL), () => this._onChangedRefreshInterval(),
            'changed::%s'.format(KEY_SHOW_FAHRENHEIT), () => this._updateLabel(),
            'changed::%s'.format(KEY_SHOW_TEMPERATURE), () => this._updateLabel(), this);
        this._sensorManager.connectObject('updated::sensors', () => this._onUpdatedSensors(), this);
        this._onChangedRefreshInterval();
    }

    _createActionButton(iconName, accessibleName) {
        return new St.Button({
            style_class: 'icon-button',
            icon_name: iconName,
            accessible_name: accessibleName,
            can_focus: true,
            reactive: true,
            track_hover: true,
        });
    }

    _onChangedRefreshInterval() {
        let refreshInterval = this._settings.get_string(KEY_REFRESH_INTERVAL);
        this._sensorManager.setRefreshInterval(parseInt(refreshInterval));
    }

    _onClickedSettings(uuid) {
        Util.spawn([ 'gnome-extensions', 'prefs', uuid ]);
        Main.overview.hide();
        this.menu.itemActivated();
    }

    _onDestroy() {
        this._sensorManager.disconnectObject(this);
        this._sensorManager.destroy();
        this._settings.disconnectObject(this);
        super._onDestroy();
    }

    _onOpenStateChanged(menu, open) {
        if (open) {
            let available = this._sensorManager.available;
            this._itemNotAvailable.actor.visible = !available;
            this._itemAverage.actor.visible = available;
            this._itemHighest.actor.visible = available;
            this._itemActions.actor.visible = available;
        }
        super._onOpenStateChanged(menu, open);
    }

    _onUpdatedSensors() {
        this._itemAverage.setStatus(this._sensorManager.average);
        this._itemHighest.setStatus(this._sensorManager.highest);
        this._updateIcon();
        this._updateLabel();
    }

    _updateIcon() {
        this._icon.icon_name = this._sensorManager.warning ? 'dialog-warning-symbolic' : 'temperature-symbolic';
    }

    _updateLabel() {
        let showTemperature = this._settings.get_strv(KEY_SHOW_TEMPERATURE)[0];
        if (showTemperature === TEMPERATURE_AVERAGE || showTemperature === TEMPERATURE_HIGHEST) {
            let temperature = this._sensorManager[showTemperature];
            let showFahrenheit = this._settings.get_boolean(KEY_SHOW_FAHRENHEIT);
            this._label.set_text(getTemperatureAsString(temperature, showFahrenheit));
        } else {
            this._settings.set_strv(KEY_SHOW_TEMPERATURE, [ TEMPERATURE_AVERAGE ]);
        }
    }
});

class Extension {
    constructor(uuid) {
        ExtensionUtils.initTranslations();
        this._uuid = uuid;
    }

    _onEnable(settings) {
        this._temperatureMenu = new TemperatureMenuButton(this._uuid, settings);
        let pos = Main.sessionMode.panel.left.indexOf('dateMenu');
        Main.panel.addToStatusArea('temperatureMenu', this._temperatureMenu, pos, 'center');
    }

    enable() {
        if (Main.layoutManager._startingUp) {
            Main.layoutManager.connectObject('startup-complete', () => {
                this._onEnable(ExtensionUtils.getSettings());
            }, this);
        } else {
            this._onEnable(ExtensionUtils.getSettings());
        }
    }

    disable() {
        Main.layoutManager.disconnectObject(this);
        this._temperatureMenu.destroy();
        this._temperatureMenu = null;
    }
}

function init(meta) {
    return new Extension(meta.uuid);
}
