/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2003 Takuro Ashie
 *
 *  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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "kz-prefs-win.h"

#include <string.h>
#include <gdk/gdkkeysyms.h>
#include <glib/gi18n.h>

#include "gobject-utils.h"
#include "kazehakase.h"
#include "kz-icons.h"

/* preference UI */
#include "prefs_ui/prefs_general.h"
#include "prefs_ui/prefs_lang.h"
#include "prefs_ui/prefs_font.h"
#include "prefs_ui/prefs_tab.h"
#include "prefs_ui/prefs_key_accel.h"
#include "prefs_ui/prefs_gesture.h"
#include "prefs_ui/prefs_entry.h"
#include "prefs_ui/prefs_proxy.h"
#include "prefs_ui/prefs_ui_editor.h"
#include "prefs_ui/prefs_privacy.h"
#include "prefs_ui/prefs_external_program.h"
#include "prefs_ui/prefs_history.h"
#include "prefs_ui/prefs_bookmark.h"


enum {
	COLUMN_TERMINATOR = -1,
	COLUMN_ICON_OPEN,
	COLUMN_ICON_CLOSE,
	COLUMN_NAME,
	COLUMN_PRIV_DATA,
	N_COLUMN
};

typedef struct _KzPrefsWinPrivate       KzPrefsWinPrivate;
struct _KzPrefsWinPrivate
{
	GList *page_list;
};
#define KZ_PREFS_WIN_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), KZ_TYPE_PREFS_WIN, KzPrefsWinPrivate))

typedef struct _KzPrefsWinPagePriv
{
	KzPrefsWinPageEntry *entry;
	GtkWidget           *widget;
} KzPrefsWinPagePriv; 


static void       kz_prefs_win_class_init        (KzPrefsWinClass *klass);
static void       kz_prefs_win_init              (KzPrefsWin      *win);
static void       kz_prefs_win_dispose           (GObject         *object);
static void       kz_prefs_win_response          (GtkDialog       *dialog,
						  gint             arg);
static gboolean   kz_prefs_win_delete_event      (GtkWidget        *widget,
						  GdkEventAny      *event);

static GtkWidget *kz_prefs_win_create_nav_tree   (KzPrefsWin *win);
static void       kz_prefs_win_reset_nav_tree    (KzPrefsWin *win);
static void       kz_prefs_win_set_ui_level      (KzPrefsWin *win);
static void       kz_prefs_win_restore_state     (KzPrefsWin *win);
static void       kz_prefs_win_store_state       (KzPrefsWin *win);

static void       cb_profile_global_changed      (KzProfile   *profile,
						  const gchar *section,
						  const gchar *key,
						  const gchar *old_value,
						  KzPrefsWin  *win);
static gboolean   cb_tree_key_press              (GtkWidget *widget,
						  GdkEventKey *event,
						  gpointer data);
static void       cb_tree_cursor_changed         (GtkTreeView *treeview,
						  gpointer data);


static KzPrefsWin *kz_prefs_win_single = NULL;

static GtkDialogClass *parent_class = NULL;
static GList *prefs_page_entries = NULL;


#define ADD_PAGE_ENTRY(func) 						\
{									\
	KzPrefsWinPageEntry *entry;					\
	gint i;								\
	for (i = 0; (entry = func(i)); i++)				\
		prefs_page_entries = g_list_append(prefs_page_entries,	\
						   entry);		\
}


KZ_OBJECT_GET_TYPE(kz_prefs_win, "KzPrefsWin", KzPrefsWin,
		   kz_prefs_win_class_init, kz_prefs_win_init,
		   GTK_TYPE_DIALOG)

