/*
 * Written by Philip M. White (pmw@qnan.org>
 * I place this code in the public domain.
 * Last update: 2007-10-11 07:08 CST
 */
#include <xosd.h>
#include <stdio.h>
// for open()
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// for read()
#include <unistd.h>
// for strncmp()
#include <string.h>
// for roundf()
#include <math.h>
// for atoi()
#include <stdlib.h>

#define ACPI_INFO_SIZE 1024
#define FILENAME_LEN 256
#define PREFIX_LEN 32

struct integer {
	int value;
	unsigned short is_set;
};

struct BattInfo {
	// for 'charg_st', 1=charging, 0=discharging
	struct integer cap_cur, cap_max, charg_st;
};

/*
 * 'needle' must be null-terminated
 */
int string_find(char *haystack, size_t hs_len, char *needle) {
	size_t needle_len;
	unsigned int i;

	needle_len = strlen(needle);
	for (i=0; i < hs_len-needle_len; i++)
		if (strncmp(haystack+i, needle, needle_len) == 0)
			return i;
	return -1;
}

/*
 * 'prefix' must be null-terminated.
 */
struct integer integer_find(char *haystack, size_t hs_len, char *prefix) {
	int i;
	struct integer retval;

	retval.value = retval.is_set = 0;
	i = string_find(haystack, hs_len, prefix);
	if (i >= 0) {
		// advance beyond the prefix
		i += strlen(prefix);
		// from here, read the integer
		while (haystack[i] >= '0' && haystack[i] <= '9') {
			retval.value *= 10;
			retval.value += (haystack[i]-'0');
			i++;
		}
		retval.is_set = 1;
	}
	return retval;
}

/*
 * 'infopath' must be null-terminated
 */
void battinfo_get(struct BattInfo *batt, char *infopath) {
	int f, acpidatasize, ret;
	char acpidata[ACPI_INFO_SIZE], filename[FILENAME_LEN], prefix[PREFIX_LEN];
	struct integer val;

	// Reading the battery's last maximum capacity
	strcpy(filename, infopath);
	strcat(filename, "/info");
	f = open(filename, O_RDONLY);
	if (f < 1) {
		fprintf(stderr, "error: could not open %s for reading.\n", filename);
		return;
	}
	if ((acpidatasize = read(f, acpidata, sizeof(acpidata))) == -1) {
		fprintf(stderr, "error: could not read any data from %s.\n", filename);
		return;
	}
	close(f);
	val = integer_find(acpidata, acpidatasize, "last full capacity:      ");
	batt->cap_max = val;

	// Reading the battery's current capacity and charging state
	strcpy(filename, infopath);
	strcat(filename, "/state");
	f = open(filename, O_RDONLY);
	if (f < 1) {
		fprintf(stderr, "error: could not open %s for reading.\n", filename);
		return;
	}
	if ((acpidatasize = read(f, acpidata, sizeof(acpidata))) == -1) {
		fprintf(stderr, "error: could not read any data from %s.\n", filename);
		return;
	}
	close(f);
	val = integer_find(acpidata, acpidatasize, "remaining capacity:      ");
	batt->cap_cur = val;

	strcpy(prefix, "charging state:          ");
	ret = string_find(acpidata, acpidatasize, prefix);
	if (ret >= 0) {
		ret += strlen(prefix);
		if (acpidata[ret] == 'c')
			batt->charg_st.value = 1;
		else
			batt->charg_st.value = 0;
		batt->charg_st.is_set = 1;
	}
}

int main (int argc, char *argv[])
{
	struct BattInfo batt;
	char outputstr[100];
	int update_delay, num_refreshes, refresh_counter, percent;

	if (argc != 4) {
		fprintf(stderr, "Syntax: %s <update-delay> <num-refreshes> <path-to-battery-info>\n"
			"Update delay is in seconds, number of refreshes is 0 for infinity.\n", argv[0]);
		return 1;
	}

	update_delay = atoi(argv[1]);
	num_refreshes = atoi(argv[2]);
	if (update_delay < 1 || num_refreshes < 0) {
		fprintf(stderr, "The update delay must be 1 or greater, and the number of refreshes must be non-negative.\n");
		return 2;
	}
	xosd *osd;
	osd = xosd_create(1);	// 1 line of text
	xosd_set_font(osd, "-*-fixed-*-*-*-*-18-*-*-*-*-*-*-*");
	xosd_set_timeout(osd, 2); // in seconds
	xosd_set_colour(osd, "LightBlue");
	xosd_display(osd, 0, XOSD_string, "xosd-battery started");
	xosd_wait_until_no_display(osd);
	xosd_set_horizontal_offset(osd, 330);
	refresh_counter = 0;
	while (num_refreshes == 0 || refresh_counter < num_refreshes) {
		batt.cap_cur.is_set = batt.cap_max.is_set = batt.charg_st.is_set = 0;
	
		battinfo_get(&batt, argv[3]);
		if (batt.cap_cur.is_set && batt.cap_max.is_set && batt.cap_max.value > 0) {
			percent = (int)roundf(100.0 * batt.cap_cur.value / batt.cap_max.value);
		}
		else {
			percent = -1;
		}
		if (percent >= 99) { // nothing useful to show
			sleep(10);
			continue;
		}
		xosd_set_timeout(osd, update_delay);
		xosd_set_shadow_offset(osd, 1);	// in pixels
		if (percent >= 0) {
			sprintf(outputstr, "Battery life remaining: %d%%", percent);
			if (batt.charg_st.is_set) {
				if (batt.charg_st.value == 1)
					strcat(outputstr, " (charging)");
				else
					strcat(outputstr, " (discharging)");
			}
			if (percent >= 50)
				xosd_set_colour(osd, "LawnGreen");
			else if (percent >= 20)
				xosd_set_colour(osd, "Yellow");
			else
				xosd_set_colour(osd, "Red");
		}
		else {
			sprintf(outputstr, "No battery information");
			xosd_set_colour(osd, "White");
		}
		xosd_display(osd, 0, XOSD_string, outputstr);
		xosd_wait_until_no_display(osd);
		if (num_refreshes > 0)
			refresh_counter++;
	}
	xosd_destroy(osd);

	return 0;
}
