/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2004-2006 William Jon McCann <mccann@jhu.edu>
 *
 * 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.
 *
 * Authors: William Jon McCann <mccann@jhu.edu>
 *
 */

#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <X11/extensions/scrnsaver.h>

#include <glib.h>
#include <glib-object.h>
#include <gdk/gdkx.h>

#include "mate-screensaver.h"

#include "gs-manager.h"
#include "gs-watcher.h"
#include "gs-fade.h"
#include "gs-grab.h"

#include "gs-listener-dbus.h"
#include "gs-monitor.h"
#include "gs-prefs.h"
#include "gs-debug.h"

static void gs_monitor_class_init(GSMonitorClass* klass);
static void gs_monitor_init(GSMonitor* monitor);
static void gs_monitor_finalize(GObject* object);

#define GS_MONITOR_GET_PRIVATE(o) \
	(G_TYPE_INSTANCE_GET_PRIVATE((o), GS_TYPE_MONITOR, GSMonitorPrivate))

struct GSMonitorPrivate {
	GSWatcher* watcher;
	GSListener* listener;
	GSManager* manager;
	GSPrefs* prefs;
	GSFade* fade;
	GSGrab* grab;
	guint release_grab_id;
};

#define FADE_TIMEOUT 10000

G_DEFINE_TYPE(GSMonitor, gs_monitor, G_TYPE_OBJECT)

static void gs_monitor_class_init(GSMonitorClass* klass)
{
	GObjectClass* object_class = G_OBJECT_CLASS(klass);

	object_class->finalize = gs_monitor_finalize;

	g_type_class_add_private(klass, sizeof(GSMonitorPrivate));
}

static void manager_activated_cb(GSManager* manager, GSMonitor* monitor)
{
	/* Nothing */
}

static void manager_deactivated_cb(GSManager* manager, GSMonitor* monitor)
{
	gs_listener_set_active (monitor->priv->listener, FALSE);
}

static gboolean watcher_idle_cb(GSWatcher* watcher, gboolean is_idle, GSMonitor* monitor)
{
	gboolean res;

	gs_debug ("Idle signal detected: %d", is_idle);

	res = gs_listener_set_session_idle(monitor->priv->listener, is_idle);

	return res;
}

static gboolean release_grab_timeout(GSMonitor* monitor)
{
	gboolean manager_active;

	manager_active = gs_manager_get_active(monitor->priv->manager);

	if (! manager_active)
	{
		gs_grab_release(monitor->priv->grab);
	}

	monitor->priv->release_grab_id = 0;
	return FALSE;
}

static gboolean watcher_idle_notice_cb(GSWatcher* watcher, gboolean in_effect, GSMonitor* monitor)
{
	gboolean activation_enabled;
	gboolean inhibited;
	gboolean handled;

	gs_debug("Idle notice signal detected: %d", in_effect);

	/* only fade if screensaver can activate */
	activation_enabled = gs_listener_get_activation_enabled(monitor->priv->listener);
	inhibited = gs_listener_is_inhibited(monitor->priv->listener);

	handled = FALSE;

	if (in_effect)
	{
		if (activation_enabled && ! inhibited)
		{
			/* start slow fade */
			if (gs_grab_grab_offscreen(monitor->priv->grab, FALSE))
			{
				gs_fade_async(monitor->priv->fade, FADE_TIMEOUT, NULL, NULL);
			}
			else
			{
				gs_debug("Could not grab the keyboard so not performing idle warning fade-out");
			}

			handled = TRUE;
		}
	}
	else
	{
		gboolean manager_active;

		manager_active = gs_manager_get_active(monitor->priv->manager);
		/* cancel the fade unless manager was activated */
		if (! manager_active)
		{
			gs_debug("manager not active, performing fade cancellation");
			gs_fade_reset(monitor->priv->fade);

			/* don't release the grab immediately to prevent typing passwords into windows */
			if (monitor->priv->release_grab_id != 0)
			{
				g_source_remove(monitor->priv->release_grab_id);
			}

			monitor->priv->release_grab_id = g_timeout_add(1000, (GSourceFunc) release_grab_timeout, monitor);
		}
		else
		{
			gs_debug("manager active, skipping fade cancellation");
		}

		handled = TRUE;
	}

	return handled;
}

