namespace AstalBattery {
/** Get the DisplayDevice. */
public Device get_default() {
    return Device.get_default();
}
}

/**
 * Client for a UPower [[https://upower.freedesktop.org/docs/Device.html|device]].
 */
public class AstalBattery.Device : Object {
    private static Device display_device;

    /** Get the DisplayDevice. */
    public static Device? get_default() {
        if (display_device != null) {
            return display_device;
        }

        try {
            display_device = new Device((ObjectPath)"/org/freedesktop/UPower/devices/DisplayDevice");
            return display_device;
        } catch (Error error) {
            critical(error.message);
        }

        return null;
    }

    private IUPowerDevice proxy;

    public Device(ObjectPath path) throws Error {
        proxy = Bus.get_proxy_sync(BusType.SYSTEM, "org.freedesktop.UPower", path);
        proxy.g_properties_changed.connect(sync);
        sync();
    }

    /**
     * If it is [enum@AstalBattery.Type.BATTERY], you will need to verify that the
     * property power-supply has the value `true` before considering it as a laptop battery.
     * Otherwise it will likely be the battery for a device of an unknown type.
     */
    public Type device_type { get; private set; }

    /**
     * Native path of the power source. This is the sysfs path,
     * for example /sys/devices/LNXSYSTM:00/device:00/PNP0C0A:00/power_supply/BAT0.
     * It is blank if the device is being driven by a user space driver.
     */
    public string native_path { owned get; private set; }

    /** Name of the vendor of the battery. */
    public string vendor { owned get; private set; }

    /** Name of the model of this battery. */
    public string model { owned get; private set; }

    /** Unique serial number of the battery. */
    public string serial { owned get; private set; }

    /**
     * The point in time (seconds since the Epoch)
     * that data was read from the power source.
     */
    public uint64 update_time { get; private set; }

    /**
     * If the power device is used to supply the system.
     * This would be set `true` for laptop batteries and UPS devices,
     * but set to `false` for wireless mice or PDAs.
     */
    public bool power_supply { get; private set; }

    /** If the power device has history. */
    // TODO: public bool has_history { get; private set; }

    /** If the power device has statistics. */
    // TODO: public bool has_statistics { get; private set; }

    /**
     * Whether power is currently being provided through line power.
     *
     * This property is only valid if [property@AstalBattery.Device:device_type] is [enum@AstalBattery.Type.LINE_POWER].
     */
    public bool online { get; private set; }

    /**
     * Amount of energy (measured in Wh) currently available in the power source.
     *
     * This property is only valid if [property@AstalBattery.Device:device_type] is [enum@AstalBattery.Type.BATTERY].
     */
    public double energy { get; private set; }

    /**
     * Amount of energy (measured in Wh) in the power source when it's considered to be empty.
     *
     * This property is only valid if [property@AstalBattery.Device:device_type] is [enum@AstalBattery.Type.BATTERY].
     */
    public double energy_empty { get; private set; }

    /**
     * Amount of energy (measured in Wh) in the power source when it's considered full.
     *
     * This property is only valid if [property@AstalBattery.Device:device_type] is [enum@AstalBattery.Type.BATTERY].
     */
    public double energy_full { get; private set; }

    /**
     * Amount of energy (measured in Wh) the power source is designed to hold when it's considered full.
     *
     * This property is only valid if [property@AstalBattery.Device:device_type] is [enum@AstalBattery.Type.BATTERY].
     */
    public double energy_full_design { get; private set; }

    /**
     * Amount of energy being drained from the source, measured in W.
     * If positive, the source is being discharged, if negative it's being charged.
     *
     * This property is only valid if [property@AstalBattery.Device:device_type] is [enum@AstalBattery.Type.BATTERY].
     */
    public double energy_rate { get; private set; }

    /** Voltage in the Cell or being recorded by the meter. */
    public double voltage { get; private set; }

    /**
     * The number of charge cycles as defined by the TCO certification,
     * or -1 if that value is unknown or not applicable.
     */
    public int charge_cycles { get; private set; }

    /** Luminosity being recorded by the meter. */
    public double luminosity { get; private set; }

    /**
     * Number of seconds until the power source is considered empty. Is set to 0 if unknown.
     *
     * This property is only valid if [property@AstalBattery.Device:device_type] is [enum@AstalBattery.Type.BATTERY].
     */
    public int64 time_to_empty { get; private set; }

