//
//  Copyright (c) 2013, 2015, 2018, 2021
//  by Tomi Tapper ( tomi.o.tapper@student.jyu.fi )
//
//  Based on linux/btrymeter.cc:
//  Copyright (c) 1997, 2006 by Mike Romberg ( mike.romberg@noaa.gov )
//
//  This file may be distributed under terms of the GPL
//
#include "btrymeter.h"

#if defined(XOSVIEW_OPENBSD)
#include "kernel.h"
#endif

#include <unistd.h>
#include <fcntl.h>

#if defined(XOSVIEW_FREEBSD) || defined(XOSVIEW_DFBSD)
#include <dev/acpica/acpiio.h>
#endif

#if defined(XOSVIEW_FREEBSD)
#include <machine/apm_bios.h>
#endif


#if defined(XOSVIEW_NETBSD)
#include <sys/envsys.h>
#include <prop/proplib.h>
#include <paths.h>
#endif

#if defined(XOSVIEW_FREEBSD) || defined(XOSVIEW_DFBSD)
static const char * const ACPIDEV = "/dev/acpi";
static const char * const APMDEV = "/dev/apm";
#endif



BtryMeter::BtryMeter(void)
    : FieldMeter(2, "BTRY", "CHRG/USED"),
      _leftColor(0), _usedColor(0), _chargeColor(0), _fullColor(0),
      _lowColor(0), _critColor(0), _noneColor(0),
      _oldState(256) {
}


void BtryMeter::checkResources(const ResDB &rdb) {
    FieldMeter::checkResources(rdb);

    _leftColor = rdb.getColor("batteryLeftColor");
    _usedColor = rdb.getColor("batteryUsedColor");
    _chargeColor = rdb.getColor("batteryChargeColor");
    _fullColor = rdb.getColor("batteryFullColor");
    _lowColor = rdb.getColor("batteryLowColor");
    _critColor = rdb.getColor("batteryCritColor");
    _noneColor = rdb.getColor("batteryNoneColor");

    setfieldcolor(0, _leftColor);
    setfieldcolor(1, _usedColor);
}


void BtryMeter::checkevent(void) {
    getstats();
}


void BtryMeter::getstats(void) {
    int remaining;
    unsigned int state;
    getBatteryInfo(remaining, state);

    if (state != _oldState) {
        if (state == XOSVIEW_BATT_NONE) { // no battery present
            setfieldcolor(0, _noneColor);
            legend("NONE/NONE");
        }
        else if (state & XOSVIEW_BATT_FULL) { // full battery
            setfieldcolor(0, _fullColor);
            legend("CHRG/FULL");
        }
        else { // present, not full
            if (state & XOSVIEW_BATT_CRITICAL) // critical charge
                setfieldcolor(0, _critColor);
            else if (state & XOSVIEW_BATT_LOW) // low charge
                setfieldcolor(0, _lowColor);
            else { // above low, below full
                if (state & XOSVIEW_BATT_CHARGING) // is charging
                    setfieldcolor(0, _chargeColor);
                else
                    setfieldcolor(0, _leftColor);
            }
            // legend tells if charging or discharging
            if (state & XOSVIEW_BATT_CHARGING)
                legend("CHRG/AC");
            else
                legend("CHRG/USED");
        }
        _oldState = state;
    }

    _total = 100.0;
    _fields[0] = remaining;
    _fields[1] = _total - remaining;
    setUsed(_fields[0], _total);
}


#if defined(XOSVIEW_NETBSD)
bool BtryMeter::hasBattery(void) {
    int fd = -1;
    if ((fd = open(_PATH_SYSMON, O_RDONLY)) == -1)
        return false;

    prop_dictionary_t pdict;
    if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &pdict))
        logFatal << "Could not get sensor dictionary" << std::endl;
    if (close(fd) == -1)
        logFatal << "Could not close " << _PATH_SYSMON << std::endl;

    if (prop_dictionary_count(pdict) == 0)
        return false;

    // just check for 1st battery.
    const prop_object_t pobj = prop_dictionary_get(pdict, "acpibat0");
    if (prop_object_type(pobj) != PROP_TYPE_ARRAY)
        return false;

    return true;
}
#endif

#if defined(XOSVIEW_OPENBSD)
bool BtryMeter::hasBattery(void) {
    // check if we can get full capacity of the 1st battery
    float val = -1.0;
    std::string emptyStr;
    BSDGetSensor("acpibat0", "amphour0", val, emptyStr);
    if (val < 0)
        return false;
    return true;
}
#endif


