/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2008 David Zeuthen <davidz@redhat.com>
 * Copyright (C) 2008 Richard Hughes <richard@hughsie.com>
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <string.h>
#include <stdlib.h>

#include <glib.h>
#include <glib/gi18n-lib.h>
#include <glib-object.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>

#include "up-config.h"
#include "up-polkit.h"
#include "up-device-list.h"
#include "up-device.h"
#include "up-backend.h"
#include "up-daemon.h"

#include "up-daemon-glue.h"
#include "up-marshal.h"

enum
{
	PROP_0,
	PROP_DAEMON_VERSION,
	PROP_CAN_SUSPEND,
	PROP_CAN_HIBERNATE,
	PROP_ON_BATTERY,
	PROP_ON_LOW_BATTERY,
	PROP_LID_IS_CLOSED,
	PROP_LID_IS_PRESENT,
	PROP_LID_FORCE_SLEEP,
	PROP_IS_DOCKED,
	PROP_LAST
};

enum
{
	SIGNAL_DEVICE_ADDED,
	SIGNAL_DEVICE_REMOVED,
	SIGNAL_DEVICE_CHANGED,
	SIGNAL_CHANGED,
	SIGNAL_SLEEPING,
	SIGNAL_RESUMING,
	SIGNAL_NOTIFY_SLEEP,
	SIGNAL_NOTIFY_RESUME,
	SIGNAL_LAST,
};

static guint signals[SIGNAL_LAST] = { 0 };

struct UpDaemonPrivate
{
	DBusGConnection		*connection;
	DBusGProxy		*proxy;
	UpConfig		*config;
	UpPolkit		*polkit;
	UpBackend		*backend;
	UpDeviceList		*power_devices;
	gboolean		 on_battery;
	gboolean		 on_low_battery;
	gboolean		 lid_is_closed;
	gboolean		 lid_is_present;
	gboolean		 lid_force_sleep;
	gboolean		 is_docked;
	gboolean		 kernel_can_suspend;
	gboolean		 kernel_can_hibernate;
	gboolean		 hibernate_has_encrypted_swap;
	gboolean		 during_coldplug;
	gboolean		 sent_sleeping_signal;
	guint			 battery_poll_id;
	guint			 battery_poll_count;
	GTimer			*about_to_sleep_timer;
	guint			 about_to_sleep_id;
	guint			 conf_sleep_timeout;
	gboolean		 conf_allow_hibernate_encrypted_swap;
	gboolean		 conf_run_powersave_command;
	const gchar		*sleep_kind;
};

static void	up_daemon_finalize		(GObject	*object);
static gboolean	up_daemon_get_on_battery_local	(UpDaemon	*daemon);
static gboolean	up_daemon_get_on_low_battery_local (UpDaemon	*daemon);
static gboolean	up_daemon_get_on_ac_local 	(UpDaemon	*daemon);

G_DEFINE_TYPE (UpDaemon, up_daemon, G_TYPE_OBJECT)

#define UP_DAEMON_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), UP_TYPE_DAEMON, UpDaemonPrivate))

/* if using more memory compared to usable swap, disable hibernate */
/* Native Linux suspend-to-disk does not use compression, and needs 2 KB of
 * page meta information for each MB of active memory. Add some error margin
 * here, though. */
#define UP_DAEMON_SWAP_WATERLINE 			98.f /* % */

/* refresh all the devices after this much time when on-battery has changed */
#define UP_DAEMON_ON_BATTERY_REFRESH_DEVICES_DELAY	1 /* seconds */
#define UP_DAEMON_POLL_BATTERY_NUMBER_TIMES		5

/* D-BUS to connect to. Can be set to session bus for testing */
static DBusBusType daemon_bus_type = DBUS_BUS_SYSTEM;

DBusBusType
up_daemon_get_bus_type (void)
{
	return daemon_bus_type;
}

void
up_daemon_set_bus_type (DBusBusType type)
{
	daemon_bus_type = type;
}

/**
 * up_daemon_get_on_battery_local:
 *
 * As soon as _any_ battery goes discharging, this is true
 **/
static gboolean
up_daemon_get_on_battery_local (UpDaemon *daemon)
{
	guint i;
	gboolean ret;
	gboolean result = FALSE;
	gboolean on_battery;
	UpDevice *device;
	GPtrArray *array;

	/* ask each device */
	array = up_device_list_get_array (daemon->priv->power_devices);
	for (i=0; i<array->len; i++) {
		device = (UpDevice *) g_ptr_array_index (array, i);
		ret = up_device_get_on_battery (device, &on_battery);
		if (ret && on_battery) {
			result = TRUE;
			break;
		}
	}
	g_ptr_array_unref (array);
	return result;
}

/**
 * up_daemon_get_number_devices_of_type:
 **/
guint
up_daemon_get_number_devices_of_type (UpDaemon *daemon, UpDeviceKind type)
{
	guint i;
	UpDevice *device;
	GPtrArray *array;
	UpDeviceKind type_tmp;
	guint count = 0;

	/* ask each device */
	array = up_device_list_get_array (daemon->priv->power_devices);
	for (i=0; i<array->len; i++) {
		device = (UpDevice *) g_ptr_array_index (array, i);
		g_object_get (device,
			      "type", &type_tmp,
			      NULL);
		if (type == type_tmp)
			count++;
	}
	g_ptr_array_unref (array);
	return count;
}

/**
 * up_daemon_get_on_low_battery_local:
 *
 * As soon as _all_ batteries are low, this is true
 **/
static gboolean
up_daemon_get_on_low_battery_local (UpDaemon *daemon)
{
	guint i;
	gboolean ret;
	gboolean result = TRUE;
	gboolean on_low_battery;
	UpDevice *device;
	GPtrArray *array;

	/* ask each device */
	array = up_device_list_get_array (daemon->priv->power_devices);
	for (i=0; i<array->len; i++) {
		device = (UpDevice *) g_ptr_array_index (array, i);
		ret = up_device_get_low_battery (device, &on_low_battery);
		if (ret && !on_low_battery) {
			result = FALSE;
			break;
		}
	}
	g_ptr_array_unref (array);
	return result;
}

