/* -*- 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-tabtree.h"

#include "gobject-utils.h"
#include "intl.h"
#include "kz-tablabel.h"


#define NORMAL_COLOR  NULL
#define LOADING_COLOR "red"


enum
{
	COLUMN_TERMINATOR = -1,
	COLUMN_TITLE,
	COLUMN_TITLE_COLOR,
	COLUMN_EMBEDER,
	N_COLUMNS
};


/* Object class methods */
static void kz_tab_tree_class_init (KzTabTreeClass *klass);
static void kz_tab_tree_init       (KzTabTree      *tabtree);
static void kz_tab_tree_dispose    (GObject        *obj);

/* KzTabTree private methods */
static void         append_children        (KzTabTree    *tabtree,
					    GNode        *parent);
static GtkTreeIter *find_node              (GtkTreeStore *store,
					    KzMozEmbed   *kzembed);
static void         remove_all_moz_signals (KzTabTree    *tabtree);

/* signal handlers for TreeView */
static void     cb_cursor_changed           (GtkTreeView     *tree_view,
					     KzTabTree       *tabtree);
static gboolean cb_tree_view_button_release (GtkWidget       *widget,
					     GdkEventButton  *event,
					     KzTabTree       *tabtree);
static gboolean cb_scroll_event             (GtkWidget       *widget,
					     GdkEventScroll  *event,
					     KzTabTree       *tabtree);

/* signal handlers for Notebook on KzWindow */
static void     cb_switch_page    (GtkNotebook     *notebook,
				   GtkNotebookPage *page,
				   guint            page_num,
				   KzTabTree       *tabtree);

/* signal handlers for KzWindow */
static void     cb_append_tab     (KzWindow        *kz,
				   GtkWidget       *widget,
				   GtkWidget       *parent,
				   KzTabTree       *tabtree);
static void     cb_remove_tab     (KzWindow        *kz,
				   GtkWidget       *widget,
				   KzTabTree       *tabtree);

/* signal handlers for mozilla */
static void     cb_net_start      (GtkMozEmbed     *embed,
				   KzTabTree       *tabtree);
static void     cb_net_stop       (GtkMozEmbed     *embed,
				   KzTabTree       *tabtree);
static void     cb_title_changed  (GtkMozEmbed     *embed,
				   KzTabTree       *tabtree);


static KzSidebarEntry kz_sidebar_tab_tree = 
{
	priority_hint: 0,
	label:         N_("Tab Tree"),
	icon:          NULL,
	create:        kz_tab_tree_new,
};

static GtkVBoxClass *parent_class = NULL;


KzSidebarEntry *
kz_tab_tree_get_entry (gint idx)
{
	if (idx == 0)
		return &kz_sidebar_tab_tree;
	else
		return NULL;
}


KZ_OBJECT_GET_TYPE(kz_tab_tree, "KzTabTree", KzTabTree,
		   kz_tab_tree_class_init, kz_tab_tree_init,
		   GTK_TYPE_VBOX)


static void
kz_tab_tree_class_init (KzTabTreeClass *klass)
{
	GObjectClass *gobject_class;

	parent_class = g_type_class_peek_parent(klass);
	gobject_class = (GObjectClass *) klass;

	gobject_class->dispose = kz_tab_tree_dispose;
}


static void
kz_tab_tree_init (KzTabTree *tabtree)
{
	GtkTreeStore *store;
	GtkWidget *widget, *scrwin;
	GtkTreeView *tree_view;
	GtkCellRenderer *cell;
	GtkTreeViewColumn *column;

	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_box_pack_start(GTK_BOX(tabtree), scrwin, TRUE, TRUE, 0);
	gtk_widget_show(scrwin);

	store = gtk_tree_store_new(N_COLUMNS,
				   G_TYPE_STRING,
				   G_TYPE_STRING,
				   G_TYPE_POINTER);

	widget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
	tree_view = GTK_TREE_VIEW(widget);
	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_view), TRUE);
	gtk_container_add(GTK_CONTAINER(scrwin), GTK_WIDGET(tree_view));
	gtk_widget_show(GTK_WIDGET(tree_view));

	cell = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes
			(_("Title"), cell,
			 "text",       COLUMN_TITLE,
			 "foreground", COLUMN_TITLE_COLOR,
			 NULL);
	gtk_tree_view_append_column(tree_view, column);

	g_signal_connect(G_OBJECT(tree_view), "cursor-changed",
			 G_CALLBACK(cb_cursor_changed), tabtree);
	g_signal_connect(G_OBJECT(tree_view), "button-release-event",
			 G_CALLBACK(cb_tree_view_button_release), tabtree);
	g_signal_connect(G_OBJECT(tree_view), "scroll-event",
			 G_CALLBACK(cb_scroll_event), tabtree);

	tabtree->sidebar   = NULL;
	tabtree->tree_view = GTK_TREE_VIEW(tree_view);
	tabtree->store     = store;
	tabtree->switching = FALSE;
}