    /**
     * Number of seconds until the power source is considered full. Is set to 0 if unknown.
     *
     * This property is only valid if [property@AstalBattery.Device:device_type] is [enum@AstalBattery.Type.BATTERY].
     */
    public int64 time_to_full { get; private set; }

    /**
     * The amount of energy left in the power source expressed as a percentage between 0 and 1.
     *
     * This property is only valid if [property@AstalBattery.Device:device_type] is [enum@AstalBattery.Type.BATTERY].
     * The percentage will be an approximation if [property@AstalBattery.Device:battery_level]
     * is set to something other than None.
     */
    public double percentage { get; private set; }

    /**
     * The temperature of the device in degrees Celsius.
     *
     * This property is only valid if [property@AstalBattery.Device:device_type] is [enum@AstalBattery.Type.BATTERY].
     */
    public double temperature { get; private set; }

    /**
     * If the power source is present in the bay.
     *
     * This property is only valid if [property@AstalBattery.Device:device_type] is [enum@AstalBattery.Type.BATTERY].
     */
    public bool is_present { get; private set; }

    /**
     * The battery power state.
     *
     * This property is only valid if [property@AstalBattery.Device:device_type] is [enum@AstalBattery.Type.BATTERY].
     */
    public State state { get; private set; }

    /**
     * If the power source is rechargeable.
     *
     * This property is only valid if [property@AstalBattery.Device:device_type] is [enum@AstalBattery.Type.BATTERY].
     */
    public bool is_rechargable { get; private set; }

    /**
     * The capacity of the power source expressed as a percentage between 0 and 1.
     *
     * This property is only valid if [property@AstalBattery.Device:device_type] is [enum@AstalBattery.Type.BATTERY].
     */
    public double capacity { get; private set; }

    /**
     * Technology used in the battery:
     *
     * This property is only valid if [property@AstalBattery.Device:device_type] is [enum@AstalBattery.Type.BATTERY].
     */
    public Technology technology { get; private set; }

    /** Warning level of the battery. */
    public WarningLevel warning_level { get; private set; }

    /**
     * The level of the battery for devices which do not report a percentage
     * but rather a coarse battery level. If the value is None.
     * then the device does not support coarse battery reporting,
     * and the [property@AstalBattery.Device:percentage] should be used instead.
     */
    public BatteryLevel battery_level { get; private set; }

    /**
     * An icon name representing this Device.
     *
     * NOTE: [property@AstalBattery.Device:battery_icon_name] might be a better fit
     * as it is calculated from percentage.
     */
    public string icon_name { owned get; private set; }

    /**
     * Indicates if [property@AstalBattery.Device:state] is charging or fully charged.
     */
    public bool charging { get; private set; }

    /**
     * Indicates if [property@AstalBattery.Device:device_type] is not line power or unknown.
     */
    public bool is_battery { get; private set; }

    /**
     * An icon name in the form of "battery-level-$percentage-$state-symbolic".
     */
    public string battery_icon_name { get; private set; }

    /**
     * A string representation of this device's [property@AstalBattery.Device:device_type].
     */
    public string device_type_name { get; private set; }

    /**
     * An icon name that can be used to represent this device's [property@AstalBattery.Device:device_type].
     */
    public string device_type_icon { get; private set; }

    // TODO: get_history
    // TODO: get_statistics

    private void sync() {
        device_type = (Type)proxy.Type;
        native_path = proxy.native_path;
        vendor = proxy.vendor;
        model = proxy.model;
        serial = proxy.serial;
        update_time = proxy.update_time;
        power_supply = proxy.power_supply;
        // TODO: has_history = proxy.has_history;
        // TODO: has_statistics = proxy.has_statistics;
        online = proxy.online;
        energy = proxy.energy;
        energy_empty = proxy.energy_empty;
        energy_full = proxy.energy_full;
        energy_full_design = proxy.energy_full_design;
        energy_rate = proxy.energy_rate;
        voltage = proxy.voltage;
        charge_cycles = proxy.charge_cycles;
        luminosity = proxy.luminosity;
        time_to_empty = proxy.time_to_empty;
        time_to_full = proxy.time_to_full;
        percentage = proxy.percentage / 100;
        temperature = proxy.temperature;
        is_present = proxy.is_present;
        state = (State)proxy.state;
        is_rechargable = proxy.is_rechargable;
        capacity = proxy.capacity / 100;
        technology = (Technology)proxy.technology;
        warning_level = (WarningLevel)proxy.warning_level;
        battery_level = (BatteryLevel)proxy.battery_level;
        icon_name = proxy.icon_name;

        charging = (state == State.FULLY_CHARGED) || (state == State.CHARGING);
        is_battery = (device_type != Type.UNKNOWN) && (device_type != Type.LINE_POWER);

        if (!is_battery) {
            battery_icon_name = "battery-missing-symbolic";
        } else if ((percentage >= 0.95) && charging) {
            battery_icon_name = "battery-level-100-charged-symbolic";
        } else {
            var state = charging ? "-charging" : "";
            var level = (int)Math.round(percentage * 10) * 10;
            battery_icon_name = @"battery-level-$level$state-symbolic";
        }

        device_type_name = device_type.get_name();
        device_type_icon = device_type.get_icon_name();
    }
}