/**
 * up_daemon_get_on_ac_local:
 *
 * As soon as _any_ ac supply goes online, this is true
 **/
static gboolean
up_daemon_get_on_ac_local (UpDaemon *daemon)
{
	guint i;
	gboolean ret;
	gboolean result = FALSE;
	gboolean online;
	UpDevice *device;
	GPtrArray *array;

	/* ask each device */
	array = up_device_list_get_array (daemon->priv->power_devices);
	for (i=0; i<array->len; i++) {
		device = (UpDevice *) g_ptr_array_index (array, i);
		ret = up_device_get_online (device, &online);
		if (ret && online) {
			result = TRUE;
			break;
		}
	}
	g_ptr_array_unref (array);
	return result;
}

/**
 * up_daemon_set_powersave:
 **/
static gboolean
up_daemon_set_powersave (UpDaemon *daemon, gboolean powersave)
{
	gboolean ret = FALSE;
	const gchar *command;
	GError *error = NULL;

	/* run script */
	command = up_backend_get_powersave_command (daemon->priv->backend, powersave);
	if (command == NULL) {
		g_warning ("no powersave command set");
		goto out;
	}
	g_debug ("excuting command: %s", command);
	ret = g_spawn_command_line_async (command, &error);
	if (!ret) {
		g_warning ("failed to run script: %s", error->message);
		g_error_free (error);
		goto out;
	}
out:
	return ret;
}

/**
 * up_daemon_refresh_battery_devices:
 **/
static gboolean
up_daemon_refresh_battery_devices (UpDaemon *daemon)
{
	guint i;
	GPtrArray *array;
	UpDevice *device;
	UpDeviceKind type;

	/* refresh all devices in array */
	array = up_device_list_get_array (daemon->priv->power_devices);
	for (i=0; i<array->len; i++) {
		device = (UpDevice *) g_ptr_array_index (array, i);
		/* only refresh battery devices */
		g_object_get (device,
			      "type", &type,
			      NULL);
		if (type == UP_DEVICE_KIND_BATTERY)
			up_device_refresh_internal (device);
	}
	g_ptr_array_unref (array);

	return TRUE;
}

/**
 * up_daemon_enumerate_devices:
 **/
gboolean
up_daemon_enumerate_devices (UpDaemon *daemon, DBusGMethodInvocation *context)
{
	guint i;
	GPtrArray *array;
	GPtrArray *object_paths;
	UpDevice *device;

	/* build a pointer array of the object paths */
	object_paths = g_ptr_array_new_with_free_func (g_free);
	array = up_device_list_get_array (daemon->priv->power_devices);
	for (i=0; i<array->len; i++) {
		device = (UpDevice *) g_ptr_array_index (array, i);
		g_ptr_array_add (object_paths, g_strdup (up_device_get_object_path (device)));
	}
	g_ptr_array_unref (array);

	/* return it on the bus */
	dbus_g_method_return (context, object_paths);

	/* free */
	g_ptr_array_unref (object_paths);
	return TRUE;
}

/**
 * up_daemon_about_to_sleep:
 **/
gboolean
up_daemon_about_to_sleep (UpDaemon *daemon,
			  const gchar *sleep_kind,
			  DBusGMethodInvocation *context)
{
	PolkitSubject *subject = NULL;
	GError *error;
	UpDaemonPrivate *priv = daemon->priv;

	/* already requested */
	if (priv->about_to_sleep_id != 0) {
		error = g_error_new (UP_DAEMON_ERROR,
				     UP_DAEMON_ERROR_GENERAL,
				     "Sleep has already been requested and is pending");
		dbus_g_method_return_error (context, error);
		g_error_free (error);
		goto out;
	}

	subject = up_polkit_get_subject (priv->polkit, context);
	if (subject == NULL)
		goto out;

	/* TODO: use another PolicyKit context? */
	if (!up_polkit_check_auth (priv->polkit, subject, "org.freedesktop.upower.suspend", context))
		goto out;

	/* we've told the clients we're going down */
	g_debug ("emitting sleeping");
	g_signal_emit (daemon, signals[SIGNAL_SLEEPING], 0);
	g_signal_emit (daemon, signals[SIGNAL_NOTIFY_SLEEP], 0,
		       sleep_kind);
	g_timer_start (priv->about_to_sleep_timer);
	daemon->priv->sent_sleeping_signal = TRUE;

	dbus_g_method_return (context, NULL);
out:
	if (subject != NULL)
		g_object_unref (subject);
	return TRUE;
}

/* temp object for deferred callback */
typedef struct {
	UpDaemon		*daemon;
	DBusGMethodInvocation	*context;
	gchar			*command;
	gulong			 handler;
} UpDaemonDeferredSleep;

static void
emit_resuming (UpDaemonDeferredSleep *sleep)
{
	UpDaemon *daemon = sleep->daemon;
	UpDaemonPrivate *priv = daemon->priv;

	/* emit signal for session components */
	g_debug ("emitting resuming");
	g_signal_emit (daemon, signals[SIGNAL_RESUMING], 0);
	g_signal_emit (daemon, signals[SIGNAL_NOTIFY_RESUME], 0,
		       priv->sleep_kind);

	/* reset the about-to-sleep logic */
	g_timer_reset (priv->about_to_sleep_timer);
	g_timer_stop (priv->about_to_sleep_timer);

	/* actually return from the DBus call now */
	dbus_g_method_return (sleep->context, NULL);

	/* clear timer */
	priv->about_to_sleep_id = 0;
	priv->sent_sleeping_signal = FALSE;

	/* delete temp object */
	if (sleep->handler)
		g_signal_handler_disconnect (priv->backend, sleep->handler);
	g_object_unref (sleep->daemon);
	g_free (sleep->command);
	g_free (sleep);
}

