#define _GNU_SOURCE

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <time.h>

#include "libacpi.h"

extern char *state[];

#define PROC_DATA_SOURCE    0
#define SYSFS_DATA_SOURCE   1
static int data_source;

/* local proto */
int acpi_get_design_cap(int batt);

static int read_sysfs_file(char *node, char *prop, char *buf, size_t buflen)
{
    char tmp[256];
    FILE *fp;
    int ret;

    ret = snprintf(tmp, sizeof(tmp), "/sys/class/power_supply/%s/%s", node, prop);
    if (ret >= (int)sizeof(tmp)) {
	perr("Path too long for %s/%s\n", node, prop);
	return -1;
    }

    fp = fopen(tmp, "r");
    if (fp == NULL) {
	perr("Could not open %s/%s\n", node, prop);
	return -2;
    }

    ret = fread(buf, 1, buflen - 1, fp);

    fclose(fp);

    if (ret == 0) {
	perr("Could not read %s/%s\n", node, prop);
	return -3;
    }
  
    buf[ret] = '\0';

    return 0;
}

/* initialise the batteries */
static int sysfs_init_batteries(global_t *globals)
{
    DIR *battdir;
    struct dirent *batt;
    char *name;
    char *names[MAXBATT];
    char ps_type[16];
    int i, j;
    
    /* now enumerate batteries */
    globals->battery_count = 0;
    battdir = opendir("/sys/class/power_supply");
    if (battdir == NULL) {
	pfatal("No batteries or ACPI not supported\n");
	return 1;
    }
    while ((batt = readdir(battdir))) {
	/* there's a serious problem with this code when there's
	 * more than one battery: the readdir won't return the
	 * entries in sorted order, so battery one won't 
	 * necessarily be the first one returned. So, we need
	 * to sort them ourselves before adding them to the 
	 * batteries array. */
	name = batt->d_name;
	
	/* skip ., .. and dotfiles */
	if (name[0] == '.')
	    continue;

	if (read_sysfs_file(name, "type", ps_type, sizeof(ps_type)) < 0)
	    continue;

	if (strncmp("Battery", ps_type, 7) != 0)
	    continue;

	names[globals->battery_count] = strdup(name);
	globals->battery_count++;
    }
    closedir(battdir);

    /* A nice quick insertion sort, ala CLR. */
    {
	char *tmp1, *tmp2;
	
	for (i = 1; i < globals->battery_count; i++) {
	    tmp1 = names[i];
	    j = i - 1;
	    while ((j >= 0) && ((strcmp(tmp1, names[j])) < 0)) {
		tmp2 = names[j+1];
		names[j+1] = names[j];
		names[j] = tmp2;
	    }
	}
    }
    
    for (i = 0; i < globals->battery_count; i++) {
	snprintf(batteries[i].name, MAX_NAME, "%s", names[i]);
	pdebug("battery detected at /sys/class/power_supply/%s\n", batteries[i].name);
	pinfo("found battery %s\n", names[i]);

	if (read_sysfs_file(batteries[i].name, "energy_now", ps_type, sizeof(ps_type)) == 0)
	    batteries[i].sysfs_capa_mode = SYSFS_CAPA_ENERGY;
	else if (read_sysfs_file(batteries[i].name, "charge_now", ps_type, sizeof(ps_type)) == 0)
	    batteries[i].sysfs_capa_mode = SYSFS_CAPA_CHARGE;
	else if (read_sysfs_file(batteries[i].name, "capacity", ps_type, sizeof(ps_type)) == 0) {
	    batteries[i].sysfs_capa_mode = SYSFS_CAPA_PERCENT;
	    batteries[i].design_cap = 100;
	    batteries[i].last_full_cap = 100;
	} else
	    batteries[i].sysfs_capa_mode = SYSFS_CAPA_ERR;
    }

    /* tell user some info */
    pdebug("%d batteries detected\n", globals->battery_count);
    pinfo("libacpi: found %d batter%s\n", globals->battery_count,
	  (globals->battery_count == 1) ? "y" : "ies");

    return 0;
}