#if defined(XOSVIEW_FREEBSD)
bool BtryMeter::hasBattery(void) {
    int fd;
    if ((fd = open(ACPIDEV, O_RDONLY)) == -1) {
        // No ACPI -> try APM
        if ((fd = open(APMDEV, O_RDONLY)) == -1)
            return false;

        struct apm_info aip;
        if (ioctl(fd, APMIO_GETINFO, &aip) == -1)
            return false;
        if (close(fd) == -1)
            logFatal << "Could not close " << APMDEV << std::endl;
        if (aip.ai_batt_stat == 0xff || aip.ai_batt_life == 0xff)
            return false;
        return true;
    }

    union acpi_battery_ioctl_arg battio;
    battio.unit = ACPI_BATTERY_ALL_UNITS;
    if (ioctl(fd, ACPIIO_BATT_GET_BATTINFO, &battio) == -1)
        return false;
    if (close(fd) == -1)
        logFatal << "Could not close " << ACPIDEV << std::endl;

    return battio.battinfo.state != ACPI_BATT_STAT_NOT_PRESENT;
}
#endif


#if defined(XOSVIEW_DFBSD)
bool BtryMeter::hasBattery(void) {
    int fd;
    if ((fd = open(ACPIDEV, O_RDONLY)) == -1) {
        return false;
    }

    union acpi_battery_ioctl_arg battio;
    battio.unit = ACPI_BATTERY_ALL_UNITS;
    if (ioctl(fd, ACPIIO_BATT_GET_BATTINFO, &battio) == -1)
        return false;
    if (close(fd) == -1)
        logFatal << "Could not close " << ACPIDEV << std::endl;

    return battio.battinfo.state != ACPI_BATT_STAT_NOT_PRESENT;
}
#endif


