/*
 * Copyright (c) 2010 Mike Massonnet, <mmassonnet@xfce.org>
 *
 * 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.
 */

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

#include "process-monitor.h"
#include "process-statusbar.h"
#include "process-tree-view.h"
#include "process-window.h"
#include "settings-dialog.h"
#include "settings.h"
#include "task-manager.h"

#ifdef HAVE_LIBX11
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xmu/WinUtil.h>
#include <X11/Xos.h>
#include <X11/Xproto.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <gdk/gdkx.h>
#endif

#include <gdk/gdkkeysyms.h>
#include <libxfce4ui/libxfce4ui.h>



typedef struct _XtmProcessWindowClass XtmProcessWindowClass;
struct _XtmProcessWindowClass
{
	GtkWidgetClass parent_class;
};

struct _XtmProcessWindow
{
	GtkWidget parent;
	/*<private>*/
	GtkBuilder *builder;
	GtkWidget *window;
	GtkWidget *filter_entry;
	GtkWidget *filter_searchbar;
	GtkWidget *cpu_monitor;
	GtkWidget *mem_monitor;
	GtkWidget *vpaned;
	GtkWidget *treeview;
	GtkWidget *statusbar;
	GtkWidget *settings_button;
	XtmSettings *settings;
	XfconfChannel *channel;
	gint width;
	gint height;
	gulong handler;
	gboolean view_stuck;
};
G_DEFINE_TYPE (XtmProcessWindow, xtm_process_window, GTK_TYPE_WIDGET)

static void xtm_process_window_finalize (GObject *object);
static void xtm_process_window_hide (GtkWidget *widget);

static gboolean emit_delete_event_signal (XtmProcessWindow *window, GdkEvent *event, GtkWidget *widget);
static void emit_destroy_signal (XtmProcessWindow *window);
static gboolean xtm_process_window_key_pressed (XtmProcessWindow *window, GdkEventKey *event);
static void monitor_update_step_size (XtmProcessWindow *window);


static void
filter_entry_icon_pressed_cb (GtkEntry *entry, gint position, GdkEventButton *event __unused, gpointer data __unused)
{
	if (position == GTK_ENTRY_ICON_SECONDARY)
	{
		gtk_entry_set_text (entry, "");
		gtk_widget_grab_focus (GTK_WIDGET (entry));
	}
}

#ifdef HAVE_LIBX11
static Window
Select_Window (Display *dpy, int screen)
{
	int status;
	Cursor cursor;
	XEvent event;
	Window target_win = None, root = RootWindow (dpy, screen);
	int buttons = 0;

	/* Make the target cursor */
	cursor = XCreateFontCursor (dpy, XC_crosshair);

	/* Grab the pointer using target cursor, letting it roam all over */
	status = XGrabPointer (dpy, root, False,
		ButtonPressMask | ButtonReleaseMask, GrabModeSync,
		GrabModeAsync, root, cursor, CurrentTime);
	if (status != GrabSuccess)
	{
		fprintf (stderr, "Can't grab the mouse.\n");
		return None;
	}

	/* Let the user select a window... */
	while ((target_win == None) || (buttons != 0))
	{
		/* allow one more event */
		XAllowEvents (dpy, SyncPointer, CurrentTime);
		XWindowEvent (dpy, root, ButtonPressMask | ButtonReleaseMask, &event);
		switch (event.type)
		{
			case ButtonPress:
				if (target_win == None)
				{
					target_win = event.xbutton.subwindow; /* window selected */
					if (target_win == None)
						target_win = root;
				}
				buttons++;
				break;
			case ButtonRelease:
				if (buttons > 0) /* there may have been some down before we started */
					buttons--;
				break;
		}
	}

	XUngrabPointer (dpy, CurrentTime); /* Done with pointer */

	return target_win;
}

