/* Not particularly good interface to hal, for programs that used to use
 * apm.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <upower.h>
#include "apm.h"

#define MAX_RETRIES 3

static UpClient * up = NULL;

struct context {
	int current;
	int needed;
	guint state;
	int percentage;
	gboolean ac;
	int time;
};

static void get_devinfo(gpointer device, gpointer result)
{
	gboolean online;
	gdouble percentage;
	guint state;
	guint kind;
	gint64 time_to_empty;
	gint64 time_to_full;
	struct context *ctx = result;

	g_object_get(G_OBJECT(device), "percentage", &percentage,
		"online", &online,
		"state", &state,
		"kind", &kind,
		"time-to-empty", &time_to_empty,
		"time-to-full", &time_to_full,
		NULL);
	if (kind == UP_DEVICE_KIND_BATTERY) {
		if (ctx->current == ctx->needed) {
			ctx->percentage = (int)percentage;
			ctx->state = state;
			if (time_to_empty) {
				ctx->time = time_to_empty;
			} else {
				ctx->time = -time_to_full;
			}
		}
		ctx->current++;
	} else if (kind == UP_DEVICE_KIND_LINE_POWER) {
		ctx->ac |= online;
	}
}

int upower_supported(void)
{
	if (!up) {
		up = up_client_new();
	}

	if (!up) {
		return 0;
	} else {
#if UP_CHECK_VERSION(0, 99, 8)
		g_autoptr(GPtrArray) devices = up_client_get_devices2(up);
#else
		g_autoptr(GPtrArray) devices = up_client_get_devices(up);
#endif

		if (!devices) {
			return 0;
		} else {
			return 1;
		}
	}
}

/* Fill the passed apm_info struct. */
int upower_read(int battery, apm_info *info)
{
	static int retries = 0;

	if (!up) {
		up = up_client_new();
	}

	if (!up) {
		return -1;
	}

	#if !UP_CHECK_VERSION(0, 9, 99)
	/* Allow a battery that was not present before to appear. */
	up_client_enumerate_devices_sync(up, NULL, NULL);
	#endif

#if UP_CHECK_VERSION(0, 99, 8)
	/*
	 * up_client_get_devices was deprecated and replaced with
	 * up_client_get_devices2 in libupower 0.99.8.
	 */
	g_autoptr(GPtrArray) devices = up_client_get_devices2(up);
#else
	g_autoptr(GPtrArray) devices = up_client_get_devices(up);
#endif

	if (!devices) {
		retries++;
		if (retries < MAX_RETRIES)
			return 0; /* fine immediately after hibernation */
		else
			return -1;
	}

	retries = 0;
	info->battery_flags = 0;
	info->using_minutes = 0;

	struct context ctx = {
		.current = 0,
		.needed = battery - 1,
		.state = UP_DEVICE_STATE_UNKNOWN,
		.percentage = -1,
		.ac = FALSE,
		.time = -1
	};

	g_ptr_array_foreach(devices, &get_devinfo, &ctx);

	info->ac_line_status = ctx.ac;

	/* remaining_time and charge_level.percentage are not a mandatory
	 * keys, so if not present, -1 will be returned */
	info->battery_time = ctx.time;
	info->battery_percentage = ctx.percentage;
	if (ctx.state == UP_DEVICE_STATE_DISCHARGING) {
		info->battery_status = BATTERY_STATUS_CHARGING;
		/* charge_level.warning and charge_level.low are not
		 * required to be available; this is good enough */
		if (info->battery_percentage < 1) {
			info->battery_status = BATTERY_STATUS_CRITICAL;
		} else if (info->battery_percentage < 10) {
			info->battery_status = BATTERY_STATUS_LOW;
		}
	} else if (info->ac_line_status && ctx.state == UP_DEVICE_STATE_CHARGING) {
		info->battery_status = BATTERY_STATUS_CHARGING;
		info->battery_flags = info->battery_flags | BATTERY_FLAGS_CHARGING;
	} else if (info->ac_line_status) {
		/* Must be fully charged. */
		info->battery_status = BATTERY_STATUS_HIGH;
	} else {
		fprintf(stderr, "unknown battery state\n");
	}

	if (ctx.percentage < 0) {
		info->battery_percentage = 0;
		info->battery_time = 0;
		info->battery_status = BATTERY_STATUS_ABSENT;
	}

	return 0;
}
