/*
A small wrapper utility to load indicators and put them as menu items
into the gnome-panel using it's applet interface.

Copyright 2009 Canonical Ltd.

Authors:
    Ted Gould <ted@canonical.com>
    Christoph Korn <c_korn@gmx.de>
    Cody Russell <crussell@canonical.com>

This program is free software: you can redistribute it and/or modify it 
under the terms of the GNU General Public License version 3, as published 
by the Free Software Foundation.

This program is distributed in the hope that it will be useful, but 
WITHOUT ANY WARRANTY; without even the implied warranties of 
MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.
*/

#include <config.h>

#include <unistd.h>

#include <glib/gi18n.h>
#include <gio/gdesktopappinfo.h>

#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-bindings.h>

#include <libdbusmenu-glib/server.h>
#include <libdbusmenu-glib/menuitem.h>
#include <libdbusmenu-glib/client.h>

#include <libindicator/indicator-service.h>

#include "dbus-shared-names.h"
#include "dbusmenu-shared.h"

#include "gtk-dialog/gconf-helper.h"

#define UP_ADDRESS    "org.freedesktop.UPower"
#define UP_OBJECT     "/org/freedesktop/UPower"
#define UP_INTERFACE  "org.freedesktop.UPower"

#define DESKTOP_FILE  "/usr/share/applications/winboot.desktop"

static DbusmenuMenuitem * root_menuitem = NULL;
static GMainLoop * mainloop = NULL;
static DBusGProxy * up_main_proxy = NULL;
static DBusGProxy * up_prop_proxy = NULL;

static DBusGProxyCall * suspend_call = NULL;

static DbusmenuMenuitem * suspend_mi = NULL;
static DbusmenuMenuitem * restart_mi = NULL;
static DbusmenuMenuitem * shutdown_mi = NULL;

/* A return from the command to sleep the system.  Make sure
   that we unthrottle the screensaver. */
static void
sleep_response (DBusGProxy * proxy, DBusGProxyCall * call, gpointer data)
{
	return;
}

/* Let's put this machine to sleep, with some info on how
   it should sleep.  */
static void
machine_sleep (DbusmenuMenuitem * mi, guint timestamp, gpointer userdata)
{
	gchar * type = (gchar *)userdata;

	if (up_main_proxy == NULL) {
		g_warning("Can not %s as no upower proxy", type);
	}

	dbus_g_proxy_begin_call(up_main_proxy,
	                        type,
	                        sleep_response,
	                        NULL,
	                        NULL,
	                        G_TYPE_INVALID);

	return;
}

/* A response to getting the suspend property */
static void
suspend_prop_cb (DBusGProxy * proxy, DBusGProxyCall * call, gpointer userdata)
{
	suspend_call = NULL;

	GValue candoit = {0};
	GError * error = NULL;
	dbus_g_proxy_end_call(proxy, call, &error, G_TYPE_VALUE, &candoit, G_TYPE_INVALID);
	if (error != NULL) {
		g_warning("Unable to check suspend: %s", error->message);
		g_error_free(error);
		return;
	}
	g_debug("Got Suspend: %s", g_value_get_boolean(&candoit) ? "true" : "false");

	if (suspend_mi != NULL) {
		dbusmenu_menuitem_property_set_value(suspend_mi, DBUSMENU_MENUITEM_PROP_VISIBLE, &candoit);
	}

	return;
}


/* A signal that we need to recheck to ensure we can still
   hibernate and/or suspend */
static void
up_changed_cb (DBusGProxy * proxy, gpointer user_data)
{
	/* Start Async call to see if we can hibernate */
	if (suspend_call == NULL) {
		suspend_call = dbus_g_proxy_begin_call(up_prop_proxy,
		                                       "Get",
		                                       suspend_prop_cb,
		                                       NULL,
		                                       NULL,
		                                       G_TYPE_STRING,
		                                       UP_INTERFACE,
		                                       G_TYPE_STRING,
		                                       "CanSuspend",
		                                       G_TYPE_INVALID,
		                                       G_TYPE_VALUE,
		                                       G_TYPE_INVALID);
	}

	return;
}

/* This function goes through and sets up what we need for
   DKp checking.  We're even setting up the calls for the props
   we need */
static void
setup_up (void) {
	DBusGConnection * bus = dbus_g_bus_get(DBUS_BUS_SYSTEM, NULL);
	g_return_if_fail(bus != NULL);

	if (up_main_proxy == NULL) {
		up_main_proxy = dbus_g_proxy_new_for_name(bus,
		                                           UP_ADDRESS,
		                                           UP_OBJECT,
		                                           UP_INTERFACE);
	}
	g_return_if_fail(up_main_proxy != NULL);

	if (up_prop_proxy == NULL) {
		up_prop_proxy = dbus_g_proxy_new_for_name(bus,
		                                           UP_ADDRESS,
		                                           UP_OBJECT,
		                                           DBUS_INTERFACE_PROPERTIES);
	}
	g_return_if_fail(up_prop_proxy != NULL);

	/* Connect to changed signal */
	dbus_g_proxy_add_signal(up_main_proxy,
	                        "Changed",
	                        G_TYPE_INVALID);

	dbus_g_proxy_connect_signal(up_main_proxy,
	                            "Changed",
	                            G_CALLBACK(up_changed_cb),
	                            NULL,
	                            NULL);

	/* Force an original "changed" event */
	up_changed_cb(up_main_proxy, NULL);

	return;
}

/* This is the function to show a dialog on actions that
   can destroy data.  Currently it just calls the GTK version
   but it seems that in the future it should figure out
   what's going on and something better. */