static void gs_monitor_lock_screen(GSMonitor* monitor)
{
	gboolean res;
	gboolean locked;

	/* set lock flag before trying to activate screensaver
	   in case something tries to react to the ActiveChanged signal */
	gs_manager_get_lock_active(monitor->priv->manager, &locked);
	gs_manager_set_lock_active(monitor->priv->manager, TRUE);
	res = gs_listener_set_active(monitor->priv->listener, TRUE);

	if (! res)
	{
		/* If we've failed then restore lock status */
		gs_manager_set_lock_active(monitor->priv->manager, locked);
		gs_debug("Unable to lock the screen");
	}
}

static void gs_monitor_simulate_user_activity(GSMonitor* monitor)
{
	Display *display = gdk_x11_display_get_xdisplay (gdk_display_get_default ());
	XScreenSaverSuspend (display, TRUE);
	XSync (display, FALSE);
	XScreenSaverSuspend (display, FALSE);
	XSync (display, FALSE);

	/* request that the manager unlock -
	   will pop up a dialog if necessary */
	gs_manager_request_unlock(monitor->priv->manager);
}

static void listener_lock_cb(GSListener* listener, GSMonitor* monitor)
{
	if (!monitor->priv->prefs->lock_disabled)
	{
		gs_monitor_lock_screen(monitor);
	}
	else
	{
		gs_debug("Locking disabled by the administrator");
	}

}

static void listener_quit_cb(GSListener* listener, GSMonitor* monitor)
{
	gs_listener_set_active(monitor->priv->listener, FALSE);
	mate_screensaver_quit();
}

static void listener_cycle_cb(GSListener* listener, GSMonitor* monitor)
{
	gs_manager_cycle(monitor->priv->manager);
}

static void listener_show_message_cb(GSListener* listener, const char* summary, const char* body, const char* icon, GSMonitor* monitor)
{
	gs_manager_show_message(monitor->priv->manager, summary, body, icon);
}

static gboolean listener_active_changed_cb(GSListener* listener, gboolean active, GSMonitor* monitor)
{
	gboolean res;
	gboolean ret;
	gboolean idle_watch_enabled;

	res = gs_manager_set_active(monitor->priv->manager, active);

	if (! res)
	{
		gs_debug("Unable to set manager active: %d", active);
		ret = FALSE;
		goto done;
	}

	ret = TRUE;

	done:

	idle_watch_enabled = gs_watcher_get_enabled(monitor->priv->watcher);

	if (ret && idle_watch_enabled)
	{
		res = gs_watcher_set_active(monitor->priv->watcher, !active);

		if (!res)
		{
			gs_debug("Unable to set the idle watcher active: %d", !active);
		}
	}

	return ret;
}

static void listener_throttle_changed_cb(GSListener* listener, gboolean throttled, GSMonitor* monitor)
{
	gs_manager_set_throttled(monitor->priv->manager, throttled);
}

static void listener_simulate_user_activity_cb(GSListener* listener, GSMonitor* monitor)
{
	gs_monitor_simulate_user_activity(monitor);
}

