/*
 *  Copyright (C) 2002-2004 Marco Pesenti Gritti <mpeseng@tin.it>
 *  Copyright (C) 2005 Peter Harvey <pah06@uow.edu.au>
 *
 *  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.
 *
 *  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.
 *
 *  $Id$
 */

#include "config.h"

#include "ephy-topics-palette.h"
#include "ephy-nodes-cover.h"
#include "ephy-node-common.h"
#include "ephy-bookmarks.h"
#include "ephy-debug.h"

#include <glib/gi18n.h>
#include <gtk/gtktreeselection.h>
#include <gdk/gdkkeysyms.h>

static void ephy_topics_palette_class_init (EphyTopicsPaletteClass *klass);
static void ephy_topics_palette_init (EphyTopicsPalette *editor);

#define EPHY_TOPICS_PALETTE_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_TOPICS_PALETTE, EphyTopicsPalettePrivate))

struct _EphyTopicsPalettePrivate
{
	EphyBookmarks *bookmarks;
	EphyNode *bookmark;
	GtkListStore *store;
	GtkTreeViewColumn *column;
	int mode;
};

enum
{
	PROP_0,
	PROP_BOOKMARKS,
	PROP_BOOKMARK,
	PROP_MODE
};

enum
{
	COLUMN_TITLE,
        COLUMN_NODE,
	COLUMN_SELECTED,
	COLUMNS
};

enum
{
	MODE_GROUPED,
	MODE_LIST,
	MODES
};

static GObjectClass *parent_class = NULL;

GType
ephy_topics_palette_get_type (void)
{
	static GType type = 0;

	if (G_UNLIKELY (type == 0))
	{
		static const GTypeInfo our_info =
		{
			sizeof (EphyTopicsPaletteClass),
			NULL,
			NULL,
			(GClassInitFunc) ephy_topics_palette_class_init,
			NULL,
			NULL,
			sizeof (EphyTopicsPalette),
			0,
			(GInstanceInitFunc) ephy_topics_palette_init
		};

		type = g_type_register_static (GTK_TYPE_TREE_VIEW,
					       "EphyTopicsPalette",
					       &our_info, 0);
	}

	return type;
}

static void
append_topics (EphyTopicsPalette *palette,
	       GtkTreeIter *iter,
	       gboolean *valid,
	       gboolean *first,
	       GPtrArray *topics)
{
	EphyNode *node;
	const char *title;
	gint i;
	
	if (topics->len == 0)
	{
		return;
	}
	 
	if (!*first)
	{
		if (!*valid) gtk_list_store_append (palette->priv->store, iter);
		gtk_list_store_set (palette->priv->store, iter, COLUMN_TITLE, NULL,
				    COLUMN_NODE, NULL, -1);
		*valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (palette->priv->store), iter);
	}

	for (i = 0; i < topics->len ; i++)
	{
		node = g_ptr_array_index (topics, i);
		title = ephy_node_get_property_string (node, EPHY_NODE_KEYWORD_PROP_NAME);
		
		if (!*valid) gtk_list_store_append (palette->priv->store, iter);
		gtk_list_store_set (palette->priv->store, iter, COLUMN_TITLE, title,
				    COLUMN_NODE, node, COLUMN_SELECTED,
				    ephy_node_has_child (node, palette->priv->bookmark), -1);
		*valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (palette->priv->store), iter);
		*first = FALSE;
	}
}