#if defined(XOSVIEW_NETBSD)
void BtryMeter::getBatteryInfo(int &remaining, unsigned int &state) {
    state = XOSVIEW_BATT_NONE;
    // Again adapted from envstat.
    // All kinds of sensors are read with libprop. We have to go through them
    // to find the batteries. We need capacity, charge, presence, charging
    // status and discharge rate for each battery for the calculations.
    // For simplicity, assume all batteries have the same
    // charge/discharge status.
    int batteries = 0;

    int fd = -1;
    if ((fd = open(_PATH_SYSMON, O_RDONLY)) == -1) {
        logProblem << "Could not open " << _PATH_SYSMON << std::endl;
        return;  // this seems to happen occasionally, so only warn
    }

    prop_dictionary_t pdict;
    if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &pdict))
        logFatal << "Could not get sensor dictionary" << std::endl;
    if (close(fd) == -1)
        logFatal << "Could not close " << _PATH_SYSMON << std::endl;
    if (prop_dictionary_count(pdict) == 0) {
        logProblem << "No sensors found" << std::endl;
        return;
    }

    const prop_object_iterator_t piter = prop_dictionary_iterator(pdict);
    if (!piter)
        logFatal << "Could not get sensor iterator" << std::endl;

    // This snippet is repeated often below.  Macro it here.
    const auto setIfFound = [](prop_dictionary_t d, const char *k, int &v) {
        auto pobj = prop_dictionary_get(d, k);
        if (pobj) {
            v = prop_number_integer_value(static_cast<prop_number_t>(pobj));
            return true;
        }
        return false;
    };

    // Sum of all batteries.
    int total_capacity = 0, total_charge = 0, total_low = 0, total_crit = 0;

    prop_object_t pobj = nullptr;
    while ((pobj = prop_object_iterator_next(piter))) {
        std::string name(prop_dictionary_keysym_cstring_nocopy(
              static_cast<prop_dictionary_keysym_t>(pobj)));
        if (name.substr(0, 7) != "acpibat")
            continue;

        const prop_array_t parray = static_cast<prop_array_t>(
            prop_dictionary_get_keysym(pdict,
              static_cast<prop_dictionary_keysym_t>(pobj)));
        if (prop_object_type(parray) != PROP_TYPE_ARRAY)
            continue;

        const prop_object_iterator_t piter2 = prop_array_iterator(parray);
        if (!piter2)
            logFatal << "Could not get sensor iterator" << std::endl;

        // For each battery find.
        int present = 0, capacity = 0, charge = 0, low = 0, crit = 0;

        while ((pobj = prop_object_iterator_next(piter2))) {
            const prop_dictionary_t pdict =
                static_cast<prop_dictionary_t>(pobj);
            prop_object_t pobj1 = prop_dictionary_get(pdict, "state");
            if (!pobj1)
                continue;

            if (prop_string_equals_cstring(static_cast<prop_string_t>(pobj1),
                "invalid")
              || prop_string_equals_cstring(static_cast<prop_string_t>(pobj1),
                "unknown"))
                continue; // skip sensors without valid data
            if (!(pobj1 = prop_dictionary_get(pdict, "description")))
                continue;

            name = prop_string_cstring_nocopy(
                static_cast<prop_string_t>(pobj1));

            if (name.substr(0, 7) == "present") // is battery present
                setIfFound(pdict, "cur-value", present);
            else if (name.substr(0, 10) == "design cap") // get full capacity
                setIfFound(pdict, "cur-value", capacity);
            else if (name.substr(0, 7) == "charge") {
                // get present charge, low and critical levels
                setIfFound(pdict, "cur-value", charge);
                setIfFound(pdict, "warning-capacity", low);
                setIfFound(pdict, "critical-capacity", crit);
            }
            else if (name.substr(0, 8) == "charging") {
                // charging or not?
                int unused = 0;
                if (setIfFound(pdict, "cur-value", unused))
                    state |= XOSVIEW_BATT_CHARGING;
            }
            else if (name.substr(0, 14) == "discharge rate") {
                // discharging or not?
                int unused = 0;
                if (setIfFound(pdict, "cur-value", unused))
                    state |= XOSVIEW_BATT_DISCHARGING;
            }
        }

        if (present) {
            total_capacity += capacity;
            total_charge += charge;
            total_low += low;
            total_crit += crit;
            batteries++;
        }
        prop_object_iterator_release(piter2);
    }
    prop_object_iterator_release(piter);
    prop_object_release(pdict);

    if (batteries == 0) { // all batteries are off
        state = XOSVIEW_BATT_NONE;
        remaining = 0;
        return;
    }
    remaining = 100 * total_charge / total_capacity;
    if (!(state & XOSVIEW_BATT_CHARGING) &&
      !(state & XOSVIEW_BATT_DISCHARGING))
        state |= XOSVIEW_BATT_FULL;  // full when not charging nor discharging
    if (total_capacity < total_low)
        state |= XOSVIEW_BATT_LOW;
    if (total_capacity < total_crit)
        state |= XOSVIEW_BATT_CRITICAL;

}
#endif


#if defined(XOSVIEW_OPENBSD)
void BtryMeter::getBatteryInfo(int &remaining, unsigned int &state) {
    state = XOSVIEW_BATT_NONE;
    float total_capacity = 0, total_charge = 0, total_low = 0, total_crit = 0;

    int batteries = 0;
    while (batteries < 1024) {
        const std::string battery = "acpibat" + util::repr(batteries);
        float val = -1.0;
        std::string emptyStr;
        BSDGetSensor(battery, "amphour0", val, emptyStr); // full capacity

        if (val < 0) // no more batteries
            break;

        batteries++;
        total_capacity += val;
        emptyStr.clear();
        BSDGetSensor(battery, "amphour1", val, emptyStr); // warning capacity
        total_low += val;
        emptyStr.clear();
        BSDGetSensor(battery, "amphour2", val, emptyStr); // low capacity
        total_crit += val;
        emptyStr.clear();
        BSDGetSensor(battery, "amphour3", val, emptyStr); // remaining
        total_charge += val;
        emptyStr.clear();
        BSDGetSensor(battery, "raw0", val, emptyStr); // state
        if (static_cast<int>(val) == 1)
            state |= XOSVIEW_BATT_DISCHARGING;
        else if (static_cast<int>(val) == 2)
            state |= XOSVIEW_BATT_CHARGING;
        // there's also 0 state for idle/full
    }

    if (batteries == 0) { // all batteries are off
        state = XOSVIEW_BATT_NONE;
        remaining = 0;
        return;
    }
    remaining = 100 * total_charge / total_capacity;
    if (!(state & XOSVIEW_BATT_CHARGING) &&
      !(state & XOSVIEW_BATT_DISCHARGING))
        state |= XOSVIEW_BATT_FULL;  // full when not charging nor discharging
    if (total_capacity < total_low)
        state |= XOSVIEW_BATT_LOW;
    if (total_capacity < total_crit)
        state |= XOSVIEW_BATT_CRITICAL;

}
#endif


