/*
Copyright (C) 1997-2008 ZSNES Team ( zsKnight, _Demo_, pagefault, Nach )

http://www.zsnes.com
http://sourceforge.net/projects/zsnes
https://zsnes.bountysource.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.

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, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "../gblhdr.h"

/* Battery probing on Linux, by Nach, most of the code from David Lee Lambert.
   ACPI needs Linux 2.4 or later; it compiles on older ones regardless. */

#ifdef __linux__
#include <dirent.h>
#include <math.h>

int CheckBattery()
{
    int battery = -1; // No battery / Can't get info
    const char* ac = "/proc/acpi/ac_adapter/";

    // Check ac adapter
    DIR* ac_dir = opendir(ac);
    if (ac_dir) {
        char fnbuf[284]; // longer than len(ac)+len(HEXDIGIT*4)+len({state|info})
        FILE* fp;
        const char* pattern = " %39[^:]: %39[ -~]"; // for sscanf
        char line[80], key[40], arg[40];

        struct dirent* ent;
        while ((ent = readdir(ac_dir))) {
            if (ent->d_name[0] == '.') {
                continue;
            }

            snprintf(fnbuf, sizeof fnbuf, "%s%s/state", ac, ent->d_name);
            fp = fopen(fnbuf, "r");
            if (fp) {
                while (fgets(line, 80, fp) && sscanf(line, pattern, key, arg) == 2) {
                    if (!strcmp(key, "state")) {
                        if (!strcmp(arg, "on-line")) {
                            battery = 0;
                        } else if (!strcmp(arg, "off-line")) {
                            battery = 1;
                            break;
                        }
                    }
                }
                fclose(fp);
            }
        }
        closedir(ac_dir);
    }
    return (battery);
}

static int BatteryLifeTime;
static int BatteryLifePercent;

static void update_battery_info()
{
    const char* batt = "/proc/acpi/battery/";

    // Check batteries
    DIR* batt_dir = opendir(batt);
    if (batt_dir) {
        char fnbuf[284]; // longer than len(ac)+len(d_name)+len({state|info})
        FILE* fp;
        const char* pattern = " %39[^:]: %39[ -~]"; // for sscanf
        char line[80], key[40], arg[40];

        float x, design_capacity = 0.0f, remaining_capacity = 0.0f, present_rate = 0.0f, full_capacity = 0.0f;

        struct dirent* ent;
        while ((ent = readdir(batt_dir))) {
            if (ent->d_name[0] == '.') {
                continue;
            }
            snprintf(fnbuf, sizeof fnbuf, "%s%s/info", batt, ent->d_name);
            fp = fopen(fnbuf, "r");
            if (fp) {
                while (fgets(line, 80, fp) && sscanf(line, pattern, key, arg) == 2) {
                    if (!strcmp(key, "design capacity") && sscanf(arg, "%g", &x) == 1) {
                        design_capacity += x;
                    } else if (!strcmp(key, "last full capacity") && sscanf(arg, "%g", &x) == 1) {
                        full_capacity += x;
                    }
                }
                fclose(fp);
            }
            snprintf(fnbuf, sizeof fnbuf, "%s%s/state", batt, ent->d_name);
            fp = fopen(fnbuf, "r");
            if (fp) {
                int charging = 0;
                while (fgets(line, 80, fp) && sscanf(line, pattern, key, arg) == 2) {
                    if (!strcmp(key, "charging state")) {
                        if (!strcmp(arg, "discharging")) {
                            charging = -1;
                        } else if (!strcmp(arg, "charging")) {
                            charging = 1;
                        }
                    } else if (!strcmp(key, "present rate") && sscanf(arg, "%g", &x) == 1) {
                        present_rate += charging * x;
                        charging = 0;
                    } else if (!strcmp(key, "remaining capacity") && sscanf(arg, "%g:", &x) == 1) {
                        remaining_capacity += x;
                        charging = 0;
                    }
                }
                fclose(fp);
            }
        }
        if (design_capacity > 0.0f) {
            BatteryLifePercent = (int)floorf(remaining_capacity / ((full_capacity > 0.0f) ? full_capacity : design_capacity) * 100.0);
            if (BatteryLifePercent > 100) {
                BatteryLifePercent = 100;
            }
            if (present_rate < 0.0f) {
                // Linux specifies rates in mWh or mAh
                BatteryLifeTime = (int)floorf(remaining_capacity / (-present_rate) * 3600.0);
            }
        }
        closedir(batt_dir);
    }
}

