/*
 * asapm is the APM (Advanced Power Management) monitor utility for X Windows
 * Copyright (c) 1998  Albert Dorofeev <Albert@mail.dma.be>
 * For the updates see http://bewoner.dma.be/Albert/linux/
 * 
 * This software is distributed under GPL. For details see LICENSE file.
 */


/*
 * These routines are for reading the APM daemon
 * output and parsing it.
 */

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#if defined(__FreeBSD__)
#include <sys/file.h>
#include <sys/ioctl.h>
#include <machine/apm_bios.h>
#endif

#if defined(__NetBSD__)||defined(__OpenBSD__)
#include <sys/ioctl.h>
#include <machine/apmvar.h>
#endif

/* file -> APM device */
extern char apm_device_file[];

#include "state.h"
extern struct apm_state state;

/* Variables for the calculation of the time left */
static double discharge_rate;
static int discharge_rate_known;
static int last_recorded_percent;
static long int apm_checks_passed;

/*
 * An error handler for the problems while reading APM.
 */
void error_handle( int place, const char * message )
{
        int error_num;
        error_num = errno;
        /* if that was an interrupt - quit quietly */
        if (error_num == EINTR) {
                printf("asapm: Interrupted.\n");
		++state.error;
		return;
	}
        printf("asapm: ");
        switch ( place )
        {
        case 1: /* Opening the file APM device */
                switch (error_num)
                {
                case ENOENT :
                        printf("The file %s does not exist. "
                        "Where did that APM of yours go?\n", apm_device_file);
			if (state.fail)
				++state.error;
                        break;
                case EACCES :
                        printf("You do not have permissions to read %s\n",
                                apm_device_file); 
			if (state.fail)
				++state.error;
                        break;  
                default:
                        /* let the user guess what it is */
                        printf("asapm: cannot open %s. Error %d:  %s\n",
                                apm_device_file, errno,
                                sys_errlist[errno]);
			if (state.fail)
				++state.error;
                }               
                break;          
        default: /* all the rest */
                printf("asapm: %s:  Error %d:  %s\n",
                        message, errno, sys_errlist[errno]);
		if (state.fail)
			++state.error;
        }               
}

/*
 * Routine to read the APM information different
 * for Linux, FreeBSD, and NetBSD.
 */
#if defined(__FreeBSD__)
void ReadAPMDevice( )		/* FreeBSD version */
{
	int fd;
	struct apm_info info;
	if ((fd = open(apm_device_file, O_RDONLY)) == -1) {
		error_handle(1, "");
		return;
	}
	if (ioctl(fd, APMIO_GETINFO, &info) == -1) {
		error_handle(4, "");
		return;
	}
	close(fd);

	sprintf(state.driver_version, "?");
	sprintf(state.apm_bios_info_version, "%d.%d", 
		info.ai_major, info.ai_minor);
	if (state.ac_line_status != info.ai_acline) {
		state.ac_line_status = info.ai_acline;
		++state.update;
		if ( state.ac_line_status == AC_ONLINE ) 
			state.flags |= CHANGE_AC_ON;
		else
			state.flags |= CHANGE_AC_OFF;
	}
	if (state.battery_status != info.ai_batt_stat) {
		state.battery_status = info.ai_batt_stat;
		++state.update;
	}
	if (state.percent != info.ai_batt_life) {
		if ( state.percent < info.ai_batt_life )
			state.flags |= CHANGE_POWER_UP;
		else
			state.flags |= CHANGE_POWER_DOWN;
		state.percent = info.ai_batt_life;
		++state.update;
	}
	state.time_left = info.ai_batt_time / 60;
}
#elif defined(__NetBSD__)||defined(__OpenBSD__)
void ReadAPMDevice( )		/* NetBSD version */
{
	int fd;
	struct apm_power_info info;
	if ((fd = open(apm_device_file, O_RDONLY)) == -1) {
		error_handle(1, "");
		return;
	}
	if (ioctl(fd, APM_IOC_GETPOWER, &info) == -1) {
		error_handle(4, "");
		return;
	}
	close(fd);

#ifdef DEBUG
	sprintf(state.driver_version, "?");
	sprintf(state.apm_bios_info_version, "?.?");
#endif
	if (state.ac_line_status != info.ac_state) {
		state.ac_line_status = info.ac_state;
		++state.update;
		if ( state.ac_line_status == APM_AC_ON ) 
			state.flags |= CHANGE_AC_ON;
		else
			state.flags |= CHANGE_AC_OFF;
	}
	if (state.battery_status != info.battery_state) {
		state.battery_status = info.battery_state;
		++state.update;
	}
	if (state.percent != info.battery_life) {
		if ( state.percent < info.battery_life )
			state.flags |= CHANGE_POWER_UP;
		else
			state.flags |= CHANGE_POWER_DOWN;
		state.percent = info.battery_life;
		++state.update;
	}
	state.time_left = info.minutes_left;
}
#else
#ifdef TEST
int countdown = 100;
int timedown = 620;
#endif
void ReadAPMDevice( )		/* Linux version */
{
	int fd;
	int tmp;
	char buf[256];
	unsigned int ac_line_status;
	unsigned int battery_status;
	unsigned int battery_flag;
	int percent;
	int time_units;
	char units[10];

        /* First check if there is an APM */
        if ((fd = open(apm_device_file, O_RDONLY)) == -1) {
                error_handle(1, "");
                return;
        }

	tmp = read(fd, buf, sizeof buf);
	close(fd);
	switch( tmp ) 
	{
	case 0:
		/* end of file */
		break;
	case -1:
		error_handle(2, "read");
		return;
	default:
		switch(sscanf(buf, "%s %s %x %x %x %x %d%% %d %s\n", 
			state.driver_version,
			state.apm_bios_info_version,
			&state.apm_bios_info_flags,
			&ac_line_status,
			&battery_status,
			&battery_flag,
			&percent,
			&time_units,
			units
			)) 
		{
		case 0:
		case -1:
			printf("asapm: invalid input character while "
				"reading %s\n", apm_device_file);
		}
	}
#ifdef TEST
	percent = countdown;
	if (countdown) countdown -= 1;
	time_units = timedown;
	if (timedown) timedown -= 20;
	strcpy(units, "min");
#endif

	if (state.ac_line_status != ac_line_status) {
		state.ac_line_status = ac_line_status;
		++state.update;
		if ( state.ac_line_status == AC_ONLINE ) 
			state.flags |= CHANGE_AC_ON;
		else
			state.flags |= CHANGE_AC_OFF;
	}
	if (state.battery_status != battery_status) {
		state.battery_status = battery_status;
		++state.update;
	}
	if (state.battery_flag != battery_flag) {
		state.battery_flag = battery_flag;
		++state.update;
	}
	if (state.percent != percent) {
		if ( state.percent < percent )
			state.flags |= CHANGE_POWER_UP;
		else
			state.flags |= CHANGE_POWER_DOWN;
		state.percent = percent;
		++state.update;
	}
	if ( strncmp( units, "min", 3 ) )
		time_units /= 60;
	/* we can display maximum 99:59 */
	if ( time_units > 5999 )
		time_units = 5999;
	if (state.time_left != time_units) {
		state.time_left = time_units;
		++state.update;
	}
}
#endif

