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

#include "gobject-utils.h"
#include "intl.h"
#include "kz-rss.h"
#include "kz-icons.h"


#define NORMAL_COLOR  NULL
#define LOADING_COLOR "red"


enum
{
	COLUMN_TERMINATOR = -1,
	COLUMN_ICON,
	COLUMN_ICON_SIZE,
	COLUMN_TITLE,
	COLUMN_URI,
	COLUMN_RSS,
	COLUMN_RSS_ITEM,
	N_COLUMNS
};


/* Object class methods */
static void kz_rss_tree_class_init (KzRSSTreeClass *klass);
static void kz_rss_tree_init       (KzRSSTree      *rsstree);
static void kz_rss_tree_dispose    (GObject        *obj);

/* KzRSSTree private methods */
static void kz_rss_tree_refresh_all (KzRSSTree *rsstree);
static void kz_rss_tree_refresh_rss (KzRSSTree *rsstree,
				     KzRSS     *rss);

static GtkTreeIter *find_node_from_rss     (GtkTreeStore *store,
					    KzRSS        *rss);
static void         remove_all_rss_signals (KzRSSTree    *rsstree);

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

/* signal handlers for KzRSS */
static void cb_rss_update_start     (KzRSS *rss, KzRSSTree *rsstree);
static void cb_rss_update_completed (KzRSS *rss, KzRSSTree *rsstree);

/* signal handler for KzRSSList */
static void cb_rss_list_updated (KzRSSList *rsslist, KzRSSTree *rsstree);


static KzSidebarEntry kz_sidebar_rss_tree = 
{
	priority_hint: 0,
	label:         N_("RSS/RDF"),
	icon:          NULL,
	create:        kz_rss_tree_new,
};

static GtkVBoxClass *parent_class = NULL;


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


KZ_OBJECT_GET_TYPE(kz_rss_tree, "KzRSSTree", KzRSSTree,
		   kz_rss_tree_class_init, kz_rss_tree_init,
		   GTK_TYPE_VBOX)


static void
kz_rss_tree_class_init (KzRSSTreeClass *klass)
{
	GObjectClass *gobject_class;

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

	gobject_class->dispose = kz_rss_tree_dispose;
}


static void
kz_rss_tree_init (KzRSSTree *rsstree)
{
	GtkTreeStore *store;
	GtkWidget *widget, *scrwin;
	GtkTreeView *tree_view;
	GtkCellRenderer *cell;
	GtkTreeViewColumn *column;

	/* create scrolled window and tree */
	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(rsstree), scrwin, TRUE, TRUE, 0);
	gtk_widget_show(scrwin);

	store = gtk_tree_store_new(N_COLUMNS,
				   G_TYPE_STRING,
				   GTK_TYPE_ICON_SIZE,
				   G_TYPE_STRING,
				   G_TYPE_STRING,
				   KZ_TYPE_RSS,
				   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));

	/* append a column */
	column = gtk_tree_view_column_new();
	cell = gtk_cell_renderer_pixbuf_new();
	gtk_tree_view_column_pack_start(column, cell, FALSE);
	gtk_tree_view_column_add_attribute(column, cell,
					   "stock-id", COLUMN_ICON);
	gtk_tree_view_column_add_attribute(column, cell,
					   "stock-size", COLUMN_ICON_SIZE);
	cell = gtk_cell_renderer_text_new();
	gtk_tree_view_column_pack_start(column, cell, TRUE);
	gtk_tree_view_column_add_attribute(column, cell,
					   "text", COLUMN_TITLE);
	gtk_tree_view_column_set_title(column, _("Title"));
	gtk_tree_view_append_column(tree_view, column);

	/* set signal handlers */
	g_signal_connect(G_OBJECT(tree_view), "cursor-changed",
			 G_CALLBACK(cb_cursor_changed), rsstree);
	g_signal_connect(G_OBJECT(tree_view), "button-press-event",
			 G_CALLBACK(cb_tree_view_button_press), rsstree);
	g_signal_connect(G_OBJECT(tree_view), "button-release-event",
			 G_CALLBACK(cb_tree_view_button_release), rsstree);
	g_signal_connect(G_OBJECT(tree_view), "scroll-event",
			 G_CALLBACK(cb_scroll_event), rsstree);

	/* initialize struct */
	rsstree->sidebar   = NULL;
	rsstree->tree_view = GTK_TREE_VIEW(tree_view);
	rsstree->store     = store;
	rsstree->rsslist   = kz_rss_list_get_instance();

	/* set signal handler for KzRSSList */
	g_signal_connect(G_OBJECT(rsstree->rsslist), "updated",
			 G_CALLBACK(cb_rss_list_updated), rsstree);

	/* set nodes */
	kz_rss_tree_refresh_all(rsstree);

	gtk_tree_view_expand_all(rsstree->tree_view);
}