static void
xwininfo_clicked_cb (GtkButton *button __unused, gpointer user_data)
{
	XtmProcessWindow *window = (XtmProcessWindow *)user_data;
	Window selected_window;
	Display *dpy;
	Atom atom_NET_WM_PID;
	unsigned long _nitems;
	Atom actual_type;
	int actual_format;
	unsigned char *prop;
	int status;
	unsigned long bytes_after;
	GPid pid = 0;

	dpy = XOpenDisplay (NULL);
	selected_window = Select_Window (dpy, 0);
	if (selected_window)
	{
		selected_window = XmuClientWindow (dpy, selected_window);
	}

	atom_NET_WM_PID = XInternAtom (dpy, "_NET_WM_PID", False);

	status = XGetWindowProperty (dpy, selected_window, atom_NET_WM_PID, 0, ~0L,
		False, AnyPropertyType, &actual_type,
		&actual_format, &_nitems, &bytes_after,
		&prop);
	if (status == BadWindow)
	{
		XTM_SHOW_MESSAGE (GTK_MESSAGE_INFO,
			_("Bad Window"), _("Window id 0x%lx does not exist!"), selected_window);
	}
	if (status != Success)
	{
		XTM_SHOW_MESSAGE (GTK_MESSAGE_ERROR,
			_("XGetWindowProperty failed"), _("XGetWindowProperty failed!"));
	}
	else
	{
		if (_nitems > 0)
		{
			memcpy (&pid, prop, sizeof (pid));
			xtm_process_tree_view_highlight_pid (XTM_PROCESS_TREE_VIEW (window->treeview), pid);
		}
		else
		{
			XTM_SHOW_MESSAGE (GTK_MESSAGE_INFO,
				_("No PID found"), _("No PID found for window 0x%lx."), selected_window);
		}
		g_free (prop);
	}
}
#endif

static void
filter_entry_keyrelease_handler (GtkEntry *entry, XtmProcessTreeView *treeview)
{
	gchar *text;
	gboolean has_text;

	text = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1);
	xtm_process_tree_view_set_filter (treeview, text);
	g_free (text);

	has_text = gtk_entry_get_text_length (GTK_ENTRY (entry)) > 0;
	gtk_entry_set_icon_sensitive (GTK_ENTRY (entry), GTK_ENTRY_ICON_SECONDARY, has_text);
}

static void
xtm_process_window_class_init (XtmProcessWindowClass *klass)
{
	GObjectClass *class;
	GtkWidgetClass *widget_class;

	xtm_process_window_parent_class = g_type_class_peek_parent (klass);
	class = G_OBJECT_CLASS (klass);
	class->finalize = xtm_process_window_finalize;
	widget_class = GTK_WIDGET_CLASS (klass);
	widget_class->show = xtm_process_window_show;
	widget_class->hide = xtm_process_window_hide;
}

static void
xtm_process_window_size_allocate (GtkWidget *widget, GtkAllocation *allocation, gpointer user_data)
{
	XtmProcessWindow *window = (XtmProcessWindow *)user_data;

	g_return_if_fail (gtk_widget_is_toplevel (widget));

	gtk_window_get_size (GTK_WINDOW (widget), &window->width, &window->height);
}

static void
show_settings_dialog (GtkButton *button, gpointer user_data)
{
	XtmProcessWindow *window = (XtmProcessWindow *)user_data;

	g_signal_handler_block (G_OBJECT (window->window), window->handler);
	xtm_settings_dialog_run (window->window);
	g_signal_handler_unblock (G_OBJECT (window->window), window->handler);
}

static void
xtm_process_window_stick_view (GtkAdjustment *adjustment, XtmProcessWindow *window)
{
	if (window->view_stuck)
		gtk_adjustment_set_value (adjustment, 0);
	else if (gtk_adjustment_get_value (adjustment) == 0)
		window->view_stuck = TRUE;
}

static gboolean
xtm_process_window_unstick_view_event (GtkWidget *widget, GdkEvent *event, XtmProcessWindow *window)
{
	GdkScrollDirection dir;
	gdouble y;

	if (!window->view_stuck)
		return FALSE;

	if (event->type == GDK_SCROLL &&
			((gdk_event_get_scroll_direction (event, &dir) && dir == GDK_SCROLL_UP) ||
				(gdk_event_get_scroll_deltas (event, NULL, &y) && y <= 0)))
		return FALSE;

	window->view_stuck = FALSE;

	return FALSE;
}

static void
xtm_process_window_unstick_view_cursor (GtkTreeView *tree_view, XtmProcessWindow *window)
{
	GtkTreePath *cursor, *end;

	if (!window->view_stuck)
		return;

	if (gtk_tree_view_get_visible_range (tree_view, NULL, &end))
	{
		gtk_tree_view_get_cursor (tree_view, &cursor, NULL);
		if (cursor != NULL && gtk_tree_path_compare (cursor, end) >= 0)
			window->view_stuck = FALSE;
		gtk_tree_path_free (cursor);
		gtk_tree_path_free (end);
	}
}

