/*
 *    wmbsdbatt - A dockapp to monitor ACPI battery status on FreeBSD
 *    Copyright (C) 2003  Lukas Ertl <l.ertl@univie.ac.at>

 *    Based on work by Florian Krohs <krohs@uni.de>
 *    Copyright (C) 2003  Florian Krohs <krohs@uni.de>
 *    Based on work by Thomas Nemeth <tnemeth@free.fr>
 *    Copyright (C) 2002  Thomas Nemeth <tnemeth@free.fr>
 *    and on work by Seiichi SATO <ssato@sh.rim.or.jp>
 *    Copyright (C) 2001,2002  Seiichi SATO <ssato@sh.rim.or.jp>
 *    and on work by Mark Staggs <me@markstaggs.net>
 *    Copyright (C) 2002  Mark Staggs <me@markstaggs.net>

 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.

 *    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.
 *
 * $Id: wmbsdbatt.c,v 1.1 2003/12/23 18:27:34 le Exp $
 */
#include <errno.h>
#include <err.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sysctl.h>

#include <X11/Xlib.h>
#include <X11/xpm.h>
#include <X11/extensions/shape.h>

#include "dockapp.h"
#include "backlight_on.xpm"
#include "backlight_off.xpm"
#include "parts.xpm"

#define PACKAGE		"wmbsdbatt"
#define SIZE		58
#define WINDOWED_BG	". c #AEAAAE"
#define TOGGLESPEED	2
#define UPDATE_INTERVAL	5
#define RATE		0
#define TEMP		1

#define DISCHARGING	0x0001
#define CHARGING	0x0002
#define CRITICAL	0x0004
#define UNKNOWN		0

struct acpiinfo {
	int acline;
	int battery_status;
	int battery_life;
	int battery_time;
	int temperature;
	int low;
#if 0
	int capacity;
	int rate;
#endif
};

struct acpiinfo acpi_info;
static char *sysctl_battery_state = "hw.acpi.battery.state";
static char *sysctl_battery_life = "hw.acpi.battery.life";
static char *sysctl_battery_time = "hw.acpi.battery.time";
static char *sysctl_temperature = "hw.acpi.thermal.tz0.temperature";
static char *sysctl_acline = "hw.acpi.acline";

typedef enum { LIGHTOFF, LIGHTON } light;

Pixmap pixmap;
Pixmap backdrop_on;
Pixmap backdrop_off;
Pixmap parts;
Pixmap mask;
static char	*display_name     = "";
static char	light_color[256]  = "";	/* back-light color */
static unsigned update_interval   = UPDATE_INTERVAL;
static unsigned alarm_level_temp  = 70;
static int 	animationspeed    = 500;
static light    backlight         = LIGHTOFF;
int		count;
int		blink_pos = 0;
#if 0
static int 	mode		  = TEMP;
#endif

/* prototypes */
void	update(void);
void	switch_light(void);
void	draw_remaining_time(void);
void	draw_batt(void);
void	draw_low(void);
void	draw_temp(void);
void	draw_statusdigit(void);
void	draw_pcgraph(void);
void	blink_batt(void);
void	draw_all(void);
int	init_stats(void);
int	acpi_exists(void);
#if 0
void	draw_rate(void);
#endif