[CCode(type_signature = "u")]
public enum AstalBattery.State {
    UNKNOWN,
    CHARGING,
    DISCHARGING,
    EMPTY,
    FULLY_CHARGED,
    PENDING_CHARGE,
    PENDING_DISCHARGE,
}

[CCode(type_signature = "u")]
public enum AstalBattery.Technology {
    UNKNOWN,
    LITHIUM_ION,
    LITHIUM_POLYMER,
    LITHIUM_IRON_PHOSPHATE,
    LEAD_ACID,
    NICKEL_CADMIUM,
    NICKEL_METAL_HYDRIDE,
}

[CCode(type_signature = "u")]
public enum AstalBattery.WarningLevel {
    UNKNOWN,
    NONE,
    DISCHARGING,
    LOW,
    CRITICIAL,
    ACTION,
}

[CCode(type_signature = "u")]
public enum AstalBattery.BatteryLevel {
    UNKNOWN,
    NONE,
    LOW,
    CRITICIAL,
    NORMAL,
    HIGH,
    FULL,
}

[CCode(type_signature = "u")]
public enum AstalBattery.Type {
    UNKNOWN,
    LINE_POWER,
    BATTERY,
    UPS,
    MONITOR,
    MOUSE,
    KEYBOARD,
    PDA,
    PHONE,
    MEDIA_PLAYER,
    TABLET,
    COMPUTER,
    GAMING_INPUT,
    PEN,
    TOUCHPAD,
    MODEM,
    NETWORK,
    HEADSET,
    SPEAKERS,
    HEADPHONES,
    VIDEO,
    OTHER_AUDIO,
    REMOVE_CONTROL,
    PRINTER,
    SCANNER,
    CAMERA,
    WEARABLE,
    TOY,
    BLUETOOTH_GENERIC;

    // TODO: add more icon names
    internal string? get_icon_name() {
        switch (this) {
            case UPS :
                    return "uninterruptible-power-supply";
            case MOUSE:
                return "input-mouse";
            case KEYBOARD:
                return "input-keyboard";
            case PDA:
            case PHONE:
                return "phone";
            case MEDIA_PLAYER:
                return "multimedia-player";
            case TABLET:
            case PEN:
                return "input-tablet";
            case GAMING_INPUT:
                return "input-gaming";
            default:
                return null;
        }
    }

    internal unowned string? get_name() {
        switch (this) {
            case LINE_POWER:
                return "Plugged In";
            case BATTERY:
                return "Battery";
            case UPS:
                return "UPS";
            case MONITOR:
                return "Display";
            case MOUSE:
                return "Mouse";
            case KEYBOARD:
                return "Keyboard";
            case PDA:
                return "PDA";
            case PHONE:
                return "Phone";
            case MEDIA_PLAYER:
                return "Media Player";
            case TABLET:
                return "Tablet";
            case COMPUTER:
                return "Computer";
            case GAMING_INPUT:
                return "Controller";
            case PEN:
                return "Pen";
            case TOUCHPAD:
                return "Touchpad";
            case MODEM:
                return "Modem";
            case NETWORK:
                return "Network";
            case HEADSET:
                return "Headset";
            case SPEAKERS:
                return "Speakers";
            case HEADPHONES:
                return "Headphones";
            case VIDEO:
                return "Video";
            case OTHER_AUDIO:
                return "Other Audio";
            case REMOVE_CONTROL:
                return "Remove Control";
            case PRINTER:
                return "Printer";
            case SCANNER:
                return "Scanner";
            case CAMERA:
                return "Camera";
            case WEARABLE:
                return "Wearable";
            case TOY:
                return "Toy";
            case BLUETOOTH_GENERIC:
                return "Bluetooth Generic";
            default:
                return "Unknown";
        }
    }
}