/**
 * up_daemon_deferred_sleep_cb:
 **/
static gboolean
up_daemon_deferred_sleep_cb (UpDaemonDeferredSleep *sleep)
{
	GError *error;
	GError *error_local = NULL;
	gchar *stdout = NULL;
	gchar *stderr = NULL;
	gboolean ret;
	UpDaemon *daemon = sleep->daemon;
	UpDaemonPrivate *priv = daemon->priv;

	if (up_backend_emits_resuming (priv->backend)) {
		sleep->handler = g_signal_connect_swapped (priv->backend, "resuming",
							   G_CALLBACK (emit_resuming), sleep);
	}

	/* run the command */
	g_debug ("Running %s", sleep->command);
	ret = g_spawn_command_line_sync (sleep->command, &stdout, &stderr, NULL, &error_local);
	if (!ret) {
		error = g_error_new (UP_DAEMON_ERROR,
				     UP_DAEMON_ERROR_GENERAL,
				     "Failed to spawn: %s, stdout:%s, stderr:%s", error_local->message, stdout, stderr);
		g_error_free (error_local);
		dbus_g_method_return_error (sleep->context, error);
		g_error_free (error);
		goto out;
	}

	if (!up_backend_emits_resuming (priv->backend))
		emit_resuming (sleep);

out:
	g_free (stdout);
	g_free (stderr);

	return FALSE;
}

/**
 * up_daemon_deferred_sleep:
 **/
static void
up_daemon_deferred_sleep (UpDaemon *daemon, const gchar *command, DBusGMethodInvocation *context)
{
	UpDaemonDeferredSleep *sleep;
	UpDaemonPrivate *priv = daemon->priv;
	gfloat elapsed;

	/* create callback object */
	sleep = g_new0 (UpDaemonDeferredSleep, 1);
	sleep->daemon = g_object_ref (daemon);
	sleep->context = context;
	sleep->command = g_strdup (command);

	/* we didn't use AboutToSleep() so send the signal for clients now */
	if (!priv->sent_sleeping_signal) {
		g_debug ("no AboutToSleep(), so emitting ::Sleeping()");
		g_signal_emit (daemon, signals[SIGNAL_SLEEPING], 0);
		g_signal_emit (daemon, signals[SIGNAL_NOTIFY_SLEEP], 0,
			       priv->sleep_kind);
		priv->about_to_sleep_id = g_timeout_add (priv->conf_sleep_timeout,
							 (GSourceFunc) up_daemon_deferred_sleep_cb, sleep);
#if GLIB_CHECK_VERSION(2,25,8)
		g_source_set_name_by_id (priv->about_to_sleep_id, "[UpDaemon] about-to-sleep no signal");
#endif
		return;
	}

	/* about to sleep */
	elapsed = 1000.0f * g_timer_elapsed (priv->about_to_sleep_timer, NULL);
	g_debug ("between AboutToSleep() and %s was %fms", sleep->command, elapsed);
	if (elapsed < priv->conf_sleep_timeout) {
		/* we have to wait for the difference in time */
		priv->about_to_sleep_id = g_timeout_add (priv->conf_sleep_timeout - elapsed,
							 (GSourceFunc) up_daemon_deferred_sleep_cb, sleep);
#if GLIB_CHECK_VERSION(2,25,8)
		g_source_set_name_by_id (priv->about_to_sleep_id, "[UpDaemon] about-to-sleep less");
#endif
	} else {
		/* we can do this straight away */
		priv->about_to_sleep_id = g_idle_add ((GSourceFunc) up_daemon_deferred_sleep_cb, sleep);
#if GLIB_CHECK_VERSION(2,25,8)
		g_source_set_name_by_id (priv->about_to_sleep_id, "[UpDaemon] about-to-sleep more");
#endif
	}
}

/**
 * up_daemon_suspend:
 **/
gboolean
up_daemon_suspend (UpDaemon *daemon, DBusGMethodInvocation *context)
{
	GError *error;
	PolkitSubject *subject = NULL;
	const gchar *command;
	UpDaemonPrivate *priv = daemon->priv;

	/* no kernel support */
	if (!priv->kernel_can_suspend) {
		error = g_error_new (UP_DAEMON_ERROR,
				     UP_DAEMON_ERROR_GENERAL,
				     "No kernel support");
		dbus_g_method_return_error (context, error);
		g_error_free (error);
		goto out;
	}

	subject = up_polkit_get_subject (priv->polkit, context);
	if (subject == NULL)
		goto out;

	if (!up_polkit_check_auth (priv->polkit, subject, "org.freedesktop.upower.suspend", context))
		goto out;

	/* already requested */
	if (priv->about_to_sleep_id != 0) {
		error = g_error_new (UP_DAEMON_ERROR,
				     UP_DAEMON_ERROR_GENERAL,
				     "Sleep has already been requested and is pending");
		dbus_g_method_return_error (context, error);
		g_error_free (error);
		goto out;
	}

	/* do this deferred action */
	priv->sleep_kind = "suspend";
	command = up_backend_get_suspend_command (priv->backend);
	up_daemon_deferred_sleep (daemon, command, context);
out:
	if (subject != NULL)
		g_object_unref (subject);
	return TRUE;
}

/**
 * up_daemon_suspend_allowed:
 **/
gboolean
up_daemon_suspend_allowed (UpDaemon *daemon, DBusGMethodInvocation *context)
{
	gboolean ret;
	PolkitSubject *subject = NULL;
	UpDaemonPrivate *priv = daemon->priv;
	GError *error;

	subject = up_polkit_get_subject (priv->polkit, context);
	if (subject == NULL)
		goto out;

	error = NULL;
	ret = up_polkit_is_allowed (priv->polkit, subject, "org.freedesktop.upower.suspend", &error);
	if (error) {
		dbus_g_method_return_error (context, error);
		g_error_free (error);
	}
	else {
		dbus_g_method_return (context, ret);
	}

out:
	if (subject != NULL)
		g_object_unref (subject);
	return TRUE;
}