static void
kz_tab_tree_dispose (GObject *obj)
{
	KzTabTree *tabtree = KZ_TAB_TREE(obj);
	KzWindow *kz = tabtree->sidebar->kz;

	remove_all_moz_signals(tabtree);

	g_signal_handlers_disconnect_by_func(G_OBJECT(kz->notebook),
					     G_CALLBACK(cb_switch_page),
					     tabtree);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kz),
					     G_CALLBACK(cb_append_tab),
					     tabtree);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kz),
					     G_CALLBACK(cb_remove_tab),
					     tabtree);

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


GtkWidget *
kz_tab_tree_new (KzSidebar *sidebar)
{
	KzTabTree *tabtree;

	tabtree = KZ_TAB_TREE(g_object_new(KZ_TYPE_TAB_TREE, NULL));
	tabtree->sidebar = sidebar;

	if (sidebar->kz)
	{
		GNode *tree = kz_window_get_tree(sidebar->kz);
		if (tree && g_node_first_child(tree))
			append_children(tabtree, tree);
	}

	gtk_tree_view_expand_all(tabtree->tree_view);

	g_signal_connect(G_OBJECT(sidebar->kz->notebook), "switch-page",
			 G_CALLBACK(cb_switch_page), tabtree);
	g_signal_connect(G_OBJECT(sidebar->kz), "append-tab",
			 G_CALLBACK(cb_append_tab), tabtree);
	g_signal_connect(G_OBJECT(sidebar->kz), "remove-tab",
			 G_CALLBACK(cb_remove_tab), tabtree);

	return GTK_WIDGET(tabtree);
}



/*****************************************************************************
 *                                                                           *
 *                       KzTabTree private methods                           *
 *                                                                           *
 *****************************************************************************/
static void
append_children (KzTabTree *tabtree, GNode *parent)
{
	GtkTreeIter iter, *parent_iter = NULL;
	GNode *node;

	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));
	g_return_if_fail(parent);

	if (parent->data)
		parent_iter = find_node(tabtree->store, parent->data);

	node = g_node_first_child(parent);
	for (; node; node = g_node_next_sibling(node))
	{
		KzMozEmbed *kzembed = node->data;
		GNode *child = g_node_first_child(node);

		if (KZ_IS_MOZ_EMBED(kzembed))
		{
			gchar *title = kz_moz_embed_ensure_title(kzembed);
			const gchar *color;
			color = kz_moz_embed_is_loading(kzembed)
				? LOADING_COLOR : NORMAL_COLOR;

			gtk_tree_store_append(tabtree->store,
					      &iter, parent_iter);
			gtk_tree_store_set(tabtree->store, &iter,
					   COLUMN_TITLE,       title,
					   COLUMN_TITLE_COLOR, color,
					   COLUMN_EMBEDER,     kzembed,
					   COLUMN_TERMINATOR);
			g_signal_connect(G_OBJECT(kzembed), "title",
					 G_CALLBACK(cb_title_changed),
					 tabtree);
			g_signal_connect(G_OBJECT(kzembed), "net-start",
					 G_CALLBACK(cb_net_start),
					 tabtree);
			g_signal_connect(G_OBJECT(kzembed), "net-stop",
					 G_CALLBACK(cb_net_stop),
					 tabtree);

			g_free(title);
		}
		else
		{
			g_warning("KzTabTree: Invalid tree item!");
		}

		if (child)
			append_children(tabtree, node);
	}

	if (parent_iter)
		gtk_tree_iter_free(parent_iter);
}


typedef struct _FindNode
{
	KzMozEmbed  *kzembed;
	GtkTreeIter *iter;
} FindNode;


static gboolean
find_node_func (GtkTreeModel *model,
		GtkTreePath *path, GtkTreeIter *iter,
		FindNode *data)
{
	KzMozEmbed *kzembed = NULL;

	if (!iter) return FALSE;

	gtk_tree_model_get(model, iter,
			   COLUMN_EMBEDER, &kzembed,
			   COLUMN_TERMINATOR);
	if (kzembed && data->kzembed && kzembed == data->kzembed)
	{
		data->iter = gtk_tree_iter_copy(iter);
		return TRUE;
	}

	return FALSE;
}


static GtkTreeIter *
find_node (GtkTreeStore *store, KzMozEmbed *kzembed)
{
	FindNode data;
	data.iter = NULL;
	data.kzembed = kzembed;
	gtk_tree_model_foreach(GTK_TREE_MODEL(store),
			       (GtkTreeModelForeachFunc) find_node_func,
			       &data);
	return data.iter;
}