#if defined(XOSVIEW_FREEBSD)
void BtryMeter::getBatteryInfo(int &remaining, unsigned int &state) {
    state = XOSVIEW_BATT_NONE;
    // Adapted from acpiconf and APM.
    int fd;
    if ((fd = open(ACPIDEV, O_RDONLY)) == -1) {
        // No ACPI -> try APM.
        if ((fd = open(APMDEV, O_RDONLY)) == -1)
            logFatal << "could not open " << ACPIDEV << " or " << APMDEV
                     << std::endl;

        struct apm_info aip;
        if (ioctl(fd, APMIO_GETINFO, &aip) == -1)
            logFatal << "failed to get APM battery info" << std::endl;
        if (close(fd) == -1)
            logFatal << "Could not close " << APMDEV << std::endl;
        if (aip.ai_batt_life <= 100)
            remaining = aip.ai_batt_life; // only 0-100 are valid values
        else
            remaining = 0;
        if (aip.ai_batt_stat == 0)
            state |= XOSVIEW_BATT_FULL;
        else if (aip.ai_batt_stat == 1)
            state |= XOSVIEW_BATT_LOW;
        else if (aip.ai_batt_stat == 2)
            state |= XOSVIEW_BATT_CRITICAL;
        else if (aip.ai_batt_stat == 3)
            state |= XOSVIEW_BATT_CHARGING;
        else
            state = XOSVIEW_BATT_NONE;
    }
    else { // ACPI
        union acpi_battery_ioctl_arg battio;
        battio.unit = ACPI_BATTERY_ALL_UNITS;
        if (ioctl(fd, ACPIIO_BATT_GET_BATTINFO, &battio) == -1)
            logFatal << "failed to get ACPI battery info" << std::endl;
        if (close(fd) == -1)
            logFatal << "Could not close " << ACPIDEV << std::endl;

        remaining = battio.battinfo.cap;
        if (battio.battinfo.state != ACPI_BATT_STAT_NOT_PRESENT) {
            if (battio.battinfo.state == 0)
                state |= XOSVIEW_BATT_FULL;
            if (battio.battinfo.state & ACPI_BATT_STAT_CRITICAL)
                state |= XOSVIEW_BATT_CRITICAL;
            if (battio.battinfo.state & ACPI_BATT_STAT_DISCHARG)
                state |= XOSVIEW_BATT_DISCHARGING;
            if (battio.battinfo.state & ACPI_BATT_STAT_CHARGING)
                state |= XOSVIEW_BATT_CHARGING;
        }
    }

}
#endif


#if defined(XOSVIEW_DFBSD)
void BtryMeter::getBatteryInfo(int &remaining, unsigned int &state) {
    state = XOSVIEW_BATT_NONE;
    // Adapted from acpiconf and APM.
    int fd;
    if ((fd = open(ACPIDEV, O_RDONLY)) == -1) {
        remaining = 0;
        state = XOSVIEW_BATT_NONE;
    }
    else { // ACPI
        union acpi_battery_ioctl_arg battio;
        battio.unit = ACPI_BATTERY_ALL_UNITS;
        if (ioctl(fd, ACPIIO_BATT_GET_BATTINFO, &battio) == -1)
            logFatal << "failed to get ACPI battery info" << std::endl;
        if (close(fd) == -1)
            logFatal << "Could not close " << ACPIDEV << std::endl;

        remaining = battio.battinfo.cap;
        if (battio.battinfo.state != ACPI_BATT_STAT_NOT_PRESENT) {
            if (battio.battinfo.state == 0)
                state |= XOSVIEW_BATT_FULL;
            if (battio.battinfo.state & ACPI_BATT_STAT_CRITICAL)
                state |= XOSVIEW_BATT_CRITICAL;
            if (battio.battinfo.state & ACPI_BATT_STAT_DISCHARG)
                state |= XOSVIEW_BATT_DISCHARGING;
            if (battio.battinfo.state & ACPI_BATT_STAT_CHARGING)
                state |= XOSVIEW_BATT_CHARGING;
        }
    }
}
#endif