/*
 * This checks the current status of the APM.
 */
void CheckAPMEvents()
{
	unsigned int tmp;
#ifdef DEBUG
	printf("Checking APM events from device file [%s]\n", apm_device_file);
#endif
	ReadAPMDevice();

#ifdef DEBUG
	printf("apmd %s ", state.driver_version);
	printf("APM BIOS %s ", state.apm_bios_info_version);
	printf("APM flags %#x\n", state.apm_bios_info_flags);
	printf("AC line %#x ", state.ac_line_status);
	printf("Battery status %#x ", state.battery_status);
	printf("Battery flag %#x\n", state.battery_flag);
	printf("Battery charge %d ", state.percent);
	printf("Battery life %d ", state.time_left);
	printf("(estimate %d)\n\n", state.time_estimate);
#endif

	if ( (! state.system_levels) && 
			(state.battery_status != BATTERY_CHARGING) ) {
		if ( state.percent < 0 )
			state.battery_status = BATTERY_UNKNOWN;
		else if ( state.percent <= 20 )
			state.battery_status = BATTERY_CRITICAL;
		else if ( state.percent <= 40 )
			state.battery_status = BATTERY_LOW;
		else if ( state.percent <= 100 )
			state.battery_status = BATTERY_HIGH;
		else
			state.battery_status = BATTERY_UNKNOWN;
	}
	if ( (state.ac_line_status < 0) || 
			(state.ac_line_status > AC_UNKNOWN) )
		state.ac_line_status = AC_UNKNOWN;
	/* The APM may put this to -1 when the battery is gone */
	if ((state.percent > 100) || (state.percent < 0))
		state.percent = -1;

	if ( state.time_left <= 0 ) {
		if ( state.ac_line_status != AC_BATTERY ) {
			state.time_estimate = 0;
			apm_checks_passed = 0;
			last_recorded_percent = state.percent;
		} else {
			++apm_checks_passed;
			if ( last_recorded_percent > state.percent ) {
				discharge_rate = 
				( (double)(last_recorded_percent - 
						state.percent) /
					( (double)(apm_checks_passed * 
					(state.apm_interval/1000000))
						/60 ) );
				if ( discharge_rate > 0 )
					discharge_rate_known = 1;
				apm_checks_passed = 0;
			}
			last_recorded_percent = state.percent;
			if ( discharge_rate_known && (discharge_rate > 0)) {
				tmp = state.percent / discharge_rate;
				/* we can display maximum 99:59 */
				if ( tmp > 5999 )
					tmp = 5999;
				if ( tmp != state.time_estimate ) {
					state.time_estimate = tmp;
					++state.update;
				}
			}
		}
	}
}