static void
xtm_process_window_init (XtmProcessWindow *window)
{
	GtkWidget *button;

	window->settings = xtm_settings_get_default ();
	window->channel = xfconf_channel_get (CHANNEL);

	window->builder = gtk_builder_new ();
	gtk_builder_add_from_resource (GTK_BUILDER (window->builder),
		"/org/xfce/taskmanager/process-window/process-window.ui",
		NULL);

	window->window = GTK_WIDGET (gtk_builder_get_object (window->builder, "process-window"));
	window->width = xfconf_channel_get_int (window->channel, SETTING_WINDOW_WIDTH, DEFAULT_WINDOW_WIDTH);
	window->height = xfconf_channel_get_int (window->channel, SETTING_WINDOW_HEIGHT, DEFAULT_WINDOW_HEIGHT);
	if (window->width >= 1 && window->height >= 1)
		gtk_window_set_default_size (GTK_WINDOW (window->window), window->width, window->height);

	/* If the window was closed maximized, reopen it maximized again */
	if (xfconf_channel_get_bool (window->channel, SETTING_WINDOW_MAXIMIZED, FALSE))
		gtk_window_maximize (GTK_WINDOW (window->window));

	g_signal_connect_swapped (window->window, "delete-event", G_CALLBACK (emit_delete_event_signal), window);
	g_signal_connect_swapped (window->window, "destroy", G_CALLBACK (emit_destroy_signal), window);
	window->handler = g_signal_connect (window->window, "size-allocate", G_CALLBACK (xtm_process_window_size_allocate), window);
	g_signal_connect_swapped (window->window, "key-press-event", G_CALLBACK (xtm_process_window_key_pressed), window);

	button = GTK_WIDGET (gtk_builder_get_object (window->builder, "button-settings"));
	g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (show_settings_dialog), window);

	button = GTK_WIDGET (gtk_builder_get_object (window->builder, "button-identify"));
#ifdef HAVE_LIBX11
	if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
		g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (xwininfo_clicked_cb), window);
	else
#endif
		gtk_widget_hide (button);

	window->filter_searchbar = GTK_WIDGET (gtk_builder_get_object (window->builder, "filter-searchbar"));
	{
		GtkWidget *toolitem;
		guint refresh_rate;

		g_object_get (window->settings,
			"refresh-rate", &refresh_rate,
			NULL);

		window->vpaned = GTK_WIDGET (gtk_builder_get_object (window->builder, "mainview-vpaned"));
		xfconf_g_property_bind (window->channel, SETTING_HANDLE_POSITION, G_TYPE_INT,
			G_OBJECT (window->vpaned), "position");

		toolitem = GTK_WIDGET (gtk_builder_get_object (window->builder, "graph-cpu"));
		window->cpu_monitor = xtm_process_monitor_new ();
		xtm_process_monitor_set_step_size (XTM_PROCESS_MONITOR (window->cpu_monitor), refresh_rate / 1000.0f);
		xtm_process_monitor_set_type (XTM_PROCESS_MONITOR (window->cpu_monitor), 0);
		gtk_widget_show (window->cpu_monitor);
		gtk_container_add (GTK_CONTAINER (toolitem), window->cpu_monitor);

		toolitem = GTK_WIDGET (gtk_builder_get_object (window->builder, "graph-mem"));
		window->mem_monitor = xtm_process_monitor_new ();
		xtm_process_monitor_set_step_size (XTM_PROCESS_MONITOR (window->mem_monitor), refresh_rate / 1000.0f);
		xtm_process_monitor_set_type (XTM_PROCESS_MONITOR (window->mem_monitor), 1);
		gtk_widget_show (window->mem_monitor);
		gtk_container_add (GTK_CONTAINER (toolitem), window->mem_monitor);

		g_signal_connect_swapped (window->settings, "notify::refresh-rate", G_CALLBACK (monitor_update_step_size), window);
	}

	window->statusbar = xtm_process_statusbar_new ();
	gtk_widget_show (window->statusbar);
	gtk_box_pack_start (GTK_BOX (gtk_builder_get_object (window->builder, "graph-vbox")), window->statusbar, FALSE, FALSE, 0);

	gtk_widget_set_visible (GTK_WIDGET (gtk_builder_get_object (window->builder, "root-warning-box")), geteuid () == 0);

	window->treeview = xtm_process_tree_view_new ();
	gtk_widget_show (window->treeview);
	{
		GtkScrolledWindow *s_window;
		GtkWidget *bar;
		GtkAdjustment *adjust;

		s_window = GTK_SCROLLED_WINDOW (gtk_builder_get_object (window->builder, "scrolledwindow"));
		bar = gtk_scrolled_window_get_vscrollbar (s_window);
		adjust = gtk_scrolled_window_get_vadjustment (s_window);
		window->view_stuck = TRUE;

		gtk_container_add (GTK_CONTAINER (s_window), window->treeview);
		g_signal_connect (adjust, "value-changed", G_CALLBACK (xtm_process_window_stick_view), window);
		g_signal_connect (bar, "button-press-event", G_CALLBACK (xtm_process_window_unstick_view_event), window);
		g_signal_connect (bar, "scroll-event", G_CALLBACK (xtm_process_window_unstick_view_event), window);
		g_signal_connect (window->treeview, "scroll-event", G_CALLBACK (xtm_process_window_unstick_view_event), window);
		g_signal_connect (window->treeview, "cursor-changed", G_CALLBACK (xtm_process_window_unstick_view_cursor), window);
	}

	window->filter_entry = GTK_WIDGET (gtk_builder_get_object (window->builder, "filter-entry"));
	g_signal_connect (G_OBJECT (window->filter_entry), "icon-press", G_CALLBACK (filter_entry_icon_pressed_cb), NULL);
	g_signal_connect (G_OBJECT (window->filter_entry), "changed", G_CALLBACK (filter_entry_keyrelease_handler), window->treeview);
	gtk_widget_set_tooltip_text (window->filter_entry, _("Filter on process name"));
	gtk_widget_grab_focus (window->filter_entry);

	{
		const gchar *const captions[] = { _("Starting task"), _("Changing task"), _("Terminating task") };
		const gchar *styles =
			".a,.b,.c{border-radius:50%}"
			".a{background-color:" XTM_LEGEND_COLOR_STARTING "}"
			".b{background-color:" XTM_LEGEND_COLOR_CHANGING "}"
			".c{background-color:" XTM_LEGEND_COLOR_TERMINATING "}";
		const gchar *const classes[] = { "a", "b", "c" };
		GtkWidget *hbox_legend = GTK_WIDGET (gtk_builder_get_object (window->builder, "legend"));
		GtkCssProvider *provider = gtk_css_provider_new ();

		gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
			GTK_STYLE_PROVIDER (provider),
			GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
		gtk_css_provider_load_from_data (provider, styles, -1, NULL);
		g_object_unref (provider);

		for (guint i = 0; i < G_N_ELEMENTS (captions); ++i)
		{
			GtkWidget *hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);

			GtkWidget *label = gtk_label_new (NULL);
			gtk_widget_set_size_request (label, 16, 16);
			gtk_style_context_add_class (gtk_widget_get_style_context (label), classes[i]);
			gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);

			label = gtk_label_new (captions[i]);
			gtk_label_set_xalign (GTK_LABEL (label), 0.0);
			gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);

			gtk_box_pack_start (GTK_BOX (hbox_legend), hbox, FALSE, FALSE, 0);
		}

		gtk_widget_show_all (hbox_legend);
		g_object_bind_property (window->settings, "show-legend", hbox_legend, "visible", G_BINDING_SYNC_CREATE);
	}
}