static void
kz_prefs_win_class_init (KzPrefsWinClass *klass)
{
	GObjectClass *object_class;
	GtkDialogClass *dialog_class;
	GtkWidgetClass *widget_class;

	parent_class = g_type_class_peek_parent(klass);
	object_class = (GObjectClass *) klass;
	dialog_class = (GtkDialogClass *) klass;
	widget_class = (GtkWidgetClass *) klass;

	object_class->dispose      = kz_prefs_win_dispose;

	dialog_class->response     = kz_prefs_win_response;
	
	widget_class->delete_event = kz_prefs_win_delete_event;
	
	ADD_PAGE_ENTRY(prefs_general_get_entry);
	ADD_PAGE_ENTRY(prefs_lang_get_entry);
	ADD_PAGE_ENTRY(prefs_font_get_entry);
	ADD_PAGE_ENTRY(prefs_tab_get_entry);
	ADD_PAGE_ENTRY(prefs_entry_get_entry);
	ADD_PAGE_ENTRY(prefs_bookmark_get_entry);
	ADD_PAGE_ENTRY(prefs_key_accel_get_entry);
	ADD_PAGE_ENTRY(prefs_gesture_get_entry);
	ADD_PAGE_ENTRY(prefs_proxy_get_entry);
	ADD_PAGE_ENTRY(prefs_privacy_get_entry); 
	/* ADD_PAGE_ENTRY(prefs_ui_editor_get_entry); */
	ADD_PAGE_ENTRY(prefs_external_program_get_entry);
	ADD_PAGE_ENTRY(prefs_history_get_entry);
	
	g_type_class_add_private (object_class, sizeof(KzPrefsWinPrivate));
}


static void
kz_prefs_win_init (KzPrefsWin *win)
{
	GtkWidget *paned, *scrwin, *tree, *notebook;
	KzPrefsWinPrivate *priv = KZ_PREFS_WIN_GET_PRIVATE (win);

	priv->page_list   = NULL;

	gtk_window_set_default_size(GTK_WINDOW(win), 600, 450);
	gtk_window_set_icon(GTK_WINDOW(win), kz_icon);

	gtk_dialog_add_buttons(GTK_DIALOG(win),
			       GTK_STOCK_APPLY,  GTK_RESPONSE_APPLY,
			       GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
			       GTK_STOCK_OK,     GTK_RESPONSE_ACCEPT,
			       NULL);

	/* paned */
	paned = gtk_hpaned_new();
	gtk_container_set_border_width(GTK_CONTAINER (paned), 5);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(win)->vbox), 
			   paned, TRUE, TRUE, 0);
	gtk_widget_show(paned);

	/* scrolled window */
	scrwin = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrwin),
				       GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrwin),
					    GTK_SHADOW_IN);
	gtk_widget_set_size_request(scrwin, 170, -1);
	gtk_widget_show(scrwin);

	/* navtree */
	tree = kz_prefs_win_create_nav_tree(win);
	gtk_container_add(GTK_CONTAINER(scrwin), tree);
	gtk_widget_show(tree);

	/* notebook */
	notebook = gtk_notebook_new();
	gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_TOP);
	gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), TRUE);
	gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
	gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), FALSE);
	gtk_widget_show(notebook);

	gtk_paned_add1(GTK_PANED(paned), scrwin);
	gtk_paned_add2(GTK_PANED(paned), notebook);

	/* initialize member */
	win->paned           = paned;
	win->scrolled_window = scrwin;
	win->tree_view       = tree;
	win->notebook        = notebook;

	/* set default state */
	kz_prefs_win_restore_state(win);
	kz_prefs_win_reset_nav_tree(win);

	/* observe UI Level */
	g_signal_connect(kz_global_profile,
			 "changed::Global",
			 G_CALLBACK(cb_profile_global_changed), win);
}

static void
kz_prefs_win_dispose (GObject *object)
{
	KzPrefsWin *win = KZ_PREFS_WIN(object);
	KzPrefsWinPrivate *priv = KZ_PREFS_WIN_GET_PRIVATE (object);

	g_signal_handlers_disconnect_by_func
		(kz_global_profile,
		 G_CALLBACK(cb_profile_global_changed), win);

	if (priv->page_list)
	{
		g_list_foreach (priv->page_list, (GFunc) g_free, NULL);
		g_list_free(priv->page_list);
		priv->page_list = NULL;
	}

	if (kz_prefs_win_single && kz_prefs_win_single == win)
		kz_prefs_win_single = NULL;

	if (G_OBJECT_CLASS(parent_class)->dispose)
		G_OBJECT_CLASS(parent_class)->dispose(object);
}


static void
kz_prefs_win_response (GtkDialog *dialog, gint arg)
{
	KzPrefsWinPrivate *priv = KZ_PREFS_WIN_GET_PRIVATE (dialog);
	GList *node = priv->page_list;

	for (; node; node = g_list_next(node))
	{
		KzPrefsWinPagePriv *page = node->data;

		if (page->widget && page->entry && page->entry->response)
			page->entry->response(page->widget, arg);
	}

	switch (arg)
	{
	case GTK_RESPONSE_ACCEPT:
	case GTK_RESPONSE_REJECT:
		kz_prefs_win_store_state(KZ_PREFS_WIN(dialog));
		gtk_widget_destroy(GTK_WIDGET(dialog));
		break;
	case GTK_RESPONSE_APPLY:
		break;
	case KZ_RESPONSE_UI_LEVEL_MEDIUM:
		break;
	case KZ_RESPONSE_UI_LEVEL_EXPERT:
		break;
	case KZ_RESPONSE_UI_LEVEL_CUSTOM:
		break;
	default:
		break;
	}
}

