/*
 * $Id: EvokeService.cpp 3575 2014-06-20 09:29:18Z karijes $
 *
 * Evoke, head honcho of everything
 * Part of Equinox Desktop Environment (EDE).
 * Copyright (c) 2007-2009 EDE Authors.
 *
 * This program is licensed under terms of the 
 * GNU General Public License version 2 or newer.
 * See COPYING for details.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <edelib/File.h>
#include <edelib/FileTest.h>
#include <edelib/Debug.h>
#include <edelib/Config.h>
#include <edelib/Resource.h>
#include <edelib/StrUtil.h>
#include <edelib/MessageBox.h>
#include <edelib/Nls.h>
#include <edelib/Run.h>

#include <FL/Fl.H>

#if EDELIB_HAVE_DBUS
#include <edelib/EdbusMessage.h>
#include <edelib/EdbusConnection.h>
#endif

#include "EvokeService.h"
#include "Splash.h"
#include "Logout.h"
#include "Xsm.h"
#include "Xshutdown.h"

EDELIB_NS_USING(Config)
EDELIB_NS_USING(Resource)
EDELIB_NS_USING(RES_SYS_ONLY)
EDELIB_NS_USING(file_remove)
EDELIB_NS_USING(file_test)
EDELIB_NS_USING(str_trim)
EDELIB_NS_USING(run_sync)
EDELIB_NS_USING(alert)
EDELIB_NS_USING(ask)
EDELIB_NS_USING(FILE_TEST_IS_REGULAR)

#if EDELIB_HAVE_DBUS
EDELIB_NS_USING(EdbusMessage)
EDELIB_NS_USING(EdbusConnection)
EDELIB_NS_USING(EdbusError)
EDELIB_NS_USING(EDBUS_SESSION)
EDELIB_NS_USING(EDBUS_SYSTEM)
#endif

#ifdef USE_LOCAL_CONFIG
# define CONFIG_GET_STRVAL(object, section, key, buff) object.get(section, key, buff, sizeof(buff))
#else
# define CONFIG_GET_STRVAL(object, section, key, buff) object.get(section, key, buff, sizeof(buff), RES_SYS_ONLY)
#endif

/* stolen from xfce's xfsm-shutdown-helper */
#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
# define POWEROFF_CMD  "/sbin/shutdown -p now"
# define REBOOT_CMD    "/sbin/shutdown -r now"
#elif defined(sun) || defined(__sun)
# define POWEROFF_CMD  "/usr/sbin/shutdown -i 5 -g 0 -y"
# define REBOOT_CMD    "/usr/sbin/shutdown -i 6 -g 0 -y"
#else
# define POWEROFF_CMD  "/sbin/shutdown -h now"
# define REBOOT_CMD    "/sbin/shutdown -r now"
#endif

static Atom XA_EDE_EVOKE_SHUTDOWN_ALL;
static Atom XA_EDE_EVOKE_QUIT;

static int get_int_property_value(Atom at) {
	Atom real;
	int format;
	unsigned long n, extra;
	unsigned char* prop;

	int status = XGetWindowProperty(fl_display, RootWindow(fl_display, fl_screen),
			at, 0L, 0x7fffffff, False, XA_CARDINAL, &real, &format, &n, &extra,
			(unsigned char**)&prop);
	int ret = -1;
	if(status != Success && !prop)
		return ret;
	ret = int(*(long*)prop);
	XFree(prop);
	return ret;
}

static void send_dbus_ede_quit(void) {
#ifdef EDELIB_HAVE_DBUS
	EdbusConnection c;
	E_RETURN_IF_FAIL(c.connect(EDBUS_SESSION));

	EdbusMessage msg;
	msg.create_signal("/org/equinoxproject/Shutdown", "org.equinoxproject.Shutdown", "Shutdown");
	c.send(msg);
#endif
}

#define CONSOLEKIT_SERVICE   "org.freedesktop.ConsoleKit"
#define CONSOLEKIT_PATH      "/org/freedesktop/ConsoleKit/Manager"
#define CONSOLEKIT_INTERFACE "org.freedesktop.ConsoleKit.Manager"

