// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect
//
// SPDX-License-Identifier: GPL-2.0-or-later

import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';


/**
 * The warning level of a battery.
 *
 * @readonly
 * @enum {number}
 */
const DeviceLevel = {
    UNKNOWN: 0,
    NONE: 1,
    DISCHARGING: 2,
    LOW: 3,
    CRITICAL: 4,
    ACTION: 5,
    NORMAL: 6,
    HIGH: 7,
    FULL: 8,
    LAST: 9,
};

/**
 * The device state.
 *
 * @readonly
 * @enum {number}
 */
const DeviceState = {
    UNKNOWN: 0,
    CHARGING: 1,
    DISCHARGING: 2,
    EMPTY: 3,
    FULLY_CHARGED: 4,
    PENDING_CHARGE: 5,
    PENDING_DISCHARGE: 6,
    LAST: 7,
};


/**
 * A class representing the system battery.
 */
const Battery = GObject.registerClass({
    GTypeName: 'GSConnectSystemBattery',
    Signals: {
        'changed': {
            flags: GObject.SignalFlags.RUN_FIRST,
        },
    },
    Properties: {
        'charging': GObject.ParamSpec.boolean(
            'charging',
            'Charging',
            'The current charging state.',
            GObject.ParamFlags.READABLE,
            false
        ),
        'level': GObject.ParamSpec.int(
            'level',
            'Level',
            'The current power level.',
            GObject.ParamFlags.READABLE,
            -1, 100,
            -1
        ),
        'threshold': GObject.ParamSpec.uint(
            'threshold',
            'Threshold',
            'The current threshold state.',
            GObject.ParamFlags.READABLE,
            0, 1,
            0
        ),
    },
}, class Battery extends GObject.Object {

    _init() {
        super._init();

        this._cancellable = new Gio.Cancellable();
        this._proxy = null;
        this._propertiesChangedId = 0;

        this._loadUPower();
    }

    async _loadUPower() {
        try {
            this._proxy = new Gio.DBusProxy({
                g_bus_type: Gio.BusType.SYSTEM,
                g_name: 'org.freedesktop.UPower',
                g_object_path: '/org/freedesktop/UPower/devices/DisplayDevice',
                g_interface_name: 'org.freedesktop.UPower.Device',
                g_flags: Gio.DBusProxyFlags.DO_NOT_AUTO_START,
            });

            await this._proxy.init_async(GLib.PRIORITY_DEFAULT,
                this._cancellable);

            this._propertiesChangedId = this._proxy.connect(
                'g-properties-changed', this._onPropertiesChanged.bind(this));

            this._initProperties(this._proxy);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
                const service = Gio.Application.get_default();

                if (service !== null)
                    service.notify_error(e);
                else
                    logError(e);
            }

            this._proxy = null;
        }
    }

    _initProperties(proxy) {
        if (proxy.g_name_owner === null)
            return;

        const percentage = proxy.get_cached_property('Percentage').unpack();
        const state = proxy.get_cached_property('State').unpack();
        const level = proxy.get_cached_property('WarningLevel').unpack();

        this._level = Math.floor(percentage);
        this._charging = (state !== DeviceState.DISCHARGING);
        this._threshold = (!this.charging && level >= DeviceLevel.LOW);

        this.emit('changed');
    }

    _onPropertiesChanged(proxy, changed, invalidated) {
        let emitChanged = false;
        const properties = changed.deepUnpack();

        if (properties.hasOwnProperty('Percentage')) {
            emitChanged = true;

            const value = proxy.get_cached_property('Percentage').unpack();
            this._level = Math.floor(value);
            this.notify('level');
        }

        if (properties.hasOwnProperty('State')) {
            emitChanged = true;

            const value = proxy.get_cached_property('State').unpack();
            this._charging = (value !== DeviceState.DISCHARGING);
            this.notify('charging');
        }

        if (properties.hasOwnProperty('WarningLevel')) {
            emitChanged = true;

            const value = proxy.get_cached_property('WarningLevel').unpack();
            this._threshold = (!this.charging && value >= DeviceLevel.LOW);
            this.notify('threshold');
        }

        if (emitChanged)
            this.emit('changed');
    }

    get charging() {
        if (this._charging === undefined)
            this._charging = false;

        return this._charging;
    }

    get is_present() {
        return (this._proxy && this._proxy.g_name_owner);
    }

    get level() {
        if (this._level === undefined)
            this._level = -1;

        return this._level;
    }

    get threshold() {
        if (this._threshold === undefined)
            this._threshold = 0;

        return this._threshold;
    }

    destroy() {
        if (this._cancellable.is_cancelled())
            return;

        this._cancellable.cancel();

        if (this._proxy && this._propertiesChangedId > 0) {
            this._proxy.disconnect(this._propertiesChangedId);
            this._propertiesChangedId = 0;
        }
    }
});


/**
 * The service class for this component
 */
export default Battery;

