#include <config.h>

#include <libintl.h>
#include <X11/Xlib.h>
#include <gdk/gdkx.h>
#include <gtk/gtk.h>
#include <glade/glade-xml.h>
#define WNCK_I_KNOW_THIS_IS_UNSTABLE
#include <libwnck/libwnck.h>

#define _(x) dgettext (GETTEXT_PACKAGE, x)
#define N_(x) x

#define RESPONSE_REMOVE 1

#define STOCK_APP_MANAGER      "app-manager"
#define STOCK_KILL_APPLICATION "kill-application"

static void
app_manager_register_stock_icon (GtkIconFactory *factory,
				 const char     *stockid,
				 const char     *filename)
{
	GtkIconSource *source;
	GtkIconSet    *icon_set;

	source = gtk_icon_source_new ();
	gtk_icon_source_set_filename (source, filename);

	icon_set = gtk_icon_set_new ();
	gtk_icon_set_add_source (icon_set, source);

	gtk_icon_factory_add (factory, stockid, icon_set);

	gtk_icon_set_unref (icon_set);
	gtk_icon_source_free (source);
}

static void
app_manager_register_stock_icons (void)
{
	GtkIconFactory *factory;
	GtkStockItem    items [1];

	factory = gtk_icon_factory_new ();
	gtk_icon_factory_add_default (factory);

	/* FIXME: should use gnome_program_locate_file
	 */
	app_manager_register_stock_icon (
		factory, STOCK_KILL_APPLICATION,
		APP_MANAGER_ICONDIR "/gnome-kill-application1.png");

	app_manager_register_stock_icon (
		factory, STOCK_APP_MANAGER,
		APP_MANAGER_ICONDIR "/gnome-app-manager.png");

	items [0].stock_id           = STOCK_KILL_APPLICATION;
	items [0].label              = _("_Force Quit");
	items [0].modifier           = 0;
	items [0].keyval             = 0;
	items [0].translation_domain = GETTEXT_PACKAGE;

	gtk_stock_add (items, G_N_ELEMENTS (items));

	g_object_unref (factory);
}

static void
app_manager_selection_changed (GtkTreeSelection *selection,
			       GtkWidget        *remove_button)
{
	gtk_widget_set_sensitive (
			remove_button,
			gtk_tree_selection_get_selected (selection, NULL, NULL));
}

/* From metacity */
static gboolean
kill_window_question (void)
{
	GtkWidget *dialog;
	gboolean   retval;

	dialog = gtk_message_dialog_new (NULL, 0,
					 GTK_MESSAGE_QUESTION,
					 GTK_BUTTONS_NONE,
					 _("Force this application to exit?\n"
					   "(Any open documents will be lost.)"));

	gtk_dialog_add_buttons (GTK_DIALOG (dialog),
				GTK_STOCK_CANCEL,
				GTK_RESPONSE_REJECT,
				STOCK_KILL_APPLICATION,
				GTK_RESPONSE_ACCEPT,
				NULL);

	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_REJECT);

	retval = gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT;

	gtk_widget_destroy (dialog);

	return retval;
}

static void
app_manager_kill_client (Window window)
{
	if (window == None)
		return;

	if (!kill_window_question ())
		return;

	gdk_error_trap_push ();
	XKillClient (gdk_display, window);
	gdk_error_trap_pop ();
}

static void
app_manager_remove_selected (GtkTreeView *tree_view)
{
	GtkTreeSelection *selection;
	GtkTreeModel     *model;
	GtkTreeIter       iter;
	WnckApplication  *application;
	WnckWindow       *window;

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));

	gtk_tree_selection_get_selected (selection, &model, &iter);

	gtk_tree_model_get (model, &iter, 0, &application, 1, &window, -1);

	if (!application && window) {
		application = g_object_ref (wnck_window_get_application (window));
		g_object_set_data (G_OBJECT (model), "select-application", application);
	}

	app_manager_kill_client (wnck_application_get_xid (application));

	if (application)
		g_object_unref (application);
	if (window)
		g_object_unref (window);

}

static void
app_manager_handle_dialog_response (GtkWidget   *dialog,
				    int          response_id,
				    GtkTreeView *tree_view)
{
	switch (response_id) {
	case RESPONSE_REMOVE:
		app_manager_remove_selected (tree_view);
		return;
	case GTK_RESPONSE_HELP:
		g_warning ("Help");
		return;
	default:
		break;
	}

	gtk_main_quit ();
}