/** 
 * up_daemon_check_hibernate_swap:
 *
 * Check current memory usage whether we have enough swap space for
 * hibernate.
 **/
static gboolean
up_daemon_check_hibernate_swap (UpDaemon *daemon)
{
	gfloat waterline;

	if (daemon->priv->kernel_can_hibernate) {
		waterline = up_backend_get_used_swap (daemon->priv->backend);
		if (waterline < UP_DAEMON_SWAP_WATERLINE) {
			g_debug ("enough swap to for hibernate");
			return TRUE;
		} else {
			g_debug ("not enough swap to hibernate");
			return FALSE;
		}
	}

	return FALSE;
}

/**
 * up_daemon_hibernate:
 **/
gboolean
up_daemon_hibernate (UpDaemon *daemon, DBusGMethodInvocation *context)
{
	GError *error;
	PolkitSubject *subject = NULL;
	const gchar *command;
	UpDaemonPrivate *priv = daemon->priv;

	/* no kernel support */
	if (!priv->kernel_can_hibernate) {
		error = g_error_new (UP_DAEMON_ERROR,
				     UP_DAEMON_ERROR_GENERAL,
				     "No kernel support");
		dbus_g_method_return_error (context, error);
		g_error_free (error);
		goto out;
	}

	/* enough swap? */
	if (!up_daemon_check_hibernate_swap (daemon)) {
		error = g_error_new (UP_DAEMON_ERROR,
				     UP_DAEMON_ERROR_GENERAL,
				     "Not enough swap space");
		dbus_g_method_return_error (context, error);
		g_error_free (error);
		goto out;
	}

	/* encrypted swap and no override? */
	if (priv->hibernate_has_encrypted_swap &&
	    !priv->conf_allow_hibernate_encrypted_swap) {
		error = g_error_new (UP_DAEMON_ERROR,
				     UP_DAEMON_ERROR_GENERAL,
				     "Swap space is encrypted, use AllowHibernateEncryptedSwap to override");
		dbus_g_method_return_error (context, error);
		g_error_free (error);
		goto out;
	}

	subject = up_polkit_get_subject (priv->polkit, context);
	if (subject == NULL)
		goto out;

	if (!up_polkit_check_auth (priv->polkit, subject, "org.freedesktop.upower.hibernate", context))
		goto out;

	/* already requested */
	if (priv->about_to_sleep_id != 0) {
		error = g_error_new (UP_DAEMON_ERROR,
				     UP_DAEMON_ERROR_GENERAL,
				     "Sleep has already been requested and is pending");
		dbus_g_method_return_error (context, error);
		g_error_free (error);
		goto out;
	}

	/* do this deferred action */
	priv->sleep_kind = "hibernate";
	command = up_backend_get_hibernate_command (priv->backend);
	up_daemon_deferred_sleep (daemon, command, context);
out:
	if (subject != NULL)
		g_object_unref (subject);
	return TRUE;
}

/**
 * up_daemon_hibernate_allowed:
 **/
gboolean
up_daemon_hibernate_allowed (UpDaemon *daemon, DBusGMethodInvocation *context)
{
	gboolean ret;
	PolkitSubject *subject = NULL;
	UpDaemonPrivate *priv = daemon->priv;
	GError *error;

	subject = up_polkit_get_subject (priv->polkit, context);
	if (subject == NULL)
		goto out;

	error = NULL;
	ret = up_polkit_is_allowed (priv->polkit, subject, "org.freedesktop.upower.hibernate", &error);
	if (error) {
		dbus_g_method_return_error (context, error);
		g_error_free (error);
	}
	else {
		dbus_g_method_return (context, ret);
	}

out:
	if (subject != NULL)
		g_object_unref (subject);
	return TRUE;
}

/**
 * up_daemon_register_power_daemon:
 **/
static gboolean
up_daemon_register_power_daemon (UpDaemon *daemon)
{
	GError *error = NULL;
	gboolean ret = FALSE;
	UpDaemonPrivate *priv = daemon->priv;

	priv->connection = dbus_g_bus_get (up_daemon_get_bus_type (), &error);
	if (priv->connection == NULL) {
		if (error != NULL) {
			g_critical ("error getting system bus: %s", error->message);
			g_error_free (error);
		}
		goto out;
	}

	/* connect to DBUS */
	priv->proxy = dbus_g_proxy_new_for_name (priv->connection,
						 DBUS_SERVICE_DBUS,
						 DBUS_PATH_DBUS,
						 DBUS_INTERFACE_DBUS);

	/* register GObject */
	dbus_g_connection_register_g_object (priv->connection,
					     "/org/freedesktop/UPower",
					     G_OBJECT (daemon));

	/* success */
	ret = TRUE;
out:
	return ret;
}

/**
 * up_daemon_startup:
 **/
gboolean
up_daemon_startup (UpDaemon *daemon)
{
	gboolean ret;
	gboolean on_battery;
	gboolean on_low_battery;
	UpDaemonPrivate *priv = daemon->priv;

	/* register on bus */
	ret = up_daemon_register_power_daemon (daemon);
	if (!ret) {
		g_warning ("failed to register");
		goto out;
	}

	/* stop signals and callbacks */
	g_debug ("daemon now coldplug");
	g_object_freeze_notify (G_OBJECT(daemon));
	priv->during_coldplug = TRUE;

	/* coldplug backend backend */
	ret = up_backend_coldplug (priv->backend, daemon);
	if (!ret) {
		g_warning ("failed to coldplug backend");
		goto out;
	}

	/* get battery state */
	on_battery = (up_daemon_get_on_battery_local (daemon) &&
		      !up_daemon_get_on_ac_local (daemon));
	on_low_battery = up_daemon_get_on_low_battery_local (daemon);
	up_daemon_set_on_battery (daemon, on_battery);
	up_daemon_set_on_low_battery (daemon, on_low_battery);

	/* start signals and callbacks */
	g_object_thaw_notify (G_OBJECT(daemon));
	priv->during_coldplug = FALSE;
	g_debug ("daemon now not coldplug");

	/* set power policy */
	if (priv->conf_run_powersave_command)
		up_daemon_set_powersave (daemon, priv->on_battery);
out:
	return ret;
}