/* initialise the batteries */
static int procfs_init_batteries(global_t *globals)
{
    DIR *battdir;
    struct dirent *batt;
    char *name;
    char *names[MAXBATT];
    int i, j;
    
    /* now enumerate batteries */
    globals->battery_count = 0;
    battdir = opendir("/proc/acpi/battery");
    if (battdir == NULL) {
	pfatal("No batteries or ACPI not supported\n");
	return 1;
    }
    while ((batt = readdir(battdir))) {
	/* there's a serious problem with this code when there's
	 * more than one battery: the readdir won't return the
	 * entries in sorted order, so battery one won't 
	 * necessarily be the first one returned. So, we need
	 * to sort them ourselves before adding them to the 
	 * batteries array. */
	name = batt->d_name;
	
	/* skip . and .. */
	if (!strncmp(".", name, 1) || !strncmp("..", name, 2))
	    continue;

	names[globals->battery_count] = strdup(name);
	globals->battery_count++;
    }
    closedir(battdir);

    /* A nice quick insertion sort, ala CLR. */
    {
	char *tmp1, *tmp2;
	
	for (i = 1; i < globals->battery_count; i++) {
	    tmp1 = names[i];
	    j = i - 1;
	    while ((j >= 0) && ((strcmp(tmp1, names[j])) < 0)) {
		tmp2 = names[j+1];
		names[j+1] = names[j];
		names[j] = tmp2;
	    }
	}
    }
    
    for (i = 0; i < globals->battery_count; i++) {
	snprintf(batteries[i].name, MAX_NAME, "%s", names[i]);
	snprintf(batteries[i].info_file, MAX_NAME, 
		 "/proc/acpi/battery/%s/info", names[i]);
	snprintf(batteries[i].state_file, MAX_NAME, 
		 "/proc/acpi/battery/%s/state", names[i]);
	pdebug("battery detected at %s\n", batteries[i].info_file);
	pinfo("found battery %s\n", names[i]);
    }

    /* tell user some info */
    pdebug("%d batteries detected\n", globals->battery_count);
    pinfo("libacpi: found %d batter%s\n", globals->battery_count,
	    (globals->battery_count == 1) ? "y" : "ies");
    
    return 0;
}

int init_batteries(global_t *globals)
{
  if (data_source == SYSFS_DATA_SOURCE)
    return sysfs_init_batteries(globals);
  else
    return procfs_init_batteries(globals);
}

/* a stub that just calls the current function */
int reinit_batteries(global_t *globals)
{
    pdebug("reinitialising batteries\n");
    return init_batteries(globals);
}

/* the actual name of the subdirectory under power_supply may
 * be anything, so we need to read the directory and use the
 * name we find there. */
static int sysfs_init_ac_adapters(global_t *globals)
{
    DIR *acdir;
    struct dirent *adapter;
    adapter_t *ap = &globals->adapter;
    char *name;
    char ps_type[16];

    acdir = opendir("/sys/class/power_supply");
    if (acdir == NULL) {
	pfatal("Unable to open /sys/class/power_supply -"
		" are you sure this system supports ACPI?\n");
	return 1;
    }
    name = NULL;
    while ((adapter = readdir(acdir)) != NULL) {
	name = adapter->d_name;

	if (name[0] == '.') {
	  name = NULL;
	  continue;
	}

	if (read_sysfs_file(name, "type", ps_type, sizeof(ps_type)) < 0) {
	  name = NULL;
	  continue;
	}

	if (strncmp("Mains", ps_type, 5) == 0) {
	  pdebug("found adapter %s\n", name);
	  break;
	} else {
	  name = NULL;
	}
    }
    closedir(acdir);

    if (name == NULL) {
      perr("No AC adapter found !\n");
      return 1;
    }

    /* we'll just use the first adapter we find ... */
    ap->name = strdup(name);
    pinfo("libacpi: found ac adapter %s\n", ap->name);
    
    return 0;
}

/* the actual name of the subdirectory under ac_adapter may
 * be anything, so we need to read the directory and use the
 * name we find there. */
static int procfs_init_ac_adapters(global_t *globals)
{
    DIR *acdir;
    struct dirent *adapter;
    adapter_t *ap = &globals->adapter;
    char *name;

    acdir = opendir("/proc/acpi/ac_adapter");
    if (acdir == NULL) {
	pfatal("Unable to open /proc/acpi/ac_adapter -"
		" are you sure this system supports ACPI?\n");
	return 1;
    }
    name = NULL;
    while ((adapter = readdir(acdir)) != NULL) {
	name = adapter->d_name;

	if (!strncmp(".", name, 1) || !strncmp("..", name, 2))
	    continue;
	pdebug("found adapter %s\n", name);
    }
    closedir(acdir);
    /* we /should/ only see one filename other than . and .. so
     * we'll just use the last value name acquires . . . */
    ap->name = strdup(name);
    snprintf(ap->state_file, MAX_NAME, "/proc/acpi/ac_adapter/%s/state",
	     ap->name);
    pinfo("libacpi: found ac adapter %s\n", ap->name);
    
    return 0;
}