static void
app_manager_init_icon (GtkWidget *dialog,
		       GtkWidget *image)
{
	/* FIXME: should use gnome_program_locate_file
	 */
	char *filename = APP_MANAGER_ICONDIR "/gnome-app-manager.png";

	gtk_window_set_icon_from_file (GTK_WINDOW (dialog), filename, NULL);

	gtk_image_set_from_stock (
		GTK_IMAGE (image), STOCK_APP_MANAGER, GTK_ICON_SIZE_DIALOG);
}

static void
app_manager_set_cell_icon (GtkTreeViewColumn *tree_column,
			   GtkCellRenderer   *cell,
			   GtkTreeModel      *model,
			   GtkTreeIter       *iter)
{
	WnckApplication *application;
	WnckWindow      *window;
	GdkPixbuf       *icon = NULL;

	gtk_tree_model_get (model, iter, 0, &application, 1, &window, -1);

	if (application && !wnck_application_get_icon_is_fallback (application))
		icon = wnck_application_get_mini_icon (application);
	else if (window)
		icon = wnck_window_get_mini_icon (window);

	g_object_set (cell, "pixbuf", icon, NULL);

	if (application)
		g_object_unref (application);
	if (window)
		g_object_unref (window);
}

static void
app_manager_set_cell_title (GtkTreeViewColumn *tree_column,
			    GtkCellRenderer   *cell,
			    GtkTreeModel      *model,
			    GtkTreeIter       *iter)
{
	WnckApplication *application;
	WnckWindow      *window;
	const char      *title = NULL;

	gtk_tree_model_get (model, iter, 0, &application, 1, &window, -1);

	if (application)
		title = wnck_application_get_name (application);
	else if (window)
		title = wnck_window_get_name (window);

	g_object_set (cell, "text", title, NULL);

	if (application)
		g_object_unref (application);
	if (window)
		g_object_unref (window);
}

static void
app_manager_set_cell_pid (GtkTreeViewColumn *tree_column,
			  GtkCellRenderer   *cell,
			  GtkTreeModel      *model,
			  GtkTreeIter       *iter)
{
	WnckApplication *application;
	WnckWindow      *window;
	char            *text = NULL; 

	gtk_tree_model_get (model, iter, 0, &application, 1, &window, -1);

	if (application) {
		int pid = wnck_application_get_pid (application);

		if (pid != 0)
			text = g_strdup_printf ("%d", pid);
		else
			text = g_strdup (_("(unknown)"));
	}

	g_object_set (cell, "text", text, NULL);

	g_free (text);

	if (application)
		g_object_unref (application);
	if (window)
		g_object_unref (window);
}

static GtkTreeStore *
app_manager_init_tree_view (GtkWidget *tree_view,
			    GtkWidget *remove_button)
{
	GtkTreeSelection  *selection;
	GtkTreeViewColumn *column;
	GtkCellRenderer   *cell;
	GtkTreeStore      *model;

	model = gtk_tree_store_new (2, WNCK_TYPE_APPLICATION, WNCK_TYPE_WINDOW);

	gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view),
				 GTK_TREE_MODEL (model));

	g_object_unref (model);

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
	gtk_tree_selection_set_mode (GTK_TREE_SELECTION (selection),
				     GTK_SELECTION_SINGLE);

	g_signal_connect (selection, "changed",
			  G_CALLBACK (app_manager_selection_changed), remove_button);

	/* Application column */
	column = gtk_tree_view_column_new ();
	gtk_tree_view_column_set_title (column, _("Application"));

	cell = gtk_cell_renderer_pixbuf_new ();
	g_object_set (G_OBJECT (cell), "xpad", 4, NULL);

	gtk_tree_view_column_pack_start (column, cell, FALSE);
	gtk_tree_view_column_set_cell_data_func (
		column,
		cell,
		(GtkTreeCellDataFunc) app_manager_set_cell_icon,
		NULL,
		NULL);

	cell = gtk_cell_renderer_text_new ();
	g_object_set (G_OBJECT (cell), "xpad", 4, NULL);

	gtk_tree_view_column_pack_start (column, cell, TRUE);
	gtk_tree_view_column_set_cell_data_func (
		column,
		cell,
		(GtkTreeCellDataFunc) app_manager_set_cell_title,
		NULL,
		NULL);

	gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);

	/* PID column */
	column = gtk_tree_view_column_new ();
	gtk_tree_view_column_set_title (column, _("PID"));

	cell = gtk_cell_renderer_text_new ();
	g_object_set (G_OBJECT (cell), "xpad", 2, NULL);

	gtk_tree_view_column_pack_start (column, cell, TRUE);
	gtk_tree_view_column_set_cell_data_func (
		column,
		cell,
		(GtkTreeCellDataFunc) app_manager_set_cell_pid,
		NULL,
		NULL);

	gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);

	return model;
}