static void
show_dialog (DbusmenuMenuitem * mi, guint timestamp, gchar * type)
{
	gchar * helper = g_build_filename(LIBEXECDIR, "gtk-logout-helper", NULL);
	gchar * dialog_line = g_strdup_printf("%s --%s", helper, type);
	g_free(helper);

	g_debug("Showing dialog '%s'", dialog_line);

	GError * error = NULL;
	if (!g_spawn_command_line_async(dialog_line, &error)) {
		g_warning("Unable to show dialog: %s", error->message);
		g_error_free(error);
	}

	g_free(dialog_line);

	return;
}

static void
winboot_cb (DbusmenuMenuitem * mi, guint timestamp, gpointer data)
{
	GAppInfo * appinfo = G_APP_INFO(data);
	g_return_if_fail(appinfo != NULL);
	g_app_info_launch(appinfo, NULL, NULL, NULL);
	return;
}


/* Builds up the menu for us */
static void
rebuild_items (DbusmenuMenuitem *root)
{
	GList *children;

	children = dbusmenu_menuitem_take_children (root);
	g_list_foreach (children, (GFunc)g_object_unref, NULL);
	g_list_free (children);

	suspend_mi = dbusmenu_menuitem_new();
	dbusmenu_menuitem_property_set_bool(suspend_mi, DBUSMENU_MENUITEM_PROP_VISIBLE, FALSE);
	dbusmenu_menuitem_property_set(suspend_mi, DBUSMENU_MENUITEM_PROP_LABEL, _("Sleep"));
	dbusmenu_menuitem_child_append(root, suspend_mi);
	g_signal_connect(G_OBJECT(suspend_mi), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, G_CALLBACK(machine_sleep), "Suspend");

	restart_mi = dbusmenu_menuitem_new();
	if (supress_confirmations()) {
		dbusmenu_menuitem_property_set(restart_mi, DBUSMENU_MENUITEM_PROP_LABEL, _("Restart"));
	} else {
		dbusmenu_menuitem_property_set(restart_mi, DBUSMENU_MENUITEM_PROP_LABEL, _("Restart..."));
	}
	dbusmenu_menuitem_child_append(root, restart_mi);
	g_signal_connect(G_OBJECT(restart_mi), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, G_CALLBACK(show_dialog), "restart");

	shutdown_mi = dbusmenu_menuitem_new();
	if (supress_confirmations()) {
		dbusmenu_menuitem_property_set(shutdown_mi, DBUSMENU_MENUITEM_PROP_LABEL, _("Switch Off"));
	} else {
		dbusmenu_menuitem_property_set(shutdown_mi, DBUSMENU_MENUITEM_PROP_LABEL, _("Switch Off..."));
	}
	dbusmenu_menuitem_child_append(root, shutdown_mi);
	g_signal_connect(G_OBJECT(shutdown_mi), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, G_CALLBACK(show_dialog), "shutdown");

	RestartShutdownLogoutMenuItems * restart_shutdown_logout_mi = g_new0 (RestartShutdownLogoutMenuItems, 1);
	restart_shutdown_logout_mi->restart_mi = restart_mi;
	restart_shutdown_logout_mi->shutdown_mi = shutdown_mi;

	update_menu_entries(restart_shutdown_logout_mi);

	if (g_file_test(DESKTOP_FILE, G_FILE_TEST_EXISTS)) {
		GAppInfo * appinfo = G_APP_INFO(g_desktop_app_info_new_from_filename(DESKTOP_FILE));

		if (appinfo != NULL) {
			DbusmenuMenuitem * separator = dbusmenu_menuitem_new();
			dbusmenu_menuitem_property_set(separator, DBUSMENU_MENUITEM_PROP_TYPE, DBUSMENU_CLIENT_TYPES_SEPARATOR);
			dbusmenu_menuitem_child_append(root, separator);

			DbusmenuMenuitem * winboot_mi = dbusmenu_menuitem_new();
			dbusmenu_menuitem_property_set(winboot_mi, DBUSMENU_MENUITEM_PROP_LABEL, g_app_info_get_name(appinfo));
			g_signal_connect(G_OBJECT(winboot_mi), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, G_CALLBACK(winboot_cb), appinfo);
			dbusmenu_menuitem_child_append(root, winboot_mi);
		}
	} else {
		g_warning("Unable to find desktop file '" DESKTOP_FILE "'");
	}

	return;
}

/* When the service interface starts to shutdown, we
   should follow it. */
void
service_shutdown (IndicatorService * service, gpointer user_data)
{
	if (mainloop != NULL) {
		g_debug("Service shutdown");
		g_main_loop_quit(mainloop);
	}
	return;
}

/* Main, is well, main.  It brings everything up and throws
   us into the mainloop of no return. */
int
main (int argc, char ** argv)
{
    g_type_init();

	/* Setting up i18n and gettext.  Apparently, we need
	   all of these. */
	setlocale (LC_ALL, "");
	bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
	textdomain (GETTEXT_PACKAGE);

	IndicatorService * service = indicator_service_new_version(INDICATOR_SESSION_DBUS_NAME,
	                                                           INDICATOR_SESSION_DBUS_VERSION);
	g_signal_connect(G_OBJECT(service),
	                 INDICATOR_SERVICE_SIGNAL_SHUTDOWN,
	                 G_CALLBACK(service_shutdown), NULL);

    root_menuitem = dbusmenu_menuitem_new();
	g_debug("Root ID: %d", dbusmenu_menuitem_get_id(root_menuitem));

    rebuild_items (root_menuitem);

	setup_up();

    DbusmenuServer * server = dbusmenu_server_new(INDICATOR_SESSION_DBUS_OBJECT);
    dbusmenu_server_set_root(server, root_menuitem);

    mainloop = g_main_loop_new(NULL, FALSE);
    g_main_loop_run(mainloop);

    return 0;
}