int init_ac_adapters(global_t *globals)
{
  if (data_source == SYSFS_DATA_SOURCE)
    return sysfs_init_ac_adapters(globals);
  else
    return procfs_init_ac_adapters(globals);
}

/* stub that does nothing but call the normal init function */
int reinit_ac_adapters(global_t *globals)
{
    pdebug("reinitialising ac adapters\n");
    return init_ac_adapters(globals);
}

/* see if we have ACPI support and check version */
int power_init(global_t *globals)
{
    FILE *acpi;
    char buf[4096];
    int acpi_ver = 0;
    int retval;
    unsigned int version_offset = 0;

    if (!(acpi = fopen("/sys/module/acpi/parameters/acpica_version", "r"))) {
	if (!(acpi = fopen("/proc/acpi/info", "r"))) {
	    pfatal("This system does not support ACPI\n");
	    return 1;
	} else {
	    version_offset = 25;
	}
    }

    /* okay, now see if we got the right version */
    fread(buf, 4096, 1, acpi);
    acpi_ver = strtol(buf + version_offset, NULL, 10);
    pinfo("ACPI version detected: %d\n", acpi_ver);
    if (acpi_ver < 20020214) {
	pfatal("This version requires ACPI subsystem version 20020214\n");
	fclose(acpi);
	return 1;
    }
    /* yep, all good */
    fclose(acpi);

    /* determine data source */
    if (access("/sys/class/power_supply", R_OK | X_OK) == 0) {
	data_source = SYSFS_DATA_SOURCE;
	pinfo("Selecting sysfs as the data source\n");
    } else {
	data_source = PROC_DATA_SOURCE;
	pinfo("Selecting procfs as the data source\n");
    }

    if (!(retval = init_batteries(globals)))
	retval = init_ac_adapters(globals);

    return retval;
}

/* reinitialise everything, to deal with changing batteries or ac adapters */
int power_reinit(global_t *globals)
{
    FILE *acpi;
    int retval;

    if (!(acpi = fopen("/sys/module/acpi/parameters/acpica_version", "r"))) {
	if (!(acpi = fopen("/proc/acpi/info", "r"))) {
	    pfatal("Could not reopen ACPI info file - does this system support ACPI?\n");
	    return 1;
	}
    }
    
    if (!(retval = reinit_batteries(globals)))
	retval = reinit_ac_adapters(globals);

    return retval;
}

static char *get_value(char *string)
{
    char *retval;
    int i;

    if (string == NULL)
	return NULL;

    i = 0;
    while (string[i] != ':') i++;
    while (!isalnum(string[i])) i++;
    retval = (string + i);

    return retval;
}

static int check_error(char *buf)
{
    if(strstr(buf, "ERROR") != NULL)
	return 1;
    return 0;
}

static power_state_t sysfs_get_power_status(global_t *globals)
{
    char online[2];
    adapter_t *ap = &globals->adapter;

    if (read_sysfs_file(ap->name, "online", online, sizeof(online)) < 0)
      return PS_ERR;

    if (*online == '1')
	return AC;
    else
	return BATT;
}

static power_state_t procfs_get_power_status(global_t *globals)
{
    FILE *file;
    char buf[1024];
    char *val;
    adapter_t *ap = &globals->adapter;
    
    if ((file = fopen(ap->state_file, "r")) == NULL) {
	snprintf(buf, 1024, "Could not open state file %s", ap->state_file);
	perror(buf);
	return PS_ERR;
    }

    fgets(buf, 1024, file);
    fclose(file);
    val = get_value(buf);
    if ((strncmp(val, "on-line", 7)) == 0)
	return AC;
    else
	return BATT;
}

power_state_t get_power_status(global_t *globals)
{
    if (data_source == SYSFS_DATA_SOURCE)
	return sysfs_get_power_status(globals);
    else
	return procfs_get_power_status(globals);
}