static GtkTreeView *
app_manager_init (void)
{
	GladeXML     *xml;
	GtkWidget    *dialog;
	GtkWidget    *image;
	GtkWidget    *sw;
	GtkWidget    *tree_view;
	GtkWidget    *remove_button;
	GtkTreeStore *model;

	xml = glade_xml_new (APP_MANAGER_GLADEDIR "/gnome-app-manager.glade", NULL, NULL);

	dialog        = glade_xml_get_widget (xml, "app_manager_dialog");
	image         = glade_xml_get_widget (xml, "image");
	sw            = glade_xml_get_widget (xml, "scrolled_window");
	tree_view     = glade_xml_get_widget (xml, "tree_view");

	remove_button = gtk_dialog_add_button (
				GTK_DIALOG (dialog),
				STOCK_KILL_APPLICATION,
				RESPONSE_REMOVE);

	gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), RESPONSE_REMOVE, FALSE);

	g_signal_connect (dialog, "response",
			  G_CALLBACK (app_manager_handle_dialog_response), tree_view);

	app_manager_init_icon (dialog, image);
	model = app_manager_init_tree_view (tree_view, remove_button);

	gtk_window_set_geometry_hints (GTK_WINDOW (dialog), sw, NULL, 0);

	gtk_widget_show_all (dialog);

	g_object_unref (xml);

	return GTK_TREE_VIEW (tree_view);
}

static void
app_manager_get_selected (GtkTreeView      *tree_view,
			  GtkTreeModel     *model,
			  WnckApplication **application,
			  WnckWindow      **window)
{
	GtkTreeSelection *selection;
	GtkTreeIter       iter;

	selection = gtk_tree_view_get_selection (tree_view);
	if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
		return;

	gtk_tree_model_get (model, &iter,
			    0, application,
			    1, window,
			    -1);

	if (*application)
		return;

	if (*window)
		*application = wnck_window_get_application (*window);

	if (*application) {
		g_object_ref (*application);
		return;
	}

	*application = g_object_get_data (G_OBJECT (model),
					  "select-application");
	if (*application)
		g_object_ref (*application);
}

static void
app_manager_save_expanded_state (GtkTreeView *tree_view,
				 GtkTreeModel *model)
{
	GtkTreeIter iter;

	if (!gtk_tree_model_get_iter_first (model, &iter))
		return;

	do {
		GtkTreePath     *path;
		WnckApplication *application = NULL;
		WnckWindow      *window = NULL;

		path = gtk_tree_model_get_path (model, &iter);

		if (!gtk_tree_view_row_expanded (tree_view, path)) {
			gtk_tree_path_free (path);
			continue;
		}

		gtk_tree_model_get (model, &iter,
				    0, &application,
				    1, &window,
				    -1);
		if (application)
			g_object_set_data (G_OBJECT (application),
					   "expand-row",
					   GINT_TO_POINTER (TRUE));

		if (application)
			g_object_unref (application);
		if (window)
			g_object_unref (window);

		gtk_tree_path_free (path);
	} while (gtk_tree_model_iter_next (model, &iter));
}

static void
app_manager_restore_expanded_state (GtkTreeView     *tree_view,
				    GtkTreeModel    *model,
				    GtkTreeIter     *iter,
				    WnckApplication *application)
{
	GtkTreePath *path;

	if (!g_object_get_data (G_OBJECT (application), "expand-row"))
		return;

	g_object_set_data (G_OBJECT (application), "expand-row", NULL);

	path = gtk_tree_model_get_path (model, iter); 
	gtk_tree_view_expand_row (tree_view, path, TRUE);

	gtk_tree_path_free (path);
}