static void
kz_rss_tree_dispose (GObject *obj)
{
	KzRSSTree *rsstree = KZ_RSS_TREE(obj);

	if (rsstree->rsslist)
	{
		remove_all_rss_signals(rsstree);
		g_signal_handlers_disconnect_by_func
			(G_OBJECT(rsstree->rsslist),
			 G_CALLBACK(cb_rss_list_updated), rsstree);
		g_object_unref(rsstree->rsslist);
		rsstree->rsslist = NULL;
	}

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


GtkWidget *
kz_rss_tree_new (KzSidebar *sidebar)
{
	KzRSSTree *rsstree;

	rsstree = KZ_RSS_TREE(g_object_new(KZ_TYPE_RSS_TREE, NULL));
	rsstree->sidebar = sidebar;

	return GTK_WIDGET(rsstree);
}


/*****************************************************************************
 *                                                                           *
 *                       KzRSSTree private methods                           *
 *                                                                           *
 *****************************************************************************/
static void
kz_rss_tree_refresh_all (KzRSSTree *rsstree)
{
	GSList *node;

	g_return_if_fail (KZ_IS_RSS_TREE(rsstree));

	remove_all_rss_signals(rsstree);
	gtk_tree_store_clear(rsstree->store);

	for (node = rsstree->rsslist->items; node; node = g_slist_next(node))
	{
		KzRSS *rss = node->data;
		GtkTreeIter iter;
		const gchar *stock;

		if (!rss) continue;

		if (rss->state == KZ_RSS_LOADING)
			stock = KZ_STOCK_RED;
		else
			stock = KZ_STOCK_GREEN;

		gtk_tree_store_append(rsstree->store, &iter, NULL);
		/* FIXME!! increase refcount? or weekref? */
		gtk_tree_store_set(rsstree->store, &iter,
				   COLUMN_ICON,      stock,
				   COLUMN_ICON_SIZE, GTK_ICON_SIZE_MENU,
				   COLUMN_TITLE,     rss->title,
				   COLUMN_URI,       rss->uri,
				   COLUMN_RSS,       rss,
				   COLUMN_TERMINATOR);

		g_signal_connect(G_OBJECT(rss), "update-start",
				 G_CALLBACK(cb_rss_update_start), rsstree);
		g_signal_connect(G_OBJECT(rss), "update-completed",
				 G_CALLBACK(cb_rss_update_completed), rsstree);

		kz_rss_tree_refresh_rss(rsstree, rss);
	}
}


static void
kz_rss_tree_refresh_rss (KzRSSTree *rsstree, KzRSS *rss)
{
	GtkTreeIter *parent_iter, iter;
	GtkTreePath *treepath;
	GSList *node;
	gboolean expand;

	g_return_if_fail(rsstree);
	g_return_if_fail(KZ_IS_RSS(rss));

	parent_iter = find_node_from_rss(rsstree->store, rss);
	if (!parent_iter) return;

	treepath = gtk_tree_model_get_path(GTK_TREE_MODEL(rsstree->store),
					   parent_iter);
	expand = gtk_tree_view_row_expanded(rsstree->tree_view, treepath);

	/* clean children */
	while (gtk_tree_model_iter_children(GTK_TREE_MODEL(rsstree->store),
					    &iter, parent_iter))
	{
		gtk_tree_store_remove(rsstree->store, &iter);
	}

	/* append children */
	for (node = rss->items; node; node = g_slist_next(node))
	{
		KzRSSItem *item = node->data;

		if (!item) continue;

		gtk_tree_store_append(rsstree->store, &iter, parent_iter);
		gtk_tree_store_set(rsstree->store, &iter,
				   COLUMN_TITLE,    item->title,
				   COLUMN_URI,      item->link,
				   COLUMN_RSS,      rss,
				   COLUMN_RSS_ITEM, item,
				   COLUMN_TERMINATOR);
	}

	gtk_tree_view_expand_row(rsstree->tree_view, treepath, TRUE);

	gtk_tree_path_free(treepath);
	gtk_tree_iter_free(parent_iter);
}


typedef struct _FindNodeRSS
{
	KzRSS       *rss;
	GtkTreeIter *iter;
} FindNodeRSS;


static gboolean
find_node_func (GtkTreeModel *model,
		GtkTreePath *path, GtkTreeIter *iter,
		FindNodeRSS *data)
{
	KzRSS *rss = NULL;
	KzRSSItem *item = NULL;

	if (!iter) return FALSE;

	gtk_tree_model_get(model, iter,
			   COLUMN_RSS,      &rss,
			   COLUMN_RSS_ITEM, &item,
			   COLUMN_TERMINATOR);
	if (rss && data->rss && rss == data->rss && !item)
	{
		data->iter = gtk_tree_iter_copy(iter);
		return TRUE;
	}

	return FALSE;
}


static GtkTreeIter *
find_node_from_rss (GtkTreeStore *store, KzRSS *rss)
{
	FindNodeRSS data;

	data.iter = NULL;
	data.rss  = rss;

	gtk_tree_model_foreach(GTK_TREE_MODEL(store),
			       (GtkTreeModelForeachFunc) find_node_func,
			       &data);

	return data.iter;
}


static gboolean
remove_rss_signal_func (GtkTreeModel *model,
			GtkTreePath *path, GtkTreeIter *iter,
			gpointer data)
{
	KzRSS *rss = NULL;
	KzRSSTree *rsstree;

	g_return_val_if_fail(KZ_IS_RSS_TREE(data), FALSE);
	rsstree = KZ_RSS_TREE(data);

	g_return_val_if_fail(iter, FALSE);

	gtk_tree_model_get(model, iter,
			   COLUMN_RSS, &rss,
			   COLUMN_TERMINATOR);

	if (rss)
	{
		g_signal_handlers_disconnect_by_func
			(G_OBJECT(rss),
			 G_CALLBACK(cb_rss_update_start),
			 rsstree);
		g_signal_handlers_disconnect_by_func
			(G_OBJECT(rss),
			 G_CALLBACK(cb_rss_update_completed),
			 rsstree);
	}

	return FALSE;
}


static void
remove_all_rss_signals (KzRSSTree *rsstree)
{
	g_return_if_fail(KZ_IS_RSS_TREE(rsstree));

	gtk_tree_model_foreach(GTK_TREE_MODEL(rsstree->store),
			       remove_rss_signal_func,
			       rsstree);
}


/*****************************************************************************
 *                                                                           *
 *                               Callbacks                                   *
 *                                                                           *
 *****************************************************************************/
static void
cb_cursor_changed(GtkTreeView *tree_view, KzRSSTree *rsstree)
{
}


static gboolean
cb_tree_view_button_press (GtkWidget *widget, GdkEventButton *event,
			   KzRSSTree *rsstree)
{
	GtkTreePath *treepath = NULL;
	GtkTreeIter iter;
	GtkTreeViewColumn *column;
	gboolean success;
	KzWindow *kz;
	KzRSS *rss = NULL;
	KzRSSItem *item = NULL;
	const gchar *link = NULL;

	g_return_val_if_fail(GTK_IS_TREE_VIEW(widget), FALSE);
	g_return_val_if_fail(KZ_IS_RSS_TREE(rsstree), FALSE);

	kz = rsstree->sidebar->kz;

	/* get node  */
	success = gtk_tree_view_get_path_at_pos(rsstree->tree_view,
						event->x, event->y,
						&treepath, &column,
						NULL, NULL);
	/* get URI */
	if (success)
	{
		gtk_tree_model_get_iter(GTK_TREE_MODEL(rsstree->store),
					&iter, treepath);
		gtk_tree_model_get(GTK_TREE_MODEL(rsstree->store), &iter,
				   COLUMN_RSS,      &rss,
				   COLUMN_RSS_ITEM, &item,
				   COLUMN_TERMINATOR);
		if (item)
			link = item->link;
		else if (rss)
			link = rss->rss_link;
	}

	/* open URI or show popup menu */
	if (event->button == 1 && event->type == GDK_2BUTTON_PRESS)
	{
		if (link && *link)
			kz_window_load_url(kz, link);
	}
	else if (event->button == 2)
	{
		if (link && *link)
			kz_window_open_new_tab(kz, link);
	}
	else if (event->button == 3)
	{
		g_print("show popup menu.\n");
	}

	if (treepath)
		gtk_tree_path_free(treepath);

	return FALSE;
}


static gboolean
cb_tree_view_button_release (GtkWidget *widget, GdkEventButton *event,
			     KzRSSTree *rsstree)
{
	return FALSE;
}


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

	g_return_val_if_fail(GTK_IS_TREE_VIEW(widget), FALSE);
	g_return_val_if_fail(KZ_IS_RSS_TREE(rsstree), 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_rss_update_start (KzRSS *rss, KzRSSTree *rsstree)
{
	GtkTreeIter *iter;

	g_return_if_fail(KZ_IS_RSS_TREE(rsstree));
	g_return_if_fail(KZ_IS_RSS(rss));

	/* update icon */
	iter = find_node_from_rss(rsstree->store, rss);
	if (!iter) return;

	gtk_tree_store_set(rsstree->store, iter,
			   COLUMN_ICON, KZ_STOCK_RED,
			   COLUMN_TERMINATOR);

	gtk_tree_iter_free(iter);
}


static void
cb_rss_update_completed (KzRSS *rss, KzRSSTree *rsstree)
{
	GtkTreeIter *iter;

	g_return_if_fail(KZ_IS_RSS_TREE(rsstree));
	g_return_if_fail(KZ_IS_RSS(rss));

	iter = find_node_from_rss(rsstree->store, rss);
	if (!iter) return;

	/* update icon */
	gtk_tree_store_set(rsstree->store, iter,
			   COLUMN_ICON, KZ_STOCK_GREEN,
			   COLUMN_TERMINATOR);

	gtk_tree_iter_free(iter);

	/* update children */
	kz_rss_tree_refresh_rss (rsstree, rss);
}


static void
cb_rss_list_updated (KzRSSList *rsslist, KzRSSTree *rsstree)
{
	g_return_if_fail(KZ_IS_RSS_LIST(rsslist));
	g_return_if_fail(KZ_IS_RSS_TREE(rsstree));

	kz_rss_tree_refresh_all(rsstree);
}