static int sysfs_get_battery_info(global_t *globals, int batt_no)
{
    battery_t *info = &batteries[batt_no];
    char buf[32];
    int ret;

    /* check to see if battery is present */
    ret = read_sysfs_file(info->name, "present", buf, sizeof(buf));
    if (ret < 0) {
	/* interestingly, when the battery is not present, the whole
	 * /sys/class/power_supply/BATn directory does not exist.
	 * Yes, this is broken.
	 */
	if (ret == -2)
	    info->present = 0;

	/* reinit batteries, this one went away and it's very
	   possible there just isn't any other one */
	reinit_batteries(globals);

	return 0;
    }

    info->present = (*buf == '1');
    if (!info->present) {
	pinfo("Battery %s not present\n", info->name);
	return 0;
    }

    /* get design capacity
     * note that all these integer values can also contain the
     * string 'unknown', so we need to check for this. */
    if (info->sysfs_capa_mode == SYSFS_CAPA_ENERGY) {
	if (read_sysfs_file(info->name, "energy_full_design", buf, sizeof(buf)) < 0)
	    info->design_cap = -1;
	else
	    info->design_cap = strtoul(buf, NULL, 10) / 1000;

	/* get last full capacity */
	if (read_sysfs_file(info->name, "energy_full", buf, sizeof(buf)) < 0)
	    info->last_full_cap = -1;
	else
	    info->last_full_cap = strtoul(buf, NULL, 10) / 1000;
    } else if (info->sysfs_capa_mode == SYSFS_CAPA_CHARGE) {
	/* get design capacity */
	if (read_sysfs_file(info->name, "charge_full_design", buf, sizeof(buf)) < 0)
	    info->design_cap = -1;
	else
	    info->design_cap = strtoul(buf, NULL, 10) / 1000;

	/* get last full capacity */
	if (read_sysfs_file(info->name, "charge_full", buf, sizeof(buf)) < 0)
	    info->last_full_cap = -1;
	else
	    info->last_full_cap = strtoul(buf, NULL, 10) / 1000;
    } else if (info->sysfs_capa_mode != SYSFS_CAPA_PERCENT) {
	info->design_cap = -1;
	info->last_full_cap = -1;
    }


    /* get design voltage */
    if (read_sysfs_file(info->name, "voltage_min_design", buf, sizeof(buf)) < 0)
	info->design_voltage = -1;
    else
	info->design_voltage = strtoul(buf, NULL, 10) / 1000;

    /* get charging state */
    if (read_sysfs_file(info->name, "status", buf, sizeof(buf)) < 0) {
	info->charge_state = CH_ERR;
    } else {
	if (strncmp(buf, "Unknown", 7) == 0)
	    info->charge_state = CH_ERR;
	else if (strncmp(buf, "Discharging", 11) == 0)
	    info->charge_state = DISCHARGE;
	else if (strncmp(buf, "Charging", 8) == 0)
	    info->charge_state = CHARGE;
	else if (strncmp(buf, "Not charging", 12) == 0)
	    info->charge_state = NO_CHARGE;
	else if (strncmp(buf, "Full", 4) == 0)
	    info->charge_state = FULL; /* DISCHARGE ? as per old comment ... */
    }

    /* get current rate of burn 
     * note that if it's on AC, this will report 0 */
    if (read_sysfs_file(info->name, "current_now", buf, sizeof(buf)) < 0)
	info->present_rate = -1;
    else {
	int rate;
	rate = strtoul(buf, NULL, 10) / 1000;
	info->present_rate = (rate != 0) ? rate : info->present_rate;
    }

    if (info->sysfs_capa_mode == SYSFS_CAPA_ENERGY) {
	/* get remaining capacity */
	if (read_sysfs_file(info->name, "energy_now", buf, sizeof(buf)) < 0)
	    info->remaining_cap = -1;
	else
	    info->remaining_cap = strtoul(buf, NULL, 10) / 1000;

    } else if (info->sysfs_capa_mode == SYSFS_CAPA_CHARGE) {
	/* get remaining capacity */
	if (read_sysfs_file(info->name, "charge_now", buf, sizeof(buf)) < 0)
	    info->remaining_cap = -1;
	else
	    info->remaining_cap = strtoul(buf, NULL, 10) / 1000;
    } else if (info->sysfs_capa_mode == SYSFS_CAPA_PERCENT) {
	/* get remaining capacity */
	if (read_sysfs_file(info->name, "capacity", buf, sizeof(buf)) < 0)
	    info->remaining_cap = -1;
	else
	    info->remaining_cap = strtoul(buf, NULL, 10) / 1000;
    } else {
	info->remaining_cap = -1;
    }

    /* get current voltage */
    if (read_sysfs_file(info->name, "voltage_now", buf, sizeof(buf)) < 0)
	info->present_voltage = -1;
    else
	info->present_voltage = strtoul(buf, NULL, 10) / 1000;

    return 1;
}