int
main(int argc, char **argv)
{
	int charging, ncolor, show;
	long timeout;
	long animation_timeout, update_timeout;
	struct sigaction sa;
	XEvent event;
	XpmColorSymbol colors[2] = { {"Back0", NULL, 0}, {"Back1", NULL, 0} };

	ncolor = show = 0;
	update_timeout = update_interval * 1000;
	animation_timeout = animationspeed;

	sa.sa_handler = SIG_IGN;
	sa.sa_flags = SA_NOCLDWAIT;

	sigemptyset(&sa.sa_mask);
	sigaction(SIGCHLD, &sa, NULL);
	
	/* Check for ACPI support */
	if (!acpi_exists()) {
		fprintf(stderr, "Unable to access ACPI info\n");
		exit(1);
	}

	dockapp_open_window(display_name, PACKAGE, SIZE, SIZE, argc, argv);
	dockapp_set_eventmask(ButtonPressMask);
	
	if (strcmp(light_color,"")) {
		colors[0].pixel = dockapp_getcolor(light_color);
		colors[1].pixel = dockapp_blendedcolor(light_color, -24, -24,
		    -24, 1.0);
		ncolor = 2;
	}
	
	/* change raw xpm data to pixmap */
	if (dockapp_iswindowed)
		backlight_on_xpm[1] = backlight_off_xpm[1] = WINDOWED_BG;
	
	if (!dockapp_xpm2pixmap(backlight_on_xpm, &backdrop_on, &mask, colors,
	    ncolor)) {
		fprintf(stderr,
		    "Error initializing backlit background image.\n");
		exit(1);
	}
	if (!dockapp_xpm2pixmap(backlight_off_xpm, &backdrop_off, NULL, NULL, 0)) {
		fprintf(stderr, "Error initializing background image.\n");
		exit(1);
	}
	if (!dockapp_xpm2pixmap(parts_xpm, &parts, NULL, colors, ncolor)) {
		fprintf(stderr, "Error initializing parts image.\n");
		exit(1);
	}
	
	/* shape window */
	if (!dockapp_iswindowed)
		dockapp_setshape(mask, 0, 0);
	if (mask)
		XFreePixmap(display, mask);
	
	/* pixmap : draw area */
	pixmap = dockapp_XCreatePixmap(SIZE, SIZE);
	
	/* Initialize pixmap */
	if (backlight == LIGHTON) 
		dockapp_copyarea(backdrop_on, pixmap, 0, 0, SIZE, SIZE, 0, 0);
	else
		dockapp_copyarea(backdrop_off, pixmap, 0, 0, SIZE, SIZE, 0, 0);
	
	dockapp_set_background(pixmap);
	dockapp_show();
	update();

	/* Main loop */
	while (1) {
		if (acpi_info.battery_status == CHARGING)
			charging = 1;
		else
			charging = 0;

		timeout = update_timeout;

		if (dockapp_nextevent_or_timeout(&event, timeout)) {
			/* Next Event */
			switch (event.type) {
			case ButtonPress:
				switch (event.xbutton.button) {
				case 1:
					switch_light();
					draw_all();
					break;
#if 0
				case 3:
					mode = !mode;
					draw_all();
					break;
#endif
				default:
					break;
				}
				break;
			default:
				break;
			}
		} else {
			/* Time Out */
			update_timeout -= timeout;
			animation_timeout -= timeout;
			if (animation_timeout <= 0) {
				animation_timeout = animationspeed;	
				if (charging) {
					show = 1;
				}
			}
			if (update_timeout <= 0) {
				update();
				show = 1;
				update_timeout = update_interval * 1000;
			}            		
			if (show) {
				/* show */
				draw_all();
				if (charging) {
					blink_pos--;
				}
				dockapp_copy2window(pixmap);
				show = 0;
			}
		}
	}

	return 0;
}


int
init_stats(void)
{
	u_long addr;
	size_t len;

	len = sizeof(addr);

	if (sysctlbyname(sysctl_battery_life, &addr, &len, NULL, 0) == -1)
		err(1, "sysctlbyname(\"%s\")", sysctl_battery_life);
	acpi_info.battery_life = addr;

	if (sysctlbyname(sysctl_battery_time, &addr, &len, NULL, 0) == -1)
		err(1, "sysctlbyname(\"%s\")", sysctl_battery_time);
	
	if (addr == -1)
		acpi_info.battery_time = 0;
	else
		acpi_info.battery_time = addr;

	if (sysctlbyname(sysctl_battery_state, &addr, &len, NULL, 0) == -1)
		err(1, "sysctlbyname(\"%s\")", sysctl_battery_state);
	acpi_info.battery_status = addr;
	
	if (sysctlbyname(sysctl_temperature, &addr, &len, NULL, 0) == -1)
		err(1, "sysctlbyname(\"%s\")", sysctl_temperature);

	/* ACPI temperature comes is tenth of Kelvin. */
	addr /= 10;
	addr -= 273;
	acpi_info.temperature = addr;

	if (sysctlbyname(sysctl_acline, &addr, &len, NULL, 0) == -1)
		err(1, "sysctlbyname(\"%s\")", sysctl_acline);
	acpi_info.acline = addr;

	/* XXX: Is there a better way to say "battery is low"? */
	if (acpi_info.acline == 0 && acpi_info.battery_life < 5)
		acpi_info.low = 1;
	else
		acpi_info.low = 0;

	return 0;
}