/**
 * up_daemon_get_device_list:
 **/
UpDeviceList *
up_daemon_get_device_list (UpDaemon *daemon)
{
	return g_object_ref (daemon->priv->power_devices);
}

/**
 * up_daemon_set_lid_is_closed:
 **/
void
up_daemon_set_lid_is_closed (UpDaemon *daemon, gboolean lid_is_closed)
{
	UpDaemonPrivate *priv = daemon->priv;

	/* check if we are ignoring the lid */
	if (up_config_get_boolean (priv->config, "IgnoreLid")) {
		g_debug ("ignoring lid state");
		return;
	}

	g_debug ("lid_is_closed = %s", lid_is_closed ? "yes" : "no");
	priv->lid_is_closed = lid_is_closed;
	g_object_notify (G_OBJECT (daemon), "lid-is-closed");
}

/**
 * up_daemon_set_lid_force_sleep:
 **/
void
up_daemon_set_lid_force_sleep (UpDaemon *daemon, gboolean lid_force_sleep)
{
	UpDaemonPrivate *priv = daemon->priv;
	g_debug ("lid_force_sleep = %s", lid_force_sleep ? "yes" : "no");
	priv->lid_force_sleep = lid_force_sleep;
	g_object_notify (G_OBJECT (daemon), "lid-enforce-sleep");
}

/**
 * up_daemon_set_lid_is_present:
 **/
void
up_daemon_set_lid_is_present (UpDaemon *daemon, gboolean lid_is_present)
{
	UpDaemonPrivate *priv = daemon->priv;

	/* check if we are ignoring the lid */
	if (up_config_get_boolean (priv->config, "IgnoreLid")) {
		g_debug ("ignoring lid state");
		return;
	}

	g_debug ("lid_is_present = %s", lid_is_present ? "yes" : "no");
	priv->lid_is_present = lid_is_present;
	g_object_notify (G_OBJECT (daemon), "lid-is-present");
}

/**
 * up_daemon_set_is_docked:
 **/
void
up_daemon_set_is_docked (UpDaemon *daemon, gboolean is_docked)
{
	UpDaemonPrivate *priv = daemon->priv;
	g_debug ("is_docked = %s", is_docked ? "yes" : "no");
	priv->is_docked = is_docked;
	g_object_notify (G_OBJECT (daemon), "is-docked");
}

/**
 * up_daemon_set_on_battery:
 **/
void
up_daemon_set_on_battery (UpDaemon *daemon, gboolean on_battery)
{
	UpDaemonPrivate *priv = daemon->priv;
	g_debug ("on_battery = %s", on_battery ? "yes" : "no");
	priv->on_battery = on_battery;
	g_object_notify (G_OBJECT (daemon), "on-battery");
}

/**
 * up_daemon_set_on_low_battery:
 **/
void
up_daemon_set_on_low_battery (UpDaemon *daemon, gboolean on_low_battery)
{
	UpDaemonPrivate *priv = daemon->priv;
	g_debug ("on_low_battery = %s", on_low_battery ? "yes" : "no");
	priv->on_low_battery = on_low_battery;
	g_object_notify (G_OBJECT (daemon), "on-low-battery");
}

/**
 * up_daemon_refresh_battery_devices_cb:
 **/
static gboolean
up_daemon_refresh_battery_devices_cb (UpDaemon *daemon)
{
	UpDaemonPrivate *priv = daemon->priv;

	/* no more left to do? */
	if (priv->battery_poll_count-- == 0) {
		priv->battery_poll_id = 0;
		return FALSE;
	}

	g_debug ("doing the delayed refresh (%i)", priv->battery_poll_count);
	up_daemon_refresh_battery_devices (daemon);

	/* keep going until none left to do */
	return TRUE;
}

/**
 * up_daemon_poll_battery_devices_for_a_little_bit:
 **/
static void
up_daemon_poll_battery_devices_for_a_little_bit (UpDaemon *daemon)
{
	UpDaemonPrivate *priv = daemon->priv;

	priv->battery_poll_count = UP_DAEMON_POLL_BATTERY_NUMBER_TIMES;

	/* already polling */
	if (priv->battery_poll_id != 0)
		return;
	priv->battery_poll_id =
		g_timeout_add_seconds (UP_DAEMON_ON_BATTERY_REFRESH_DEVICES_DELAY,
				       (GSourceFunc) up_daemon_refresh_battery_devices_cb, daemon);
#if GLIB_CHECK_VERSION(2,25,8)
	g_source_set_name_by_id (priv->battery_poll_id, "[UpDaemon] poll batteries for AC event");
#endif
}

/**
 * up_daemon_device_changed_cb:
 **/