static void
xtm_process_window_finalize (GObject *object)
{
	XtmProcessWindow *window = XTM_PROCESS_WINDOW (object);

	g_object_unref (window->settings);
	g_object_unref (window->builder);

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

/**
 * Helper functions
 */

static void
xtm_process_window_store_size (XtmProcessWindow *window)
{
	gboolean maximized = gtk_window_is_maximized (GTK_WINDOW (window->window));

	/* Store whether window is maximized */
	xfconf_channel_set_bool (window->channel, SETTING_WINDOW_MAXIMIZED, maximized);

	if (!maximized)
	{
		/* Store window size */
		xfconf_channel_set_int (window->channel, SETTING_WINDOW_WIDTH, window->width);
		xfconf_channel_set_int (window->channel, SETTING_WINDOW_HEIGHT, window->height);
	}
}

static gboolean
emit_delete_event_signal (XtmProcessWindow *window, GdkEvent *event, GtkWidget *widget)
{
	gboolean ret = FALSE;
	xtm_process_window_store_size (window);
	g_signal_emit_by_name (window, "delete-event", event, &ret);
	return ret;
}

static void
emit_destroy_signal (XtmProcessWindow *window)
{
	xtm_process_window_store_size (window);
	g_signal_emit_by_name (window, "destroy");
}

static gboolean
xtm_process_window_key_pressed (XtmProcessWindow *window, GdkEventKey *event)
{
	gboolean ret = FALSE;

	if (event->keyval == GDK_KEY_Escape &&
			gtk_widget_is_focus (GTK_WIDGET (window->filter_entry)))
	{
		if (xfconf_channel_get_bool (window->channel, SETTING_SHOW_FILTER, FALSE))
			gtk_entry_set_text (GTK_ENTRY (window->filter_entry), "");
		else
			g_signal_emit_by_name (window, "delete-event", event, &ret, G_TYPE_BOOLEAN);
	}
	else if (event->keyval == GDK_KEY_Escape ||
					 (event->keyval == GDK_KEY_q && (event->state & GDK_CONTROL_MASK)))
	{
		g_signal_emit_by_name (window, "delete-event", event, &ret, G_TYPE_BOOLEAN);
		ret = TRUE;
	}
	else if (event->keyval == GDK_KEY_f && (event->state & GDK_CONTROL_MASK))
	{
		gtk_widget_grab_focus (GTK_WIDGET (window->filter_entry));
		xfconf_channel_set_bool (window->channel, SETTING_SHOW_FILTER, TRUE);
		ret = TRUE;
	}

	return ret;
}

static void
monitor_update_step_size (XtmProcessWindow *window)
{
	guint refresh_rate;
	g_object_get (window->settings, "refresh-rate", &refresh_rate, NULL);
	g_object_set (window->cpu_monitor, "step-size", refresh_rate / 1000.0, NULL);
	g_object_set (window->mem_monitor, "step-size", refresh_rate / 1000.0, NULL);
}

/**
 * Class functions
 */

GtkWidget *
xtm_process_window_new (void)
{
	return g_object_new (XTM_TYPE_PROCESS_WINDOW, NULL);
}

void
xtm_process_window_show (GtkWidget *widget)
{
	g_return_if_fail (GTK_IS_WIDGET (widget));
	g_return_if_fail (GTK_IS_WIDGET (XTM_PROCESS_WINDOW (widget)->window));
	gtk_widget_show (XTM_PROCESS_WINDOW (widget)->window);
	gtk_window_present (GTK_WINDOW (XTM_PROCESS_WINDOW (widget)->window));
	GTK_WIDGET_CLASS (xtm_process_window_parent_class)->show (widget);
}

static void
xtm_process_window_hide (GtkWidget *widget)
{
	gint winx, winy;
	g_return_if_fail (GTK_IS_WIDGET (widget));
	if (!GTK_IS_WIDGET (XTM_PROCESS_WINDOW (widget)->window))
		return;
	gtk_window_get_position (GTK_WINDOW (XTM_PROCESS_WINDOW (widget)->window), &winx, &winy);
	gtk_widget_hide (XTM_PROCESS_WINDOW (widget)->window);
	gtk_window_move (GTK_WINDOW (XTM_PROCESS_WINDOW (widget)->window), winx, winy);
	GTK_WIDGET_CLASS (xtm_process_window_parent_class)->hide (widget);
}

GtkTreeModel *
xtm_process_window_get_model (XtmProcessWindow *window)
{
	g_return_val_if_fail (XTM_IS_PROCESS_WINDOW (window), NULL);
	g_return_val_if_fail (XTM_IS_PROCESS_TREE_VIEW (window->treeview), NULL);
	return xtm_process_tree_view_get_model (XTM_PROCESS_TREE_VIEW (window->treeview));
}

void
xtm_process_window_set_system_info (XtmProcessWindow *window, guint num_processes, gfloat cpu, gfloat memory, gchar *memory_str, gfloat swap, gchar *swap_str)
{
	gchar text[100];
	gchar value[4];

	g_return_if_fail (XTM_IS_PROCESS_WINDOW (window));
	g_return_if_fail (GTK_IS_BOX (window->statusbar));

	g_object_set (window->statusbar, "num-processes", num_processes, "cpu", cpu, "memory", memory_str, "swap", swap_str, NULL);

	xtm_process_monitor_add_peak (XTM_PROCESS_MONITOR (window->cpu_monitor), cpu / 100.0f, -1.0);
	g_snprintf (value, sizeof (value), "%.0f", cpu);
	g_snprintf (text, sizeof (text), _("CPU: %s%%"), value);
	gtk_widget_set_tooltip_text (window->cpu_monitor, text);

	xtm_process_monitor_add_peak (XTM_PROCESS_MONITOR (window->mem_monitor), memory / 100.0f, swap / 100.0f);
	g_snprintf (text, sizeof (text), _("Memory: %s"), memory_str);
	gtk_widget_set_tooltip_text (window->mem_monitor, text);
}

void
xtm_process_window_show_swap_usage (XtmProcessWindow *window, gboolean show_swap_usage)
{
	g_return_if_fail (XTM_IS_PROCESS_WINDOW (window));
	g_return_if_fail (GTK_IS_BOX (window->statusbar));
	g_object_set (window->statusbar, "show-swap", show_swap_usage, NULL);
}