static int procfs_get_battery_info(global_t *globals, int batt_no)
{
    FILE *file;
    battery_t *info = &batteries[batt_no];
    char buf[1024];
    char *entry;
    int buflen;
    char *val;

    globals = globals; /* silencing a warning */

    if ((file = fopen(info->info_file, "r")) == NULL) {
	/* this is cheating, but string concatenation should work . . . */
	pfatal("Could not open %s:", info->info_file );
	perror(NULL);
	return 0;
    }
    
    /* grab the contents of the file */
    buflen = fread(buf, sizeof(buf), 1, file);
    fclose(file);

    /* check to see if there were any errors reported in the file */
    if(check_error(buf)) {
	pinfo("Error reported in file %s - discarding data\n",
	      info->info_file);
	return 0;
    }

    /* check to see if battery is present */
    entry = strstr(buf, "present:");
    val = get_value(entry);
    if ((strncmp(val, "yes", 3)) == 0) {
	info->present = 1;
    } else {
	pinfo("Battery %s not present\n", info->name);
	info->present = 0;
	return 0;
    }
    
    /* get design capacity
     * note that all these integer values can also contain the
     * string 'unknown', so we need to check for this. */
    entry = strstr(buf, "design capacity:");
    val = get_value(entry);
    if (val[0] == 'u') 
	info->design_cap = -1;
    else
	info->design_cap = strtoul(val, NULL, 10);

    /* get last full capacity */
    entry = strstr(buf, "last full capacity:");
    val = get_value(entry);
    if (val[0] == 'u')
	info->last_full_cap = -1;
    else
	info->last_full_cap = strtoul(val, NULL, 10);

    /* get design voltage */
    entry = strstr(buf, "design voltage:");
    val = get_value(entry);
    if (val[0] == 'u')
	info->design_voltage = -1;
    else
	info->design_voltage = strtoul(val, NULL, 10);

    
    if ((file = fopen(info->state_file, "r")) == NULL) {
	perr("Could not open %s:", info->state_file );
	perror(NULL);
	return 0;
    }
    
    /* grab the file contents */
    memset(buf, 0, sizeof(buf));
    buflen = fread(buf, sizeof(buf), 1, file);
    fclose(file);

    /* check to see if there were any errors reported in the file */
    if(check_error(buf)) {
	pinfo("Error reported in file %s - discarding data\n",
	      info->state_file);
	return 0;
    }
    /* check to see if battery is present */
    entry = strstr(buf, "present:");
    val = get_value(entry);
    if ((strncmp(val, "yes", 3)) == 0) {
	info->present = 1;
    } else {
	info->present = 0;
	perr("Battery %s no longer present\n", info->name);
	return 0;
    }

    /* get charging state */
    entry = strstr(buf, "charging state:");
    val = get_value(entry);
    if (val[0] == 'u') 
	info->charge_state = CH_ERR;
    else if ((strncmp(val, "discharging", 10)) == 0)
	info->charge_state = DISCHARGE;
    else if ((strncmp(val, "charged", 7)) == 0)
	/* this is a workaround for machines that report
	 * their charge state as 'charged', rather than
	 * what my laptop does, which is go straight to
	 * 'discharging'. dunno which matches the standard */
	info->charge_state = DISCHARGE;
    else
	info->charge_state = CHARGE;

    /* get current rate of burn 
     * note that if it's on AC, this will report 0 */
    entry = strstr(buf, "present rate:");
    val = get_value(entry);
    if (val[0] == 'u') {
	info->present_rate = -1;
    } else {
	int rate;
	rate = strtoul(val, NULL, 10);
	if (rate != 0)
	    info->present_rate = rate;
    }

    /* get remaining capacity */
    entry = strstr(buf, "remaining capacity:");
    val = get_value(entry);
    if (val[0] == 'u')
	info->remaining_cap = -1;
    else
	info->remaining_cap = strtoul(val, NULL, 10);

    /* get current voltage */
    entry = strstr(buf, "present voltage:");
    val = get_value(entry);
    if (val[0] == 'u')
	info->present_voltage = -1;
    else
	info->present_voltage = strtoul(val, NULL, 10);

    return 1;
}