static void
up_daemon_device_changed_cb (UpDevice *device, UpDaemon *daemon)
{
	const gchar *object_path;
	UpDeviceKind type;
	gboolean ret;
	UpDaemonPrivate *priv = daemon->priv;

	g_return_if_fail (UP_IS_DAEMON (daemon));
	g_return_if_fail (UP_IS_DEVICE (device));

	/* refresh battery devices when AC state changes */
	g_object_get (device,
		      "type", &type,
		      NULL);
	if (type == UP_DEVICE_KIND_LINE_POWER) {
		/* refresh now, and again in a little while */
		up_daemon_refresh_battery_devices (daemon);
		up_daemon_poll_battery_devices_for_a_little_bit (daemon);
	}

	/* second, check if the on_battery and on_low_battery state has changed */
	ret = (up_daemon_get_on_battery_local (daemon) && !up_daemon_get_on_ac_local (daemon));
	if (ret != priv->on_battery) {
		up_daemon_set_on_battery (daemon, ret);

		/* set power policy */
		if (priv->conf_run_powersave_command)
			up_daemon_set_powersave (daemon, ret);
	}
	ret = up_daemon_get_on_low_battery_local (daemon);
	if (ret != priv->on_low_battery)
		up_daemon_set_on_low_battery (daemon, ret);

	/* emit */
	if (!priv->during_coldplug) {
		object_path = up_device_get_object_path (device);
		g_debug ("emitting device-changed: %s", object_path);

		/* don't crash the session */
		if (object_path == NULL) {
			g_warning ("INTERNAL STATE CORRUPT: not sending NULL, device:%p", device);
			return;
		}
		g_signal_emit (daemon, signals[SIGNAL_DEVICE_CHANGED], 0, object_path);
	}
}

/**
 * up_daemon_device_added_cb:
 **/
static void
up_daemon_device_added_cb (UpBackend *backend, GObject *native, UpDevice *device, UpDaemon *daemon)
{
	UpDeviceKind type;
	const gchar *object_path;
	UpDaemonPrivate *priv = daemon->priv;

	g_return_if_fail (UP_IS_DAEMON (daemon));
	g_return_if_fail (UP_IS_DEVICE (device));
	g_return_if_fail (G_IS_OBJECT (native));

	/* add to device list */
	up_device_list_insert (priv->power_devices, native, G_OBJECT (device));

	/* connect, so we get changes */
	g_signal_connect (device, "changed",
			  G_CALLBACK (up_daemon_device_changed_cb), daemon);

	/* refresh after a short delay */
	g_object_get (device,
		      "type", &type,
		      NULL);
	if (type == UP_DEVICE_KIND_BATTERY)
		up_daemon_poll_battery_devices_for_a_little_bit (daemon);

	/* emit */
	if (!priv->during_coldplug) {
		object_path = up_device_get_object_path (device);
		g_debug ("emitting added: %s (during coldplug %i)", object_path, priv->during_coldplug);

		/* don't crash the session */
		if (object_path == NULL) {
			g_warning ("INTERNAL STATE CORRUPT: not sending NULL, native:%p, device:%p", native, device);
			return;
		}
		g_signal_emit (daemon, signals[SIGNAL_DEVICE_ADDED], 0, object_path);
	}
}

/**
 * up_daemon_device_removed_cb:
 **/
static void
up_daemon_device_removed_cb (UpBackend *backend, GObject *native, UpDevice *device, UpDaemon *daemon)
{
	UpDeviceKind type;
	const gchar *object_path;
	UpDaemonPrivate *priv = daemon->priv;

	g_return_if_fail (UP_IS_DAEMON (daemon));
	g_return_if_fail (UP_IS_DEVICE (device));
	g_return_if_fail (G_IS_OBJECT (native));

	/* remove from list */
	up_device_list_remove (priv->power_devices, G_OBJECT(device));

	/* refresh after a short delay */
	g_object_get (device,
		      "type", &type,
		      NULL);
	if (type == UP_DEVICE_KIND_BATTERY)
		up_daemon_poll_battery_devices_for_a_little_bit (daemon);

	/* emit */
	if (!priv->during_coldplug) {
		object_path = up_device_get_object_path (device);
		g_debug ("emitting device-removed: %s", object_path);

		/* don't crash the session */
		if (object_path == NULL) {
			g_warning ("INTERNAL STATE CORRUPT: not sending NULL, native:%p, device:%p", native, device);
			return;
		}
		g_signal_emit (daemon, signals[SIGNAL_DEVICE_REMOVED], 0, object_path);
	}

	/* finalise the object */
	g_object_unref (device);
}

/**
 * up_daemon_properties_changed_cb:
 **/
static void
up_daemon_properties_changed_cb (GObject *object, GParamSpec *pspec, UpDaemon *daemon)
{
	g_return_if_fail (UP_IS_DAEMON (daemon));

	/* emit */
	if (!daemon->priv->during_coldplug) {
		g_debug ("emitting changed");
		g_signal_emit (daemon, signals[SIGNAL_CHANGED], 0);
	}
}

/**
 * up_daemon_init:
 **/