static bool do_shutdown_or_restart(bool restart, void (*x_shutdown_func)(void)) {
	const char *action;
	int r = 1;

#ifdef EDELIB_HAVE_DBUS
	const char *can_do_action;

	EdbusConnection c;
	if(!c.connect(EDBUS_SYSTEM)) {
		alert(_("Unable to connect to DBus daemon. Make sure system DBus daemon is running and have permissions to access it"));
		return false;
	}

	/* determine what to check first; ConsoleKit shutdown action calls 'Stop' */
	can_do_action = restart ? "CanRestart" : "CanStop";
	action        = restart ? "Restart" : "Stop";

	EdbusMessage msg, ret;
	msg.create_method_call(CONSOLEKIT_SERVICE,
						   CONSOLEKIT_PATH,
						   CONSOLEKIT_INTERFACE,
						   can_do_action);

	if(!c.send_with_reply_and_block(msg, 100, ret)) {
		EdbusError *err = c.error();
		alert(_("Unable to communicate with DBus properly. Got '%s' (%s)"), err->name(), err->message());
		return false;
	}

	if(ret.size() != 1) {
		alert(_("Bad reply size. Expected '1' element, but got '%i' instead"), ret.size());
		return false;
	}

	EdbusMessage::iterator it = ret.begin();
	if(it->is_bool() && it->to_bool() == true) {
		/* first close all X children */
		x_shutdown_func();

		/* call action */
		msg.create_method_call(CONSOLEKIT_SERVICE,
							   CONSOLEKIT_PATH,
							   CONSOLEKIT_INTERFACE,
							   action);
		c.send(msg);
		return true;
	}

	r = ask(_("You are not allowed to execute this command. Please consult ConsoleKit documentation on how to allow privileged actions. "
			  "Would you like to try to execute system commands?"));
#endif /* EDELIB_HAVE_DBUS */

	/* try to do things manually */
	if(!r) return false;
	action = _("restart");

	if(restart) {
		r = run_sync(REBOOT_CMD);
	} else {
		r = run_sync(POWEROFF_CMD);
		action = _("shutdown");
	}

	if(r) {
		alert(_("Unable to %s the computer. Probably you do not have enough permissions to do that"), action);
		return false;
	}

	return true;
}

EvokeService::EvokeService() : lock_name(NULL), xsm(NULL), is_running(false) { 
	/* TODO: or add setup_atoms() function */
	XA_EDE_EVOKE_SHUTDOWN_ALL = XInternAtom(fl_display, "_EDE_EVOKE_SHUTDOWN_ALL", False);
	XA_EDE_EVOKE_QUIT         = XInternAtom(fl_display, "_EDE_EVOKE_QUIT", False);
}

EvokeService::~EvokeService() { 
	clear_startup_items();
	stop_xsettings_manager(true);
	remove_lock();
}

EvokeService* EvokeService::instance(void) {
	static EvokeService es;
	return &es;
}

bool EvokeService::setup_lock(const char* name) {
	E_RETURN_VAL_IF_FAIL(file_test(name, FILE_TEST_IS_REGULAR) == false, false);

	FILE* f = fopen(name, "w");
	E_RETURN_VAL_IF_FAIL(f != NULL, false);

	fclose(f);

	lock_name = strdup(name);
	return true;
}

void EvokeService::remove_lock(void) {
	E_RETURN_IF_FAIL(lock_name != NULL);

	file_remove(lock_name);
	free(lock_name);
	lock_name = NULL;
}

void EvokeService::clear_startup_items(void) {
	E_RETURN_IF_FAIL(startup_items.size() > 0);

	StartupItemListIter it = startup_items.begin(), ite = startup_items.end();
	while(it != ite) {
		delete *it;
		it = startup_items.erase(it);
	}
}