int get_battery_info(global_t *globals, int batt_no)
{
  if (data_source == SYSFS_DATA_SOURCE)
      return sysfs_get_battery_info(globals, batt_no);
  else
      return procfs_get_battery_info(globals, batt_no);
}

/*
 * 2003-7-1.
 * In order to make this code more convenient for things other than
 * just plain old wmacpi-ng I'm breaking the basic functionality
 * up into several chunks: collecting and collating info for a 
 * single battery, calculating the global info (such as rtime), and 
 * some stuff to provide a similar interface to now.
 */

/* calculate the percentage remaining, using the values of 
 * remaining capacity and last full capacity, as outlined in
 * the ACPI spec v2.0a, section 3.9.3. */
static int calc_remaining_percentage(int batt)
{
    float rcap, lfcap;
    battery_t *binfo;
    int retval;
    
    binfo = &batteries[batt];

    rcap = (float)binfo->remaining_cap;
    lfcap = (float)binfo->last_full_cap;

    /* we use -1 to indicate that the value is unknown . . . */
    if (rcap < 0) {
	perr("unknown percentage value\n");
	retval = -1;
    } else {
	if (lfcap <= 0)
	    lfcap = 1;
	retval = (int)((rcap/lfcap) * 100.0);
	pdebug("percent: %d\n", retval);
    }
    return retval;
}

/* check to see if we've been getting bad data from the batteries - if
 * we get more than some limit we switch to using the remaining capacity
 * for the calculations. */
static enum rtime_mode check_rt_mode(global_t *globals)
{
    int i;
    int bad_limit = 5;
    battery_t *binfo;

    /* if we were told what to do, we should keep doing it */
    if(globals->rt_forced)
	return globals->rt_mode;

    for(i = 0; i < MAXBATT; i++) {
	binfo = &batteries[i];
	if(binfo->present && globals->adapter.power == BATT) {
	    if(binfo->present_rate <= 0) {
		pdebug("Bad report from %s\n", binfo->name);
		binfo->bad_count++;
	    }
	}
    }
    for(i = 0; i < MAXBATT; i++) {
	binfo = &batteries[i];
	if(binfo->bad_count > bad_limit) {
	    if(globals->rt_mode != RT_CAP)
		pinfo("More than %d bad reports from %s; "
		      "Switching to remaining capacity mode\n",
		      bad_limit, binfo->name);
	    return RT_CAP;
	}
    }
    return RT_RATE;
}

/* calculate remaining time until the battery is charged.
 * when charging, the battery state file reports the 
 * current being used to charge the battery. We can use 
 * this and the remaining capacity to work out how long
 * until it reaches the last full capacity of the battery.
 * XXX: make sure this is actually portable . . . */
static int calc_charge_time_rate(int batt)
{
    float rcap, lfcap;
    battery_t *binfo;
    int charge_time = 0;

    binfo = &batteries[batt];

    if (binfo->charge_state == CHARGE) {
	if (binfo->present_rate == -1) {
	    perr("unknown present rate\n");
	    charge_time = -1;
	} else {
	    lfcap = (float)binfo->last_full_cap;
	    rcap = (float)binfo->remaining_cap;
	    
	    charge_time = (int)(((lfcap - rcap)/binfo->present_rate) * 60.0);
	}
    } else
	if (binfo->charge_time)
	    charge_time = 0;
    return charge_time;
}

/* we need to calculate the present rate the same way we do in rt_cap
 * mode, and then use that to estimate charge time. This will 
 * necessarily be even less accurate than it is for remaining time, but
 * it's just as neessary . . . */