static void
up_daemon_init (UpDaemon *daemon)
{
	gboolean ret;
	GError *error = NULL;
	GKeyFile *file;
	const gchar *filename_self_test;
	gchar *filename;

	daemon->priv = UP_DAEMON_GET_PRIVATE (daemon);
	daemon->priv->polkit = up_polkit_new ();
	daemon->priv->config = up_config_new ();
	daemon->priv->lid_is_present = FALSE;
	daemon->priv->is_docked = FALSE;
	daemon->priv->lid_is_closed = FALSE;
	daemon->priv->kernel_can_suspend = FALSE;
	daemon->priv->kernel_can_hibernate = FALSE;
	daemon->priv->hibernate_has_encrypted_swap = FALSE;
	daemon->priv->power_devices = up_device_list_new ();
	daemon->priv->on_battery = FALSE;
	daemon->priv->on_low_battery = FALSE;
	daemon->priv->during_coldplug = FALSE;
	daemon->priv->sent_sleeping_signal = FALSE;
	daemon->priv->battery_poll_id = 0;
	daemon->priv->battery_poll_count = 0;
	daemon->priv->about_to_sleep_id = 0;
	daemon->priv->conf_sleep_timeout = 1000;
	daemon->priv->conf_allow_hibernate_encrypted_swap = FALSE;
	daemon->priv->conf_run_powersave_command = TRUE;

	/* load some values from the config file */
	file = g_key_file_new ();
	filename_self_test = g_getenv ("UPOWER_CONF_FILE_NAME");
	if (filename_self_test != NULL) {
		g_debug ("using %s as the self test conf file", filename_self_test);
		filename = g_strdup (filename_self_test);
	} else {
		filename = g_build_filename (PACKAGE_SYSCONF_DIR,"UPower", "UPower.conf", NULL);
	}
	ret = g_key_file_load_from_file (file, filename, G_KEY_FILE_NONE, &error);
	if (ret) {
		daemon->priv->conf_sleep_timeout =
			g_key_file_get_integer (file, "UPower", "SleepTimeout", NULL);
		daemon->priv->conf_allow_hibernate_encrypted_swap =
			g_key_file_get_boolean (file, "UPower", "AllowHibernateEncryptedSwap", NULL);
		daemon->priv->conf_run_powersave_command =
			g_key_file_get_boolean (file, "UPower", "RunPowersaveCommand", NULL);
	} else {
		g_warning ("failed to load config file %s: %s", filename, error->message);
		g_error_free (error);
	}
	g_key_file_free (file);
	g_free (filename);

	daemon->priv->backend = up_backend_new ();
	g_signal_connect (daemon->priv->backend, "device-added",
			  G_CALLBACK (up_daemon_device_added_cb), daemon);
	g_signal_connect (daemon->priv->backend, "device-removed",
			  G_CALLBACK (up_daemon_device_removed_cb), daemon);

	/* use a timer for the about-to-sleep logic */
	daemon->priv->about_to_sleep_timer = g_timer_new ();
	g_timer_stop (daemon->priv->about_to_sleep_timer);

	/* watch when these properties change */
	g_signal_connect (daemon, "notify::lid-is-present",
			  G_CALLBACK (up_daemon_properties_changed_cb), daemon);
	g_signal_connect (daemon, "notify::lid-is-closed",
			  G_CALLBACK (up_daemon_properties_changed_cb), daemon);
	g_signal_connect (daemon, "notify::on-battery",
			  G_CALLBACK (up_daemon_properties_changed_cb), daemon);
	g_signal_connect (daemon, "notify::on-low-battery",
			  G_CALLBACK (up_daemon_properties_changed_cb), daemon);

	/* check if we have support */
	daemon->priv->kernel_can_suspend = up_backend_kernel_can_suspend (daemon->priv->backend);
	daemon->priv->kernel_can_hibernate = up_backend_kernel_can_hibernate (daemon->priv->backend);

	/* is the swap usable? */
	if (daemon->priv->kernel_can_hibernate)
		daemon->priv->hibernate_has_encrypted_swap = up_backend_has_encrypted_swap (daemon->priv->backend);
}

/**
 * up_daemon_error_quark:
 **/
GQuark
up_daemon_error_quark (void)
{
	static GQuark ret = 0;
	if (ret == 0)
		ret = g_quark_from_static_string ("up_daemon_error");
	return ret;
}

#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
/**
 * up_daemon_error_get_type:
 **/
GType
up_daemon_error_get_type (void)
{
	static GType etype = 0;

	if (etype == 0) {
		static const GEnumValue values[] = {
			ENUM_ENTRY (UP_DAEMON_ERROR_GENERAL, "GeneralError"),
			ENUM_ENTRY (UP_DAEMON_ERROR_NOT_SUPPORTED, "NotSupported"),
			ENUM_ENTRY (UP_DAEMON_ERROR_NO_SUCH_DEVICE, "NoSuchDevice"),
			{ 0, 0, 0 }
		};
		g_assert (UP_DAEMON_NUM_ERRORS == G_N_ELEMENTS (values) - 1);
		etype = g_enum_register_static ("UpDaemonError", values);
	}
	return etype;
}

/**
 * up_daemon_get_property:
 **/