void EvokeService::read_startup(void) {
#ifdef USE_LOCAL_CONFIG
	/* 
	 * this will load SETTINGS_FILENAME only from local directory;
	 * intended for development and testing only
	 */
	Config c;
	int ret = c.load("ede-startup.conf");
#else
	/* only system resource will be loaded; use ede-startup will be skipped */
	Resource c;
	int ret = c.load("ede-startup");
#endif
	if(!ret) {
		E_WARNING(E_STRLOC ": Unable to load EDE startup file\n");
		return;
	}

	char tokbuf[128], buf[128];
	/* if nothing found, exit */
	E_RETURN_IF_FAIL(CONFIG_GET_STRVAL(c, "Startup", "start_order", tokbuf));

	if(CONFIG_GET_STRVAL(c, "Startup", "splash_theme", buf))
		splash_theme = buf;

	for(const char* sect = strtok(tokbuf, ","); sect; sect = strtok(NULL, ",")) {
		/* remove leading/ending spaces, if exists */
		str_trim(buf);

		/* assure each startup item has 'exec' key */
		if(!CONFIG_GET_STRVAL(c, sect, "exec", buf)) {
			E_WARNING(E_STRLOC ": Startup item '%s' does not have anything to execute. Skipping...\n", sect);
			continue;
		}

		StartupItem *s = new StartupItem;
		s->exec = buf;

		if(CONFIG_GET_STRVAL(c, sect, "icon", buf))
			s->icon = buf;
		if(CONFIG_GET_STRVAL(c, sect, "description", buf))
			s->description = buf;

		startup_items.push_back(s);
	}
}

void EvokeService::run_startup(bool splash, bool dryrun) {
	E_RETURN_IF_FAIL(startup_items.size() > 0);

	Splash s(startup_items, splash_theme, splash, dryrun);
	s.run();
	clear_startup_items();
}

void EvokeService::start_xsettings_manager(void) {
	xsm = new Xsm;

	if(Xsm::manager_running(fl_display, fl_screen)) {
		int ret = ask(_("XSETTINGS manager already running on this screen. Would you like to replace it?"));
		if(ret < 1) {
			stop_xsettings_manager(false);
			return;
		}
	}

	if(!xsm->init(fl_display, fl_screen)) {
		alert(_("Unable to load XSETTINGS manager properly"));
		stop_xsettings_manager(false);
	}

	if(xsm->load_serialized())
		xsm->notify();
}

void EvokeService::stop_xsettings_manager(bool serialize) {
	E_RETURN_IF_FAIL(xsm != NULL);

	if(serialize)
		xsm->save_serialized();

	delete xsm;
	xsm = NULL;
}

int EvokeService::handle(const XEvent* xev) {
	if(xsm && xsm->should_terminate(xev)) {
		E_DEBUG(E_STRLOC ": Terminating XSETTINGS manager on request by X event\n");
		stop_xsettings_manager(true);
		return 1;
	}

	if(xev->xproperty.atom == XA_EDE_EVOKE_QUIT) {
		int val = get_int_property_value(XA_EDE_EVOKE_QUIT);
		if(val == 1)
			stop();
	}

	if(xev->xproperty.atom == XA_EDE_EVOKE_SHUTDOWN_ALL) {
		int val = get_int_property_value(XA_EDE_EVOKE_SHUTDOWN_ALL);
		if(val == 1) {
			int dw, dh, ret;

			dw = DisplayWidth(fl_display, fl_screen);
			dh = DisplayHeight(fl_display, fl_screen);

			ret = logout_dialog_show(dw, dh, LOGOUT_OPT_SHUTDOWN | LOGOUT_OPT_RESTART);
			if(ret == LOGOUT_RET_CANCEL)
				return 1;

			send_dbus_ede_quit();

			switch(ret) {
				case LOGOUT_RET_RESTART:
					if(!do_shutdown_or_restart(true, x_shutdown)) return 1;
					break;
				case LOGOUT_RET_SHUTDOWN:
					if(!do_shutdown_or_restart(false, x_shutdown)) return 1;
					break;
				case LOGOUT_RET_LOGOUT:
				default:
					x_shutdown();
					break;
			}

			stop();
		}
	}

	return 0;
}