int
acpi_exists(void)
{
	/* For now. */
	return 1;
}


/* called by timer */
void
update(void)
{
	static light pre_backlight;
	static int in_alarm_mode = 0;
	
	/* get current battery usage in percent */
	init_stats();
	
	/* alarm mode */
	if (acpi_info.low || (acpi_info.temperature > alarm_level_temp)) {
		if (!in_alarm_mode) {
			in_alarm_mode = 1;
			pre_backlight = backlight;
		}
		if (backlight != pre_backlight) {
			switch_light();
			return;
		}
	} else if (in_alarm_mode) {
		in_alarm_mode = 0;
		if (backlight != pre_backlight) {
			switch_light();
			return;
		}
	}
	draw_all();
}

/* called when mouse button pressed */
void
switch_light(void)
{
	switch (backlight) {
	case LIGHTOFF:
		backlight = LIGHTON;
		dockapp_copyarea(backdrop_on, pixmap, 0, 0, 58, 58, 0, 0);
		break;
	case LIGHTON:
		backlight = LIGHTOFF;
		dockapp_copyarea(backdrop_off, pixmap, 0, 0, 58, 58, 0, 0);
		break;
	}
	
}

void
draw_all(void)
{
	/* all clear */
	if (backlight == LIGHTON) 
		dockapp_copyarea(backdrop_on, pixmap, 0, 0, 58, 58, 0, 0);
	else 
		dockapp_copyarea(backdrop_off, pixmap, 0, 0, 58, 58, 0, 0);
	/* draw digit */
	draw_remaining_time();
	draw_statusdigit();
	draw_pcgraph();

#if 0
	if (mode == RATE)
		draw_rate();
	else if (mode == TEMP)
		draw_temp();
#endif
	draw_temp();
	
	if (acpi_info.battery_status == CHARGING)
		blink_batt();

	if (acpi_info.low)
		draw_low();
	
	draw_batt();

	/* show */
	dockapp_copy2window(pixmap);
}

void
draw_batt(void)
{
	int y;

	if (backlight == LIGHTON)
		y = 28;
	else
		y = 0;

	if (acpi_info.battery_status == DISCHARGING)
	      	dockapp_copyarea(parts, pixmap, 33 + y , 63, 9, 5, 16, 39);
}

void
draw_remaining_time(void)
{
	int hrs_left, min_left, y;

	hrs_left = acpi_info.battery_time / 60;
	min_left = acpi_info.battery_time % 60;

	if (backlight == LIGHTON)
		y = 20;
	else
		y = 0;

	if (acpi_info.acline == 1 && !(acpi_info.battery_status == CHARGING)) {
		dockapp_copyarea(parts, pixmap, 0, 68 + 68 + y, 10, 20, 17, 5);
		dockapp_copyarea(parts, pixmap, 10, 68 + 68 + y, 10, 20, 32, 5);
	} else {
		dockapp_copyarea(parts, pixmap, (hrs_left / 10) * 10, 68 + y,
		    10, 20, 5, 5);
		dockapp_copyarea(parts, pixmap, (hrs_left % 10) * 10, 68 + y,
		    10, 20, 17, 5);
		dockapp_copyarea(parts, pixmap, (min_left / 10) * 10, 68 + y,
		    10, 20, 32, 5);
		dockapp_copyarea(parts, pixmap, (min_left % 10) * 10, 68 + y,
		    10, 20, 44, 5);
	}

}