static void _gs_monitor_update_from_prefs(GSMonitor* monitor, GSPrefs* prefs)
{
	gboolean idle_detection_enabled;
	gboolean idle_detection_active;
	gboolean activate_watch;
	gboolean manager_active;
	gboolean lock_enabled;
	gboolean user_switch_enabled;

	lock_enabled = (monitor->priv->prefs->lock_enabled && !monitor->priv->prefs->lock_disabled);
	user_switch_enabled = (monitor->priv->prefs->user_switch_enabled && !monitor->priv->prefs->user_switch_disabled);

	gs_manager_set_lock_enabled(monitor->priv->manager, lock_enabled);
	gs_manager_set_lock_timeout(monitor->priv->manager, monitor->priv->prefs->lock_timeout);
	gs_manager_set_logout_enabled(monitor->priv->manager, monitor->priv->prefs->logout_enabled);
	gs_manager_set_user_switch_enabled(monitor->priv->manager, user_switch_enabled);
	gs_manager_set_keyboard_enabled(monitor->priv->manager, monitor->priv->prefs->keyboard_enabled);
	gs_manager_set_logout_timeout(monitor->priv->manager, monitor->priv->prefs->logout_timeout);
	gs_manager_set_logout_command(monitor->priv->manager, monitor->priv->prefs->logout_command);
	gs_manager_set_keyboard_command(monitor->priv->manager, monitor->priv->prefs->keyboard_command);
	gs_manager_set_cycle_timeout(monitor->priv->manager, monitor->priv->prefs->cycle);
	gs_manager_set_mode(monitor->priv->manager, monitor->priv->prefs->mode);
	gs_manager_set_themes(monitor->priv->manager, monitor->priv->prefs->themes);

	/* enable activation when allowed */
	gs_listener_set_activation_enabled(monitor->priv->listener, monitor->priv->prefs->idle_activation_enabled);

	/* idle detection always enabled */
	idle_detection_enabled = TRUE;

	gs_watcher_set_enabled(monitor->priv->watcher, idle_detection_enabled);

	/* in the case where idle detection is reenabled we may need to
	   activate the watcher too */

	manager_active = gs_manager_get_active(monitor->priv->manager);
	idle_detection_active = gs_watcher_get_active(monitor->priv->watcher);

	activate_watch = (! manager_active && ! idle_detection_active && idle_detection_enabled);

	if (activate_watch)
	{
		gs_watcher_set_active(monitor->priv->watcher, TRUE);
	}

	if (monitor->priv->prefs->status_message_enabled)
	{
		char* text;
		g_object_get(monitor->priv->watcher, "status-message", &text, NULL);
		gs_manager_set_status_message(monitor->priv->manager, text);
		g_free(text);
	}
	else
	{
		gs_manager_set_status_message(monitor->priv->manager, NULL);
	}
}

static void disconnect_listener_signals(GSMonitor* monitor)
{
	g_signal_handlers_disconnect_by_func(monitor->priv->listener, listener_lock_cb, monitor);
	g_signal_handlers_disconnect_by_func(monitor->priv->listener, listener_quit_cb, monitor);
	g_signal_handlers_disconnect_by_func(monitor->priv->listener, listener_cycle_cb, monitor);
	g_signal_handlers_disconnect_by_func(monitor->priv->listener, listener_active_changed_cb, monitor);
	g_signal_handlers_disconnect_by_func(monitor->priv->listener, listener_throttle_changed_cb, monitor);
	g_signal_handlers_disconnect_by_func(monitor->priv->listener, listener_simulate_user_activity_cb, monitor);
	g_signal_handlers_disconnect_by_func(monitor->priv->listener, listener_show_message_cb, monitor);
}

static void connect_listener_signals(GSMonitor* monitor)
{
	g_signal_connect(monitor->priv->listener, "lock", G_CALLBACK(listener_lock_cb), monitor);
	g_signal_connect(monitor->priv->listener, "quit", G_CALLBACK(listener_quit_cb), monitor);
	g_signal_connect(monitor->priv->listener, "cycle", G_CALLBACK(listener_cycle_cb), monitor);
	g_signal_connect(monitor->priv->listener, "active-changed", G_CALLBACK(listener_active_changed_cb), monitor);
	g_signal_connect(monitor->priv->listener, "throttle-changed", G_CALLBACK(listener_throttle_changed_cb), monitor);
	g_signal_connect(monitor->priv->listener, "simulate-user-activity", G_CALLBACK(listener_simulate_user_activity_cb), monitor);
	g_signal_connect(monitor->priv->listener, "show-message", G_CALLBACK(listener_show_message_cb), monitor);
}

static void on_watcher_status_message_changed(GSWatcher* watcher, GParamSpec* pspec, GSMonitor* monitor)
{
	char* text;
	g_object_get(watcher, "status-message", &text, NULL);
	gs_manager_set_status_message(monitor->priv->manager, text);
	g_free(text);
}