static int calc_charge_time_cap(int batt)
{
    static float cap_samples[CAP_SAMPLES];
    static int time_samples[CAP_SAMPLES];
    static int sample_count = 0;
    static int current = 0;
    static int old = 1;
    int rtime;
    int tdiff;
    float cdiff;
    float current_rate;
    battery_t *binfo = &batteries[batt];
    
    cap_samples[current] = (float) binfo->remaining_cap;
    time_samples[current] = time(NULL);

    if (sample_count == 0) {
	/* we can't do much if we don't have any data . . . */
	current_rate = 0;
    } else if (sample_count < CAP_SAMPLES) {
	/* if we have less than SAMPLES samples so far, we use the first
	 * sample and the current one */
	cdiff = cap_samples[current] - cap_samples[0];
	tdiff = time_samples[current] - time_samples[0];
	current_rate = cdiff/tdiff;
    } else {
	/* if we have more than SAMPLES samples, we use the oldest
	 * current one, which at this point is current + 1. This will
	 * wrap the same way that current will wrap, but one cycle
	 * ahead */
	cdiff = cap_samples[current] - cap_samples[old];
	tdiff = time_samples[current] - time_samples[old];
	current_rate = cdiff/(float)tdiff;
    }
    if (current_rate == 0) 
	rtime = 0;
    else {
	float cap_left = (float)(binfo->last_full_cap - binfo->remaining_cap);
	rtime = (int)(cap_left/(current_rate * 60.0));
    }
    sample_count++, current++, old++;
    if (current >= CAP_SAMPLES)
	current = 0;
    if (old >= CAP_SAMPLES)
	old = 0;

    pdebug("cap charge time rem: %d\n", rtime);
    return rtime;
}

static int calc_charge_time(global_t *globals, int batt)
{
    int ctime = 0;

    globals->rt_mode = check_rt_mode(globals);

    switch(globals->rt_mode) {
    case RT_RATE:
	ctime = calc_charge_time_rate(batt);
	break;
    case RT_CAP:
	ctime = calc_charge_time_cap(batt);
	break;
    }
    return ctime;
}

void acquire_batt_info(global_t *globals, int batt)
{
    battery_t *binfo;
    adapter_t *ap = &globals->adapter;
    
    get_battery_info(globals, batt);
    
    binfo = &batteries[batt];
    
    if (!binfo->present) {
	binfo->percentage = 0;
	binfo->valid = 0;
	binfo->charge_time = 0;
	globals->rtime = 0;
	return;
    }

    binfo->percentage = calc_remaining_percentage(batt);

    /* set the battery's capacity state, based (at present) on some 
     * guesstimated values: more than 75% == HIGH, 25% to 75% MED, and
     * less than 25% is LOW. Less than globals->crit_level is CRIT. */
    if (binfo->percentage == -1)
	binfo->state = BS_ERR;
    if (binfo->percentage < globals->crit_level)
	binfo->state = CRIT;
    else if (binfo->percentage > 75) 
	binfo->state = HIGH;
    else if (binfo->percentage > 25)
	binfo->state = MED;
    else 
	binfo->state = LOW;

    /* we need to /know/ that we've got a valid state for the 
     * globals->power value . . . .*/
    ap->power = get_power_status(globals);

    binfo->charge_time = calc_charge_time(globals, batt);

    /* and finally, we tell anyone who wants to use this information
     * that it's now valid . . .*/
    binfo->valid = 1;
}
	
void acquire_all_batt_info(global_t *globals)
{
    int i;
    
    for(i = 0; i < globals->battery_count; i++)
	acquire_batt_info(globals, i);
}

/*
 * One of the feature requests I've had is for some way to deal with
 * batteries that are too dumb or too b0rken to report a present rate
 * value. The way to do this, obviously, is to record the time that
 * samples were taken and use that information to calculate the rate
 * at which the battery is draining/charging. This still won't help
 * systems where the battery doesn't even report the remaining
 * capacity, but without the present rate or the remaining capacity, I
 * don't think there's /anything/ we can do to work around it.
 *
 * So, what we need to do is provide a way to use a different method
 * to calculate the time remaining. What seems most sensible is to
 * split out the code to calculate it into a seperate function, and
 * then provide multiple implementations . . . 
 */

/*
 * the default implementation - if present rate and remaining capacity
 * are both reported correctly, we use them.
 */