static gboolean
remove_moz_signal_func (GtkTreeModel *model,
			GtkTreePath *path, GtkTreeIter *iter,
			gpointer data)
{
	KzMozEmbed *kzembed = NULL;
	KzTabTree *tabtree;

	g_return_val_if_fail(KZ_IS_TAB_TREE(data), FALSE);
	tabtree = KZ_TAB_TREE(data);

	g_return_val_if_fail(iter, FALSE);

	gtk_tree_model_get(model, iter,
			   COLUMN_EMBEDER, &kzembed,
			   COLUMN_TERMINATOR);
	g_return_val_if_fail(kzembed, FALSE);

	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(cb_title_changed),
					     tabtree);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(cb_net_start),
					     tabtree);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(cb_net_stop),
					     tabtree);

	return FALSE;
}


static void
remove_all_moz_signals (KzTabTree *tabtree)
{
	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));

	gtk_tree_model_foreach(GTK_TREE_MODEL(tabtree->store),
			       remove_moz_signal_func,
			       tabtree);
}



/*****************************************************************************
 *                                                                           *
 *                               Callbacks                                   *
 *                                                                           *
 *****************************************************************************/
static void
cb_cursor_changed(GtkTreeView *tree_view, KzTabTree *tabtree)
{
	GtkTreePath *treepath;
	GtkTreeIter iter;
	KzWindow *kz;
	KzMozEmbed *kzembed = NULL;
	gint num;

	g_return_if_fail(GTK_IS_TREE_VIEW(tree_view));
	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));

	if (tabtree->switching) return;

	kz = tabtree->sidebar->kz;

	gtk_tree_view_get_cursor(tabtree->tree_view, &treepath, NULL);
	if (!treepath) return;

	gtk_tree_model_get_iter(GTK_TREE_MODEL(tabtree->store),
				&iter, treepath);
	gtk_tree_path_free(treepath);
	gtk_tree_model_get(GTK_TREE_MODEL(tabtree->store), &iter,
			   COLUMN_EMBEDER, &kzembed,
			   COLUMN_TERMINATOR);
	if (!kzembed) return;

	num = gtk_notebook_page_num(GTK_NOTEBOOK(kz->notebook),
				    GTK_WIDGET(kzembed));
	g_return_if_fail(num >= 0);

	tabtree->switching = TRUE;
	gtk_notebook_set_current_page(GTK_NOTEBOOK(kz->notebook), num);
	tabtree->switching = FALSE;
}


static gboolean
cb_tree_view_button_release (GtkWidget *widget, GdkEventButton *event,
			     KzTabTree *tabtree)
{
	GtkTreePath *treepath;
	GtkTreeIter iter;
	KzWindow *kz;
	KzMozEmbed *kzembed = NULL;
	GtkWidget *label;

	g_return_val_if_fail(GTK_IS_TREE_VIEW(widget), FALSE);
	g_return_val_if_fail(KZ_IS_TAB_TREE(tabtree), FALSE);

	kz = tabtree->sidebar->kz;

	gtk_tree_view_get_cursor(tabtree->tree_view, &treepath, NULL);
	if (!treepath) return FALSE;

	gtk_tree_model_get_iter(GTK_TREE_MODEL(tabtree->store),
				&iter, treepath);
	gtk_tree_path_free(treepath);
	gtk_tree_model_get(GTK_TREE_MODEL(tabtree->store), &iter,
			   COLUMN_EMBEDER, &kzembed,
			   COLUMN_TERMINATOR);
	if (!kzembed) return FALSE;

	switch (event->button) {
	case 3:
		label = kz_window_get_tab_label(kz, GTK_WIDGET(kzembed));
		g_return_val_if_fail(KZ_IS_TAB_LABEL(label), FALSE);

		kz_tab_label_popup_menu_modal(KZ_TAB_LABEL(label),
					      event->button, event->time);
		return TRUE;
	default:
		break;
	}

	return FALSE;
}


static gboolean
cb_scroll_event (GtkWidget *widget, GdkEventScroll *event, KzTabTree *tabtree)
{
	gboolean retval = FALSE;

	g_return_val_if_fail(GTK_IS_TREE_VIEW(widget), FALSE);
	g_return_val_if_fail(KZ_IS_TAB_TREE(tabtree), FALSE);

	switch (event->direction) {
	case GDK_SCROLL_UP:
		g_signal_emit_by_name(GTK_TREE_VIEW(widget), "move-cursor",
				      GTK_MOVEMENT_DISPLAY_LINES, -1, &retval);
		break;
	case GDK_SCROLL_DOWN:
		g_signal_emit_by_name(GTK_TREE_VIEW(widget), "move-cursor",
				      GTK_MOVEMENT_DISPLAY_LINES, 1, &retval);
		break;
	case GDK_SCROLL_LEFT:
	case GDK_SCROLL_RIGHT:
		break;
	default:
		g_warning ("Invalid scroll direction!");
		break;
	}

	return retval;
}