static gboolean 
kz_prefs_win_delete_event (GtkWidget *widget, GdkEventAny *event)
{
	kz_prefs_win_store_state(KZ_PREFS_WIN(widget));
	
	return FALSE;
}

GtkWidget *
kz_prefs_win_new (void)
{
	GObject *obj;
	KzPrefsWin *win;

	obj = g_object_new(KZ_TYPE_PREFS_WIN,
			   "title", _("Preference - Kazehakase"),
			   NULL);
	win = KZ_PREFS_WIN(obj);

	return GTK_WIDGET(win);
}

GtkWidget *
kz_prefs_win_get_instance (void)
{
	if (kz_prefs_win_single)
		gtk_widget_show(GTK_WIDGET(kz_prefs_win_single));
	else
		kz_prefs_win_single = KZ_PREFS_WIN(kz_prefs_win_new());

	return GTK_WIDGET(kz_prefs_win_single);
}

static void
kz_prefs_win_create_page (KzPrefsWin *win, KzPrefsWinPagePriv *priv)
{
	gchar *title = NULL;
	GtkWidget *widget, *label;

	if (!priv || !priv->entry) return;
	if (priv->widget) return;

	/* translate page title */
	if (priv->entry->path)
		title = g_path_get_basename (_(priv->entry->path));

	/* create page */
	if (priv->entry->create) {
		widget = priv->entry->create();
		label = gtk_label_new(title);
		gtk_notebook_append_page(GTK_NOTEBOOK(win->notebook), widget, label);
		priv->widget = widget;
		kz_prefs_win_set_ui_level(win);
	}

	g_free(title);
}

void
kz_prefs_win_set_page (KzPrefsWin *win, const gchar *path)
{
	KzPrefsWinPagePriv *priv = NULL;
	GList *node;
	gint num;
	KzPrefsWinPrivate *winpriv = KZ_PREFS_WIN_GET_PRIVATE (win);

	if (!path || !*path)
	{
		if (!winpriv->page_list) return;
		priv = winpriv->page_list->data;
	}
	else
	{
		for (node = winpriv->page_list; node; node = g_list_next(node))
		{
			priv = node->data;
			if (priv->entry && !strcmp(path, priv->entry->path)) 
				break;
			priv = NULL;
		}
	}

	if (!priv)
	{
		if (!winpriv->page_list) return;
		priv = winpriv->page_list->data;
	}

	if (!priv->widget)
		kz_prefs_win_create_page(win, priv);

	if (priv->widget)
		gtk_widget_show(priv->widget);
	else
		return;

	num = gtk_notebook_page_num (GTK_NOTEBOOK(win->notebook),
				     priv->widget);
	if (num >= 0)
		gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), num);
}


static GtkWidget *
kz_prefs_win_create_nav_tree (KzPrefsWin *win)
{
	GtkWidget *tree;
	GtkTreeStore *store;
	GtkTreeViewColumn *col;
	GtkCellRenderer *render;

	store = gtk_tree_store_new(N_COLUMN,
				   GDK_TYPE_PIXBUF, GDK_TYPE_PIXBUF,
				   G_TYPE_STRING, G_TYPE_POINTER);

	tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (tree), TRUE);
	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree), FALSE);

	g_signal_connect (tree, "key_press_event",
			  G_CALLBACK (cb_tree_key_press), win);
	g_signal_connect (tree, "cursor_changed",
			  G_CALLBACK (cb_tree_cursor_changed), win);

	/* name column */
	col = gtk_tree_view_column_new();

	render = gtk_cell_renderer_pixbuf_new ();
	gtk_tree_view_column_pack_start (col, render, FALSE);
	gtk_tree_view_column_add_attribute (col, render,
					    "pixbuf",
					    COLUMN_ICON_CLOSE);
	gtk_tree_view_column_add_attribute (col, render,
					    "pixbuf_expander_open",
					    COLUMN_ICON_OPEN);
	gtk_tree_view_column_add_attribute (col, render,
					    "pixbuf_expander_closed",
					    COLUMN_ICON_CLOSE);

	render = gtk_cell_renderer_text_new ();
	gtk_tree_view_column_pack_start (col, render, TRUE);
	gtk_tree_view_column_add_attribute (col, render, "text", COLUMN_NAME);

	gtk_tree_view_append_column (GTK_TREE_VIEW (tree), col);
	gtk_tree_view_set_expander_column (GTK_TREE_VIEW (tree), col);
	
	g_object_unref(store);
	return tree;
}