static void
update_list (EphyTopicsPalette *palette)
{
	GPtrArray *children, *bookmarks, *topics;
	EphyNode *node;
	GtkTreeIter iter;
	gint i, priority;
	gboolean valid, first;
	
	gtk_widget_queue_draw (GTK_WIDGET (palette));
	valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (palette->priv->store), &iter);
	first = TRUE;
	
	if (palette->priv->mode == MODE_LIST)
	{
		/* Allocate and fill the suggestions array. */
		node = ephy_bookmarks_get_keywords (palette->priv->bookmarks);
		children = ephy_node_get_children (node);
		topics = g_ptr_array_sized_new (children->len);
		for (i = 0; i < children->len; i++)
		{
			node = g_ptr_array_index (children, i);
			
			priority = ephy_node_get_property_int (node, EPHY_NODE_KEYWORD_PROP_PRIORITY);
			if (priority != EPHY_NODE_NORMAL_PRIORITY)
			  continue;

			g_ptr_array_add (topics, node);
		}

		g_ptr_array_sort (topics, ephy_bookmarks_compare_topic_pointers);		
		append_topics (palette, &iter, &valid, &first, topics);
		g_ptr_array_free (topics, TRUE);
	}
	else if (palette->priv->mode == MODE_GROUPED)
	{
		GPtrArray *suggested, *selected;
		
		/* Allocate and fill the bookmarks array. */
		node = ephy_bookmarks_get_bookmarks (palette->priv->bookmarks);
		children = ephy_node_get_children (node);
		bookmarks = g_ptr_array_sized_new (children->len);
		for (i = 0; i < children->len; i++)
		{
			g_ptr_array_add(bookmarks, g_ptr_array_index (children, i));
		}
		
		/* Allocate and fill the topics array. */
		node = ephy_bookmarks_get_keywords (palette->priv->bookmarks);
		children = ephy_node_get_children (node);
		topics = g_ptr_array_sized_new (children->len);
		suggested = g_ptr_array_sized_new (children->len);
		selected = g_ptr_array_sized_new (children->len);
		for (i = 0; i < children->len; i++)
		{
			node = g_ptr_array_index (children, i);
			
			priority = ephy_node_get_property_int (node, EPHY_NODE_KEYWORD_PROP_PRIORITY);
			if (priority != EPHY_NODE_NORMAL_PRIORITY)
			  continue;
			
			/* We'll consider only bookmarks covered by the same topics as our bookmark. */
			if (ephy_node_has_child (node, palette->priv->bookmark))
			{
				ephy_nodes_remove_not_covered (node, bookmarks);
				g_ptr_array_add (selected, node);
			}
			
			/* We'll onsider only topics that are not already selected for our bookmark. */
			else
			{
				g_ptr_array_add (topics, node);
			}
		}
	
		/* Get the minimum cover of topics for the bookmarks. */
		suggested = ephy_nodes_get_covering (topics, bookmarks, suggested, 0, 0);
		
		for (i = 0; i < suggested->len; i++)
		{
			g_ptr_array_remove_fast (topics, g_ptr_array_index (suggested, i));
		}
		
		/* Add any topics which cover the bookmarks completely in their own right, or 
		   have no bookmarks currently associated with it. */
		for (i = 0; i < topics->len ; i++)
		{
			node = g_ptr_array_index (topics, i);
			if (!ephy_node_has_child (node, palette->priv->bookmark) &&
			     ephy_nodes_covered (node, bookmarks))
			{
				g_ptr_array_add (suggested, node);
				g_ptr_array_remove_index_fast (topics, i);
				i--;
			}
		}
	

		g_ptr_array_sort (selected, ephy_bookmarks_compare_topic_pointers);
		g_ptr_array_sort (suggested, ephy_bookmarks_compare_topic_pointers);
		g_ptr_array_sort (topics, ephy_bookmarks_compare_topic_pointers);
		append_topics (palette, &iter, &valid, &first, selected);
		append_topics (palette, &iter, &valid, &first, suggested);
		append_topics (palette, &iter, &valid, &first, topics);
		g_ptr_array_free (selected, TRUE);
		g_ptr_array_free (suggested, TRUE);
		g_ptr_array_free (bookmarks, TRUE);
		g_ptr_array_free (topics, TRUE);
	}
	
	while (valid)
	{
		valid = gtk_list_store_remove (palette->priv->store, &iter);
	}
}

static void
tree_changed_cb (EphyBookmarks *bookmarks,
		 EphyTopicsPalette *palette)
{
	update_list (palette);
}

static void
node_added_cb (EphyNode *parent,
	       EphyNode *child,
	       EphyTopicsPalette *palette)
{
	update_list (palette);
}

static void
node_changed_cb (EphyNode *parent,
		 EphyNode *child,
		 guint property_id,
		 EphyTopicsPalette *palette)
{
	update_list (palette);
}

static void
node_removed_cb (EphyNode *parent,
		 EphyNode *child,
		 guint index,
		 EphyTopicsPalette *palette)
{
	update_list (palette);
}