int CheckBatteryTime()
{
    BatteryLifeTime = -1;
    update_battery_info();
    return (BatteryLifeTime);
}

int CheckBatteryPercent()
{
    BatteryLifePercent = -1;
    update_battery_info();
    return (BatteryLifePercent);
}

/* Battery on FreeBSD/DragonFly, by Nach. */
#elif (defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__))
#include <sys/sysctl.h>
#include <sys/types.h>

int CheckBattery()
{
    int state;
    size_t state_len = sizeof(state);
    if (!sysctlbyname("hw.acpi.battery.state", &state, &state_len, 0, 0)) {
        if ((state > -1) && (state < 7)) // 7 == failure
        {
            if (!state || state & 2) {
                return (0); // Plugged in
            }
            return (1); // Running off of battery
        }
    }
    return (-1);
}

// Note that I have not yet gotten anyone to test if this function has correct info returned
int CheckBatteryTime()
{
    int batt_time;
    size_t batt_time_len = sizeof(batt_time);
    if (!sysctlbyname("hw.acpi.battery.time", &batt_time, &batt_time_len, 0, 0)) {
        if (batt_time > -1) {
            return (batt_time * 60);
        }
    }
    return (-1);
}

int CheckBatteryPercent()
{
    int life = -1;
    size_t life_len = sizeof(life);
    sysctlbyname("hw.acpi.battery.life", &life, &life_len, 0, 0);
    return (life);
}

/* Battery on NetBSD/OpenBSD, by Nach. The least tested of the three. */

#elif (defined(__NetBSD__) || defined(__OpenBSD__))
#include <fcntl.h>
#include <machine/apmvar.h>
#include <sys/ioctl.h>
#include <unistd.h>

#ifndef APM_BATT_ABSENT
#define APM_BATT_ABSENT APM_BATTERY_ABSENT
#endif

int CheckBattery()
{
    int fd = open("/dev/apm", O_RDONLY);
    if (fd != -1) {
        struct apm_power_info info;
        if (!ioctl(fd, APM_IOC_GETPOWER, &info) && (info.battery_state != APM_BATT_UNKNOWN) && (info.battery_state != APM_BATT_ABSENT)) {
            close(fd);
            if ((info.battery_state == APM_BATT_CHARGING) || (info.ac_state == APM_AC_ON)) {
                return (0);
            } // Plugged in
            return (1); // Running off of battery
        }
        close(fd);
    }
    return (-1);
}

int CheckBatteryTime()
{
    int fd = open("/dev/apm", O_RDONLY);
    if (fd != -1) {
        struct apm_power_info info;
        if (!ioctl(fd, APM_IOC_GETPOWER, &info) && (info.minutes_left > 0) && (info.minutes_left < 0xFFFF)) {
            close(fd);
            return (info.minutes_left * 60);
        }
        close(fd);
    }
    return (-1);
}

int CheckBatteryPercent()
{
    int fd = open("/dev/apm", O_RDONLY);
    if (fd != -1) {
        struct apm_power_info info;
        if (!ioctl(fd, APM_IOC_GETPOWER, &info)) {
            close(fd);
            return ((info.battery_life == 255) ? 100 : info.battery_life);
        }
        close(fd);
    }
    return (-1);
}

/* Battery on macOS, by drizztbsd and Nach. */