static gint
compare_page_entry (gconstpointer a, gconstpointer b)
{
	const KzPrefsWinPagePriv *priv = a;
	const KzPrefsWinPageEntry *entry = b;

	return priv->entry - entry;
}


static gboolean
kz_prefs_win_ensure_parent (KzPrefsWin *win,
			    KzPrefsWinPagePriv *priv,
			    GtkTreeIter *iter)
{
	/* Implement me! */
	return FALSE;
}

static void
kz_prefs_win_reset_nav_tree (KzPrefsWin *win)
{
	GtkTreeView *treeview;
	GtkTreeStore *store;
	GtkTreePath *path;
	GList *node;
	KzPrefsWinPrivate *winpriv;

	g_return_if_fail(KZ_IS_PREFS_WIN(win));

	winpriv = KZ_PREFS_WIN_GET_PRIVATE (win);
	treeview = GTK_TREE_VIEW(win->tree_view);
	store = GTK_TREE_STORE(gtk_tree_view_get_model(treeview));
	gtk_tree_view_get_cursor(treeview, &path, NULL);

	gtk_tree_store_clear(store);
	for (node = prefs_page_entries; node; node = g_list_next(node))
	{
		KzPrefsWinPagePriv *priv;
		KzPrefsWinPageEntry *entry = node->data;
		GtkTreeIter iter, parent_iter;
		gchar *title;
		GList *node;

		/* set private data */
		node = g_list_find_custom(winpriv->page_list, entry,
					  compare_page_entry);
		if (node)
		{
			priv = node->data;
		}
		else
		{
			priv = g_new0(KzPrefsWinPagePriv, 1);
			priv->entry  = entry;
			priv->widget = NULL;
			winpriv->page_list = g_list_append(winpriv->page_list, priv);
		}

		if (!(priv->entry->ui_level & kz_ui_level()))
		{
			if (priv->widget)
				gtk_widget_destroy(priv->widget);
			priv->widget = NULL;
			continue;
		}

		if (kz_prefs_win_ensure_parent (win, priv, &parent_iter))
		{
			gtk_tree_store_append (store, &iter, &parent_iter);
		}
		else
		{
			gtk_tree_store_append (store, &iter, NULL);
		}

		title = g_path_get_basename(_(entry->path));
		gtk_tree_store_set(store, &iter,
				   /*
				   COLUMN_ICON_CLOSE,      pixbuf,
				   COLUMN_ICON_OPEN,       pixbuf_open,
				   */
				   COLUMN_NAME,            title,
				   COLUMN_PRIV_DATA,       priv,
				   COLUMN_TERMINATOR);
		g_free(title);
	}

	if (path) {
		gtk_tree_view_set_cursor(treeview, path, NULL, FALSE);
		gtk_tree_path_free(path);
	}
}

static void
kz_prefs_win_store_state (KzPrefsWin *win)
{
	gint client_x, client_y, width, height;

	g_return_if_fail(KZ_IS_PREFS_WIN(win));

	gdk_window_get_geometry (GTK_WIDGET(win)->window,
				 &client_x, &client_y,
				 &width, &height, NULL);

	KZ_CONF_SET("PreferenceWindow", "width",  width,  INT);
	KZ_CONF_SET("PreferenceWindow", "height", height, INT);
}


static void
kz_prefs_win_restore_state (KzPrefsWin *win)
{
	gint width = 600, height = 450;

	g_return_if_fail(KZ_IS_PREFS_WIN(win));

	KZ_CONF_GET("PreferenceWindow", "width",  width, INT);
	KZ_CONF_GET("PreferenceWindow", "height", height, INT);

	/* size */
	gtk_window_set_default_size(GTK_WINDOW(win), width, height);
}