static void
app_manager_update_model (WnckScreen   *screen,
			  GtkTreeView  *tree_view,
			  GtkTreeStore *model)
{
	GtkTreeSelection *selection;
	WnckApplication  *sel_application = NULL;
	WnckWindow       *sel_window = NULL;
	GList            *windows, *l;
	GSList           *applications = NULL, *l1;

	/* An attempt to retain the selection somewhere sensible */
	app_manager_get_selected (tree_view, GTK_TREE_MODEL (model),
				  &sel_application, &sel_window);

	/* expand the rows that were previously expanded */
	app_manager_save_expanded_state (tree_view, GTK_TREE_MODEL (model));

	gtk_tree_store_clear (model);

	windows = wnck_screen_get_windows (screen);

	selection = gtk_tree_view_get_selection (tree_view);

	for (l = windows; l; l = l->next) {
		WnckApplication *application;
		WnckWindow      *window = l->data;
		GtkTreeIter      iter, *parent_iter;

		application = wnck_window_get_application (window);

		parent_iter = g_object_get_data (G_OBJECT (application), "app-manager-iter");
		if (!parent_iter) {
			gtk_tree_store_append (model, &iter, NULL);
			gtk_tree_store_set (model, &iter, 0, application, 1, window, -1);

			if (application == sel_application)
				gtk_tree_selection_select_iter (selection, &iter);

			parent_iter = gtk_tree_iter_copy (&iter);
			g_object_set_data (G_OBJECT (application),
					   "app-manager-iter",
					   parent_iter);

			applications = g_slist_prepend (applications, application);
		}

		gtk_tree_store_append (model, &iter, parent_iter);
		gtk_tree_store_set (model, &iter, 0, NULL, 1, window, -1);

		if (window == sel_window)
			gtk_tree_selection_select_iter (selection, &iter);
	}

	for (l1 = applications; l1; l1 = l1->next) {
		GtkTreeIter *iter;

		iter = g_object_get_data (G_OBJECT (l1->data), "app-manager-iter");
		g_object_set_data (G_OBJECT (l1->data), "app-manager-iter", NULL);

		app_manager_restore_expanded_state (
			tree_view, GTK_TREE_MODEL (model), iter, l1->data);

		gtk_tree_iter_free (iter);
	}

	g_slist_free (applications);

	gtk_tree_view_columns_autosize (tree_view);

	if (sel_window)
		g_object_unref (sel_window);
	if (sel_application)
		g_object_unref (sel_application);

	g_object_set_data (G_OBJECT (model), "select-application", NULL);
}

typedef struct {
	WnckScreen   *screen;
	GtkTreeView  *tree_view;
	GtkTreeStore *model;
} AppManagerUpdateData;

static gboolean
app_manager_update_in_idle (AppManagerUpdateData *data)
{
	g_object_set_data (G_OBJECT (data->model), "idle-id", NULL);

	app_manager_update_model (data->screen, data->tree_view, data->model);

	g_free (data);

	return FALSE;
}

static void
app_manager_queue_update (WnckScreen   *screen,
			  GtkTreeView  *tree_view,
			  GtkTreeStore *model)
{
	AppManagerUpdateData *data;
	guint                 idle_id;

	if (g_object_get_data (G_OBJECT (model), "idle-id"))
		return;

	data = g_new (AppManagerUpdateData, 1);

	data->screen    = screen;
	data->tree_view = tree_view;
	data->model     = model;

	idle_id = g_idle_add ((GSourceFunc) app_manager_update_in_idle, data);
	g_object_set_data (G_OBJECT (model), "idle-id", GUINT_TO_POINTER (idle_id));
}

static void
app_manager_window_changed (WnckScreen  *screen,
			    WnckWindow  *window,
			    GtkTreeView *tree_view)
{
	GtkTreeStore *model;

	model = GTK_TREE_STORE (gtk_tree_view_get_model (tree_view));

	app_manager_queue_update (screen, tree_view, model);
}

static void
app_manager_connect_to_screen (WnckScreen  *screen,
			       GtkTreeView *tree_view)
{

	g_signal_connect (screen, "window_opened",
			  G_CALLBACK (app_manager_window_changed), tree_view);
	g_signal_connect (screen, "window_closed",
			  G_CALLBACK (app_manager_window_changed), tree_view);
}

int
main (int argc, char **argv)
{
	GtkTreeView *tree_view;
	WnckScreen  *screen;

	bindtextdomain (GETTEXT_PACKAGE, GNOME_LOCALEDIR);
	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
	textdomain (GETTEXT_PACKAGE);

	gtk_init (&argc, &argv);

	app_manager_register_stock_icons ();
    
	tree_view = app_manager_init ();

	screen = wnck_screen_get_default ();

	app_manager_connect_to_screen (screen, tree_view);

	gtk_main ();

	return 0;
} 