static void
ephy_topics_palette_set_property (GObject *object,
		                   guint prop_id,
		                   const GValue *value,
		                   GParamSpec *pspec)
{
	EphyTopicsPalette *palette = EPHY_TOPICS_PALETTE (object);
	EphyNode *node;

	switch (prop_id)
	{
	case PROP_BOOKMARKS:
		palette->priv->bookmarks = g_value_get_object (value);
		node = ephy_bookmarks_get_keywords (palette->priv->bookmarks);
		ephy_node_signal_connect_object (node, EPHY_NODE_CHILD_ADDED,
				   (EphyNodeCallback) node_added_cb, object);
		ephy_node_signal_connect_object (node, EPHY_NODE_CHILD_CHANGED,
				   (EphyNodeCallback) node_changed_cb, object);
		ephy_node_signal_connect_object (node, EPHY_NODE_CHILD_REMOVED,
				   (EphyNodeCallback) node_removed_cb, object);
		g_signal_connect_object (palette->priv->bookmarks, "tree-changed",
					 G_CALLBACK (tree_changed_cb), palette,
					 G_CONNECT_AFTER);
		break;
	case PROP_BOOKMARK:
		palette->priv->bookmark = g_value_get_pointer (value);
		break;
	case PROP_MODE:
		palette->priv->mode = g_value_get_int (value);
		update_list (palette);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
cell_edited (GtkCellRendererText *renderer,
	     const char *path_str,
	     const char *new_text,
	     EphyTopicsPalette *palette)
{
	if (*new_text != 0)
	{
		EphyNode *node;
		node = ephy_bookmarks_add_keyword (palette->priv->bookmarks, new_text);
		ephy_bookmarks_set_keyword (palette->priv->bookmarks, node,
					    palette->priv->bookmark);
	}
	else
	{
		update_list (palette);
	}
}

static void
toggled (GtkCellRendererToggle *cell_renderer,
	 gchar *path,
	 EphyTopicsPalette *palette)
{
	EphyNode *topic;
	GtkTreeModel *model;
	GtkTreeIter iter;
	
	model = gtk_tree_view_get_model (GTK_TREE_VIEW (palette));

	g_return_if_fail(gtk_tree_model_get_iter_from_string (model, &iter, path));

	gtk_tree_model_get (model, &iter, COLUMN_NODE, &topic, -1);
	
	/* Need to protect against toggling separators. */
	if (topic == NULL) return;
	
	if (ephy_node_has_child (topic, palette->priv->bookmark))
	{
		ephy_bookmarks_unset_keyword (palette->priv->bookmarks,
					      topic,
					      palette->priv->bookmark);
	}
	else
	{
		ephy_bookmarks_set_keyword (palette->priv->bookmarks,
					    topic,
					    palette->priv->bookmark);
	}
}

static gboolean
is_separator (GtkTreeModel *model,
	      GtkTreeIter *iter,
	      gpointer data)
{
	EphyNode *node;
	gtk_tree_model_get (model, iter, COLUMN_NODE, &node, -1);
	return (node == NULL);
}

static GObject *
ephy_topics_palette_constructor (GType type,
				 guint n_construct_properties,
				 GObjectConstructParam *construct_params)

{
	GObject *object;
	EphyTopicsPalette *palette;
	EphyTopicsPalettePrivate *priv;
	GtkCellRenderer *renderer;

	object = parent_class->constructor (type, n_construct_properties,
                                            construct_params);
	palette = EPHY_TOPICS_PALETTE (object);
	priv = EPHY_TOPICS_PALETTE_GET_PRIVATE (object);

	priv->store = gtk_list_store_new (COLUMNS, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_BOOLEAN);
	gtk_tree_view_set_model (GTK_TREE_VIEW (object), GTK_TREE_MODEL (priv->store));
	g_object_unref (priv->store);

	priv->column = gtk_tree_view_column_new ();
	
	renderer = gtk_cell_renderer_toggle_new ();
	gtk_tree_view_column_pack_start (priv->column, renderer, FALSE);
	gtk_tree_view_column_add_attribute (priv->column, renderer, "active", COLUMN_SELECTED);
	g_signal_connect (renderer, "toggled", G_CALLBACK (toggled), palette);
	
	renderer = gtk_cell_renderer_text_new ();
	gtk_tree_view_column_pack_start (priv->column, renderer, TRUE);
	gtk_tree_view_column_add_attribute (priv->column, renderer, "text", COLUMN_TITLE);
	g_signal_connect (renderer, "edited", G_CALLBACK (cell_edited), palette);

	gtk_tree_view_append_column (GTK_TREE_VIEW (object), priv->column);

	gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (object), is_separator, NULL, NULL);
	gtk_tree_view_set_enable_search (GTK_TREE_VIEW (object), TRUE);
	gtk_tree_view_set_search_column (GTK_TREE_VIEW (object), COLUMN_TITLE);
	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (object), FALSE);
	gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (object)), GTK_SELECTION_NONE);
	
	update_list (palette);
    
	return object;
}

static void
ephy_topics_palette_init (EphyTopicsPalette *palette)
{
	palette->priv = EPHY_TOPICS_PALETTE_GET_PRIVATE (palette);
}

GtkWidget *
ephy_topics_palette_new (EphyBookmarks *bookmarks,
			 EphyNode *bookmark)
{
	EphyTopicsPalette *palette;

	g_assert (bookmarks != NULL);

	palette = EPHY_TOPICS_PALETTE (g_object_new
				       (EPHY_TYPE_TOPICS_PALETTE,
					"bookmarks", bookmarks,
					"bookmark", bookmark,
					NULL));

	return GTK_WIDGET (palette);
}

static void
ephy_topics_palette_class_init (EphyTopicsPaletteClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	
	parent_class = g_type_class_peek_parent (klass);

	object_class->set_property = ephy_topics_palette_set_property;
	object_class->constructor = ephy_topics_palette_constructor;
	
	g_object_class_install_property (object_class,
					 PROP_BOOKMARKS,
					 g_param_spec_object ("bookmarks",
							      "Bookmarks set",
							      "Bookmarks set",
							      EPHY_TYPE_BOOKMARKS,
							      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
							      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | 
							      G_PARAM_STATIC_BLURB));

	g_object_class_install_property (object_class,
					 PROP_BOOKMARK,
					 g_param_spec_pointer ("bookmark",
							       "Bookmark",
							       "Bookmark",
							       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
							       G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | 
							       G_PARAM_STATIC_BLURB));

	g_object_class_install_property (object_class,
					 PROP_MODE,
					 g_param_spec_int ("mode",
							   "Mode",
							   "Mode",
							   0, MODES-1, 0, 
							   G_PARAM_WRITABLE |
							   G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
							   G_PARAM_STATIC_BLURB));

	g_type_class_add_private (object_class, sizeof(EphyTopicsPalettePrivate));
}