GtkWidget *
kz_prefs_ui_utils_create_title(const gchar *title)
{
	GtkWidget *vbox, *hbox, *label, *hsep;
	gchar buf[256];
	gint buflen = sizeof(buf) / sizeof(gchar);

	vbox = gtk_vbox_new(FALSE, 0);
	gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);

	hbox = gtk_hbox_new(FALSE, 0);
	gtk_container_set_border_width(GTK_CONTAINER(hbox), 5);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
	gtk_widget_show(hbox);

	g_snprintf(buf, buflen,
		   "<span weight=\"bold\" size=\"x-large\">%s</span>",
		   title);
	label = gtk_label_new(buf);
	gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	gtk_widget_show(label);

	hsep = gtk_hseparator_new();
	gtk_box_pack_start(GTK_BOX(vbox), hsep, FALSE, FALSE, 0);
	gtk_widget_show(hsep);

	return vbox;
}


static void
kz_prefs_win_set_ui_level (KzPrefsWin *win)
{
	gint response;

	switch (kz_ui_level())
	{
	case KZ_UI_LEVEL_BEGINNER:
		response = KZ_RESPONSE_UI_LEVEL_BEGINNER;
		break;
	case KZ_UI_LEVEL_MEDIUM:
		response = KZ_RESPONSE_UI_LEVEL_MEDIUM;
		break;
	case KZ_UI_LEVEL_EXPERT:
		response = KZ_RESPONSE_UI_LEVEL_EXPERT;
		break;
	case KZ_UI_LEVEL_CUSTOM:
		response = KZ_RESPONSE_UI_LEVEL_CUSTOM;
		break;
	default:
		g_warning("Invalid UI Level: %d", kz_ui_level());
		response = KZ_RESPONSE_UI_LEVEL_BEGINNER;
		break;
	}
	g_signal_emit_by_name(win, "response",
			      response);
}


static gboolean
cb_tree_key_press (GtkWidget *widget, GdkEventKey *event, gpointer data)
{
	GtkTreeSelection *selection;
	GtkTreeModel *model;
	GtkTreeIter iter;
	GtkTreePath *treepath;
	gboolean success, retval = FALSE;

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
	if (!selection) return FALSE;
	success = gtk_tree_selection_get_selected (selection, &model, &iter);
	if (!success) return FALSE;
	treepath = gtk_tree_model_get_path (model, &iter);
	if (!treepath) return FALSE;

	switch (event->keyval) {
	case GDK_KP_Enter:
	case GDK_Return:
	case GDK_ISO_Enter:
	case GDK_space:
		if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), treepath))
			gtk_tree_view_collapse_row (GTK_TREE_VIEW (widget), treepath);
		else
			gtk_tree_view_expand_row (GTK_TREE_VIEW (widget), treepath, FALSE);
		retval = TRUE;
		break;
	case GDK_Right:
		gtk_tree_view_expand_row (GTK_TREE_VIEW (widget), treepath, FALSE);
		retval = TRUE;
		break;
	case GDK_Left:
		gtk_tree_view_collapse_row (GTK_TREE_VIEW (widget), treepath);
		retval = TRUE;
		break;
	default:
		break;
	}

	gtk_tree_path_free(treepath);

	return retval;
}


static void
cb_tree_cursor_changed (GtkTreeView *treeview, gpointer data)
{
	KzPrefsWin *win;
	GtkTreeSelection *selection;
	GtkTreeModel *model;
	GtkTreeIter iter;
	KzPrefsWinPagePriv *priv;

	g_return_if_fail(KZ_IS_PREFS_WIN(data));

	win = KZ_PREFS_WIN(data);

	selection = gtk_tree_view_get_selection (treeview);
	gtk_tree_selection_get_selected (selection, &model, &iter);

	gtk_tree_model_get (model, &iter,
			    COLUMN_PRIV_DATA, &priv,
			    COLUMN_TERMINATOR);

	g_return_if_fail (priv);
	g_return_if_fail (priv->entry);

	kz_prefs_win_set_page (win, priv->entry->path);
}


static void
cb_profile_global_changed (KzProfile *profile,
			   const gchar *section, const gchar *key,
			   const gchar *old_value,
			   KzPrefsWin *win)
{
	g_return_if_fail(KZ_IS_PREFS_WIN(win));

	kz_prefs_win_reset_nav_tree(win);

	if (key && !strcmp(key, "ui_level"))
	{
		kz_prefs_win_set_ui_level(win);
	}
}