#if 0
void
draw_rate(void)
{
	int light_offset;
	long rate = acpi_info.rate;

	if (backlight == LIGHTON)
		light_offset = 50;
	else
		light_offset = 0;
	
	dockapp_copyarea(parts, pixmap, (rate/10000)*5 + light_offset, 40, 5,
	     9, 5, 46);
	dockapp_copyarea(parts, pixmap, ((rate/1000)%10)*5 + light_offset, 40,
	     5, 9, 11, 46);
	dockapp_copyarea(parts, pixmap, ((rate/100)%10)*5 + light_offset, 40,
	     5, 9, 17, 46);
	dockapp_copyarea(parts, pixmap, ((rate/10)%10)*5 + light_offset, 40,
	     5, 9, 23, 46);
	dockapp_copyarea(parts, pixmap, (rate%10)*5 + light_offset, 40, 5, 9,
	     29, 46);
	
	dockapp_copyarea(parts, pixmap, 0 + light_offset, 49, 5, 9, 36, 46); //m
	dockapp_copyarea(parts, pixmap, 5 + light_offset, 49, 5, 9, 42, 46); //W
}
#endif

void
draw_temp(void)
{
	int light_offset=0;
	int temp = acpi_info.temperature;

	if (backlight == LIGHTON)
		light_offset = 50;
	else
		light_offset = 0;
	
	if (temp < 0 || temp > 99)
		temp = 0;

	dockapp_copyarea(parts, pixmap, (temp / 10) * 5 + light_offset, 40, 5,
	    9, 23, 46);
	dockapp_copyarea(parts, pixmap, (temp % 10) * 5 + light_offset, 40, 5,
	    9, 29, 46);
	
	/* '°C'. */
	dockapp_copyarea(parts, pixmap, 10 + light_offset, 49, 5, 9, 36, 46);
	dockapp_copyarea(parts, pixmap, 15 + light_offset, 49, 5, 9, 42, 46);

}

void
draw_statusdigit(void)
{
	int light_offset;

	if (backlight == LIGHTON)
		light_offset = 28;
	else
		light_offset = 0;

	if (acpi_info.acline == 1)
		dockapp_copyarea(parts, pixmap, 33 + light_offset, 58, 9, 5,
		    5, 39);
}

void
draw_pcgraph(void)
{
	int width;
	int light_offset;
	int percent;

	if (backlight == LIGHTON)
		light_offset = 5;
	else
		light_offset = 0;

	percent = acpi_info.battery_life;

	width = (percent * 32) / 100;	
	dockapp_copyarea(parts, pixmap, 0, 58 + light_offset, width, 5, 5, 26);

	/* Don't display leading zero. */
	if (percent == 100)
		dockapp_copyarea(parts, pixmap, 4 * (percent / 100),
		    126 + light_offset, 3, 5, 38, 26);

	/* Don't display leading zero. */
	if (percent > 9)
		dockapp_copyarea(parts, pixmap, 4 * ((percent % 100) / 10),
		    126 + light_offset, 3, 5, 42, 26);

	dockapp_copyarea(parts, pixmap, 4 * (percent % 10), 126 + light_offset,
	    3, 5, 46, 26);
}

void
blink_batt(void)
{
	int light_offset = 0;

	if (backlight == LIGHTON)
		light_offset = 50;
	else
		light_offset = 0;

	blink_pos = (blink_pos + 1) % 5;
	if (acpi_info.battery_status == CHARGING)
		dockapp_copyarea(parts, pixmap, blink_pos * 9 + light_offset,
		    117, 9, 5, 16, 39);
}

void
draw_low(void)
{
	int y;

	if (backlight == LIGHTON)
		y = 28;
	else
		y = 0;

	dockapp_copyarea(parts, pixmap, 42 + y, 58, 17, 7, 38, 38);
}