static void
cb_switch_page (GtkNotebook *notebook, GtkNotebookPage *page,
		guint page_num, KzTabTree *tabtree)
{
	GtkWidget *widget;
	GtkTreeIter *iter;
	GtkTreePath *treepath;

	g_return_if_fail(GTK_IS_NOTEBOOK(notebook));
	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));

	if (tabtree->switching) return;

	widget = gtk_notebook_get_nth_page(notebook, page_num);
	iter = find_node(tabtree->store, KZ_MOZ_EMBED(widget));
	/* g_return_if_fail(iter); */
	if (!iter) return;

	treepath = gtk_tree_model_get_path(GTK_TREE_MODEL(tabtree->store),
					   iter);
	g_return_if_fail(treepath);

	tabtree->switching = TRUE;
	gtk_tree_view_set_cursor(tabtree->tree_view, treepath, NULL, FALSE);
	tabtree->switching = FALSE;

	gtk_tree_path_free(treepath);
	gtk_tree_iter_free(iter);
}


static void
cb_append_tab (KzWindow *kz, GtkWidget *widget, GtkWidget *parent,
	       KzTabTree *tabtree)
{
	GNode *tree = kz_window_get_tree(tabtree->sidebar->kz);

	/* FIXME!! It's a quick hack */
	remove_all_moz_signals(tabtree);
	gtk_tree_store_clear(tabtree->store);
	if (tree && g_node_first_child(tree))
		append_children(tabtree, tree);
	gtk_tree_view_expand_all(tabtree->tree_view);
}


static void
cb_remove_tab (KzWindow *kz, GtkWidget *widget,
	       KzTabTree *tabtree)
{
	GNode *tree = kz_window_get_tree(tabtree->sidebar->kz);

	/* FIXME!! It's a quick hack */
	remove_all_moz_signals(tabtree);
	gtk_tree_store_clear(tabtree->store);
	if (tree && g_node_first_child(tree))
		append_children(tabtree, tree);
	gtk_tree_view_expand_all(tabtree->tree_view);
}


/*****************************************************************************
 *                                                                           *
 *                         Callbacks for Mozilla                             *
 *                                                                           *
 *****************************************************************************/
static void
cb_net_start (GtkMozEmbed *embed, KzTabTree *tabtree)
{
	GtkTreeIter *iter;
	gchar *title;

	g_return_if_fail(KZ_IS_MOZ_EMBED(embed));
	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));

	title = kz_moz_embed_ensure_title(KZ_MOZ_EMBED(embed));

	iter = find_node (tabtree->store, KZ_MOZ_EMBED(embed));
	if (iter)
	{
		gtk_tree_store_set(tabtree->store, iter,
				   COLUMN_TITLE,       title,
				   COLUMN_TITLE_COLOR, LOADING_COLOR,
				   COLUMN_TERMINATOR);
		gtk_tree_iter_free(iter);
	}

	g_free(title);
}


static void
cb_net_stop (GtkMozEmbed *embed, KzTabTree *tabtree)
{
	GtkTreeIter *iter;
	gchar *title;

	g_return_if_fail(KZ_IS_MOZ_EMBED(embed));
	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));

	title = kz_moz_embed_ensure_title(KZ_MOZ_EMBED(embed));

	iter = find_node (tabtree->store, KZ_MOZ_EMBED(embed));
	if (iter)
	{
		gtk_tree_store_set(tabtree->store, iter,
				   COLUMN_TITLE,       title,
				   COLUMN_TITLE_COLOR, NORMAL_COLOR,
				   COLUMN_TERMINATOR);
		gtk_tree_iter_free(iter);
	}

	g_free(title);
}


static void
cb_title_changed (GtkMozEmbed *embed, KzTabTree *tabtree)
{
	gchar *title;
	GtkTreeIter *iter;

	g_return_if_fail(KZ_IS_MOZ_EMBED(embed));
	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));

	title = kz_moz_embed_ensure_title(KZ_MOZ_EMBED(embed));

	iter = find_node (tabtree->store, KZ_MOZ_EMBED(embed));
	if (iter)
	{
		const gchar *color;
		color = kz_moz_embed_is_loading(KZ_MOZ_EMBED(embed))
			? LOADING_COLOR : NORMAL_COLOR;
		gtk_tree_store_set(tabtree->store, iter,
				   COLUMN_TITLE, title,
				   COLUMN_TITLE_COLOR, color,
				   COLUMN_TERMINATOR);
		gtk_tree_iter_free(iter);
	}

	g_free(title);
}