#elif defined(__APPLE__)
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/ps/IOPSkeys.h>
#include <IOKit/ps/IOPowerSources.h>
#include <math.h>
#include <sys/types.h>

static int stringsAreEqual(CFStringRef a, CFStringRef b)
{
    if (!a || !b) {
        return (0);
    }
    return (CFStringCompare(a, b, 0) == kCFCompareEqualTo);
}

static int BatteryLifeTime;
static int BatteryLifePercent;
static int HasBattery;

static void update_battery_info()
{
    CFTypeRef powerBlob = IOPSCopyPowerSourcesInfo();
    CFArrayRef powerSourcesList = IOPSCopyPowerSourcesList(powerBlob);
    unsigned int count = CFArrayGetCount(powerSourcesList);
    unsigned int i;
    unsigned int tmp;

    int totalCurrentCapacity = 0, totalMaxCapacity = 0;

    BatteryLifeTime = -1;
    BatteryLifePercent = -1;
    HasBattery = -1;

    for (i = 0; i < count; ++i) {
        CFTypeRef powerSource;
        CFDictionaryRef description;

        powerSource = CFArrayGetValueAtIndex(powerSourcesList, i);
        description = IOPSGetPowerSourceDescription(powerBlob, powerSource);

        // continue if one battery is not present
        if (CFDictionaryGetValue(description, CFSTR(kIOPSIsPresentKey)) == kCFBooleanFalse) {
            continue;
        }

        if (stringsAreEqual(CFDictionaryGetValue(description, CFSTR(kIOPSTransportTypeKey)), CFSTR(kIOPSInternalType))) {
            int currentCapacity, maxCapacity;

            CFStringRef currentState = CFDictionaryGetValue(description, CFSTR(kIOPSPowerSourceStateKey));

            if (CFEqual(currentState, CFSTR(kIOPSACPowerValue)) && (HasBattery != 1)) {
                HasBattery = 0;
            } else if (CFEqual(currentState, CFSTR(kIOPSBatteryPowerValue))) {
                CFNumberRef timeToEmptyNum = CFDictionaryGetValue(description, CFSTR(kIOPSTimeToEmptyKey));
                if (CFNumberGetValue(timeToEmptyNum, kCFNumberIntType, &tmp)) {
                    if (BatteryLifeTime > -1) {
                        BatteryLifeTime += tmp;
                    } else {
                        BatteryLifeTime = tmp;
                    }
                }
                HasBattery = 1;
            }
            CFNumberRef currentCapacityNum = CFDictionaryGetValue(description, CFSTR(kIOPSCurrentCapacityKey));
            CFNumberRef maxCapacityNum = CFDictionaryGetValue(description, CFSTR(kIOPSMaxCapacityKey));

            if (CFNumberGetValue(currentCapacityNum, kCFNumberIntType, &currentCapacity) && CFNumberGetValue(maxCapacityNum, kCFNumberIntType, &maxCapacity)) {
                totalCurrentCapacity += currentCapacity;
                totalMaxCapacity += maxCapacity;
            }
        }
    }

    CFRelease(powerSourcesList);
    CFRelease(powerBlob);

    if (totalCurrentCapacity && totalMaxCapacity) {
        BatteryLifePercent = (int)roundf((totalCurrentCapacity / (float)totalMaxCapacity) * 100.0f);
    }
}

int CheckBattery()
{
    update_battery_info();
    return (HasBattery);
}

int CheckBatteryTime()
{
    update_battery_info();
    if (BatteryLifeTime > -1) {
        return (BatteryLifeTime * 60);
    }
    return (-1);
}

int CheckBatteryPercent()
{
    update_battery_info();
    return (BatteryLifePercent);
}

#else // Not Linux, FreeBSD/DragonFlyBSD, NetBSD/OpenBSD, macOS

int CheckBattery()
{
    return (-1);
}

int CheckBatteryTime()
{
    return (-1);
}

int CheckBatteryPercent()
{
    return (-1);
}

#endif