int calc_time_remaining_rate(global_t *globals)
{
    int i;
    int rtime;
    float rcap = 0;
    float rate = 0;
    battery_t *binfo;
    static float rate_samples[SAMPLES];
    static int sample_count = 0;
    static int j = 0;
    static int n = 0;

    /* calculate the time remaining, using the battery's remaining 
     * capacity and the reported burn rate (3.9.3). 
     * For added accuracy, we average the value over the last 
     * SAMPLES number of calls, or for anything less than this we
     * simply report the raw number. */
    /* XXX: this needs to correctly handle the case where 
     * any of the values used is unknown (which we flag using
     * -1). */
    for (i = 0; i < globals->battery_count; i++) {
	binfo = &batteries[i];
	if (binfo->present && binfo->valid) {
	    rcap += (float)binfo->remaining_cap;
	    rate += (float)binfo->present_rate;
	}
    }
    rate_samples[j] = rate;
    j++, sample_count++;
    if (j >= SAMPLES)
	j = 0;
    
    /* for the first SAMPLES number of calls we calculate the
     * average based on sample_count, then we use SAMPLES to
     * calculate the rolling average. */
    
    /* when this fails, n should be equal to SAMPLES. */
    if (sample_count < SAMPLES)
	n++;
    for (i = 0, rate = 0; i < n; i++) {
	/* if any of our samples are invalid, we drop 
	 * straight out, and flag our unknown values. */
	if (rate_samples[i] < 0) {
	    rate = -1;
	    rtime = -1;
	    goto out;
	}
	rate += rate_samples[i];
    }
    rate = rate/(float)n;
    
    if ((rcap < 1) || (rate < 1)) {
	rtime = 0;
	goto out;
    }
    if (rate <= 0) 
	rate = 1;
    /* time remaining in minutes */
    rtime = (int)((rcap/rate) * 60.0);
    if(rtime <= 0) 
	rtime = 0;
 out:
    pdebug("discharge time rem: %d\n", rtime);
    return rtime;
}

/*
 * the alternative implementation - record the time at which each
 * sample was taken, and then use the difference between the latest
 * sample and the one SAMPLES ago to calculate the difference over
 * that time, and from there the rate of change of capacity.
 *
 * XXX: this code sucks, but largely because batteries aren't exactly
 * precision instruments - mine only report with about 70mAH
 * resolution, so they don't report any changes until the difference
 * is 70mAH. This means that calculating the current rate from the
 * remaining capacity is very choppy . . . 
 *
 * To fix this, we should calculate an average over some number of
 * samples at the old end of the set - this would smooth out the
 * transitions. 
 */
int calc_time_remaining_cap(global_t *globals)
{
    static float cap_samples[CAP_SAMPLES];
    static int time_samples[CAP_SAMPLES];
    static int sample_count = 0;
    static int current = 0;
    static int old = 1;
    battery_t *binfo;
    int i;
    int rtime;
    int tdiff;
    float cdiff;
    float cap = 0;
    float current_rate;

    for (i = 0; i < globals->battery_count; i++) {
	binfo = &batteries[i];
	if (binfo->present && binfo->valid)
	    cap += binfo->remaining_cap;
    }
    cap_samples[current] = cap;
    time_samples[current] = time(NULL);

    if (sample_count == 0) {
	/* we can't do much if we don't have any data . . . */
	current_rate = 0;
    } else if (sample_count < CAP_SAMPLES) {
	/* if we have less than SAMPLES samples so far, we use the first
	 * sample and the current one */
	cdiff = cap_samples[0] - cap_samples[current];
	tdiff = time_samples[current] - time_samples[0];
	current_rate = cdiff/tdiff;
    } else {
	/* if we have more than SAMPLES samples, we use the oldest
	 * current one, which at this point is current + 1. This will
	 * wrap the same way that current will wrap, but one cycle
	 * ahead */
	cdiff = cap_samples[old] - cap_samples[current];
	tdiff = time_samples[current] - time_samples[old];
	current_rate = cdiff/tdiff;
    }
    if (current_rate == 0) 
	rtime = 0;
    else
	rtime = (int)(cap_samples[current]/(current_rate * 60.0));

    sample_count++, current++, old++;
    if (current >= CAP_SAMPLES)
	current = 0;
    if (old >= CAP_SAMPLES)
	old = 0;

    pdebug("cap discharge time rem: %d\n", rtime);
    return rtime;
}    

void acquire_global_info(global_t *globals)
{
    adapter_t *ap = &globals->adapter;

    globals->rt_mode = check_rt_mode(globals);

    switch(globals->rt_mode) {
    case RT_RATE:
	globals->rtime = calc_time_remaining_rate(globals);
	break;
    case RT_CAP:
	globals->rtime = calc_time_remaining_cap(globals);
	break;
    }

    /* get the power status.
     * note that this is actually reported seperately from the
     * battery info, under /proc/acpi/ac_adapter/AC/state */
    ap->power = get_power_status(globals);
}

void acquire_all_info(global_t *globals)
{
    acquire_all_batt_info(globals);
    acquire_global_info(globals);
}