static void disconnect_watcher_signals(GSMonitor *monitor)
{
	g_signal_handlers_disconnect_by_func(monitor->priv->watcher, watcher_idle_cb, monitor);
	g_signal_handlers_disconnect_by_func(monitor->priv->watcher, watcher_idle_notice_cb, monitor);
	g_signal_handlers_disconnect_by_func(monitor->priv->watcher, on_watcher_status_message_changed, monitor);
}

static void connect_watcher_signals(GSMonitor *monitor)
{
	g_signal_connect(monitor->priv->watcher, "idle-changed", G_CALLBACK(watcher_idle_cb), monitor);
	g_signal_connect(monitor->priv->watcher, "idle-notice-changed", G_CALLBACK(watcher_idle_notice_cb), monitor);
	g_signal_connect(monitor->priv->watcher, "notify::status-message", G_CALLBACK(on_watcher_status_message_changed), monitor);

}

static void disconnect_manager_signals(GSMonitor* monitor)
{
	g_signal_handlers_disconnect_by_func(monitor->priv->manager, manager_activated_cb, monitor);
	g_signal_handlers_disconnect_by_func(monitor->priv->manager, manager_deactivated_cb, monitor);
}

static void connect_manager_signals(GSMonitor* monitor)
{
	g_signal_connect(monitor->priv->manager, "activated", G_CALLBACK(manager_activated_cb), monitor);
	g_signal_connect(monitor->priv->manager, "deactivated", G_CALLBACK(manager_deactivated_cb), monitor);
}

static void disconnect_prefs_signals(GSMonitor* monitor)
{
	g_signal_handlers_disconnect_by_func(monitor->priv->prefs, _gs_monitor_update_from_prefs, monitor);
}

static void connect_prefs_signals(GSMonitor* monitor)
{
	g_signal_connect_swapped(monitor->priv->prefs, "changed", G_CALLBACK(_gs_monitor_update_from_prefs), monitor);
}

static void gs_monitor_init(GSMonitor* monitor)
{

	monitor->priv = GS_MONITOR_GET_PRIVATE(monitor);

	monitor->priv->prefs = gs_prefs_new();
	connect_prefs_signals(monitor);

	monitor->priv->listener = gs_listener_new();
	connect_listener_signals(monitor);

	monitor->priv->fade = gs_fade_new();
	monitor->priv->grab = gs_grab_new();

	monitor->priv->watcher = gs_watcher_new();
	connect_watcher_signals(monitor);

	monitor->priv->manager = gs_manager_new();
	connect_manager_signals(monitor);

	_gs_monitor_update_from_prefs(monitor, monitor->priv->prefs);
}

static void gs_monitor_finalize(GObject* object)
{
	GSMonitor* monitor;

	g_return_if_fail(object != NULL);
	g_return_if_fail(GS_IS_MONITOR(object));

	monitor = GS_MONITOR(object);

	g_return_if_fail(monitor->priv != NULL);

	disconnect_watcher_signals(monitor);
	disconnect_listener_signals(monitor);
	disconnect_manager_signals(monitor);
	disconnect_prefs_signals(monitor);

	g_object_unref(monitor->priv->fade);
	g_object_unref(monitor->priv->grab);
	g_object_unref(monitor->priv->watcher);
	g_object_unref(monitor->priv->listener);
	g_object_unref(monitor->priv->manager);
	g_object_unref(monitor->priv->prefs);

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

GSMonitor* gs_monitor_new(void)
{
	GSMonitor* monitor;

	monitor = g_object_new(GS_TYPE_MONITOR, NULL);

	return GS_MONITOR(monitor);
}

gboolean gs_monitor_start(GSMonitor* monitor, GError** error)
{
	g_return_val_if_fail(GS_IS_MONITOR(monitor), FALSE);

	if (!gs_listener_acquire(monitor->priv->listener, error))
	{
		return FALSE;
	}

	return TRUE;
}