static void
up_daemon_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
	UpDaemon *daemon = UP_DAEMON (object);
	UpDaemonPrivate *priv = daemon->priv;

	switch (prop_id) {
	case PROP_DAEMON_VERSION:
		g_value_set_string (value, PACKAGE_VERSION);
		break;
	case PROP_CAN_SUSPEND:
		g_value_set_boolean (value, priv->kernel_can_suspend);
		break;
	case PROP_CAN_HIBERNATE:
		g_value_set_boolean (value, (priv->kernel_can_hibernate &&
					     up_daemon_check_hibernate_swap (daemon) &&
					     (!priv->hibernate_has_encrypted_swap ||
					      priv->conf_allow_hibernate_encrypted_swap)));
		break;
	case PROP_ON_BATTERY:
		g_value_set_boolean (value, priv->on_battery);
		break;
	case PROP_ON_LOW_BATTERY:
		g_value_set_boolean (value, priv->on_battery && priv->on_low_battery);
		break;
	case PROP_LID_IS_CLOSED:
		g_value_set_boolean (value, priv->lid_is_closed);
		break;
	case PROP_LID_IS_PRESENT:
		g_value_set_boolean (value, priv->lid_is_present);
		break;
	case PROP_LID_FORCE_SLEEP:
		g_value_set_boolean (value, priv->lid_force_sleep);
		break;
	case PROP_IS_DOCKED:
		g_value_set_boolean (value, priv->is_docked);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

/**
 * up_daemon_set_property:
 **/
static void
up_daemon_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
	switch (prop_id) {
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

/**
 * up_daemon_class_init:
 **/
static void
up_daemon_class_init (UpDaemonClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	object_class->finalize = up_daemon_finalize;
	object_class->get_property = up_daemon_get_property;
	object_class->set_property = up_daemon_set_property;

	g_type_class_add_private (klass, sizeof (UpDaemonPrivate));

	signals[SIGNAL_DEVICE_ADDED] =
		g_signal_new ("device-added",
			      G_OBJECT_CLASS_TYPE (klass),
			      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
			      0, NULL, NULL,
			      g_cclosure_marshal_VOID__STRING,
			      G_TYPE_NONE, 1, G_TYPE_STRING);

	signals[SIGNAL_DEVICE_REMOVED] =
		g_signal_new ("device-removed",
			      G_OBJECT_CLASS_TYPE (klass),
			      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
			      0, NULL, NULL,
			      g_cclosure_marshal_VOID__STRING,
			      G_TYPE_NONE, 1, G_TYPE_STRING);

	signals[SIGNAL_DEVICE_CHANGED] =
		g_signal_new ("device-changed",
			      G_OBJECT_CLASS_TYPE (klass),
			      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
			      0, NULL, NULL,
			      g_cclosure_marshal_VOID__STRING,
			      G_TYPE_NONE, 1, G_TYPE_STRING);

	signals[SIGNAL_CHANGED] =
		g_signal_new ("changed",
			      G_OBJECT_CLASS_TYPE (klass),
			      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
			      0, NULL, NULL,
			      g_cclosure_marshal_VOID__VOID,
			      G_TYPE_NONE, 0);

	signals[SIGNAL_SLEEPING] =
		g_signal_new ("sleeping",
			      G_OBJECT_CLASS_TYPE (klass),
			      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
			      0, NULL, NULL,
			      g_cclosure_marshal_VOID__VOID,
			      G_TYPE_NONE, 0);

	signals[SIGNAL_NOTIFY_SLEEP] =
		g_signal_new ("notify-sleep",
			      G_OBJECT_CLASS_TYPE (klass),
			      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
			      0, NULL, NULL,
			      g_cclosure_marshal_VOID__STRING,
			      G_TYPE_NONE, 1, G_TYPE_STRING);

	signals[SIGNAL_RESUMING] =
		g_signal_new ("resuming",
			      G_OBJECT_CLASS_TYPE (klass),
			      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
			      0, NULL, NULL,
			      g_cclosure_marshal_VOID__VOID,
			      G_TYPE_NONE, 0);

	signals[SIGNAL_NOTIFY_RESUME] =
		g_signal_new ("notify-resume",
			      G_OBJECT_CLASS_TYPE (klass),
			      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
			      0, NULL, NULL,
			      g_cclosure_marshal_VOID__STRING,
			      G_TYPE_NONE, 1, G_TYPE_STRING);

	g_object_class_install_property (object_class,
					 PROP_DAEMON_VERSION,
					 g_param_spec_string ("daemon-version",
							      "Daemon Version",
							      "The version of the running daemon",
							      NULL,
							      G_PARAM_READABLE));

	g_object_class_install_property (object_class,
					 PROP_LID_IS_PRESENT,
					 g_param_spec_boolean ("lid-is-present",
							       "Is a laptop",
							       "If this computer is probably a laptop",
							       FALSE,
							       G_PARAM_READABLE));

	g_object_class_install_property (object_class,
					 PROP_LID_FORCE_SLEEP,
					 g_param_spec_boolean ("lid-force-sleep",
							       "Enforce sleep on lid close",
							       "If this computer has to sleep on lid close",
							       FALSE,
							       G_PARAM_READABLE));

	g_object_class_install_property (object_class,
					 PROP_IS_DOCKED,
					 g_param_spec_boolean ("is-docked",
							       "Is docked",
							       "If this computer is docked",
							       FALSE,
							       G_PARAM_READABLE));

	g_object_class_install_property (object_class,
					 PROP_CAN_SUSPEND,
					 g_param_spec_boolean ("can-suspend",
							       "Can Suspend",
							       "Whether the system can suspend",
							       FALSE,
							       G_PARAM_READABLE));

	g_object_class_install_property (object_class,
					 PROP_CAN_HIBERNATE,
					 g_param_spec_boolean ("can-hibernate",
							       "Can Hibernate",
							       "Whether the system can hibernate",
							       FALSE,
							       G_PARAM_READABLE));

	g_object_class_install_property (object_class,
					 PROP_ON_BATTERY,
					 g_param_spec_boolean ("on-battery",
							       "On Battery",
							       "Whether the system is running on battery",
							       FALSE,
							       G_PARAM_READABLE));

	g_object_class_install_property (object_class,
					 PROP_ON_LOW_BATTERY,
					 g_param_spec_boolean ("on-low-battery",
							       "On Low Battery",
							       "Whether the system is running on battery and if the battery is critically low",
							       FALSE,
							       G_PARAM_READABLE));

	g_object_class_install_property (object_class,
					 PROP_LID_IS_CLOSED,
					 g_param_spec_boolean ("lid-is-closed",
							       "Laptop lid is closed",
							       "If the laptop lid is closed",
							       FALSE,
							       G_PARAM_READABLE));

	dbus_g_object_type_install_info (UP_TYPE_DAEMON, &dbus_glib_up_daemon_object_info);

	dbus_g_error_domain_register (UP_DAEMON_ERROR, NULL, UP_DAEMON_TYPE_ERROR);
}

/**
 * up_daemon_finalize:
 **/
static void
up_daemon_finalize (GObject *object)
{
	UpDaemon *daemon = UP_DAEMON (object);
	UpDaemonPrivate *priv = daemon->priv;

	if (priv->battery_poll_id != 0)
		g_source_remove (priv->battery_poll_id);

	if (priv->proxy != NULL)
		g_object_unref (priv->proxy);
	if (priv->connection != NULL)
		dbus_g_connection_unref (priv->connection);
	g_object_unref (priv->power_devices);
	g_object_unref (priv->polkit);
	g_object_unref (priv->config);
	g_object_unref (priv->backend);
	g_timer_destroy (priv->about_to_sleep_timer);

	G_OBJECT_CLASS (up_daemon_parent_class)->finalize (object);
}

/**
 * up_daemon_new:
 **/
UpDaemon *
up_daemon_new (void)
{
	UpDaemon *daemon;
	daemon = UP_DAEMON (g_object_new (UP_TYPE_DAEMON, NULL));
	return daemon;
}

