/* 
 *  Copyright (C) 2002  Ricardo Fernándezs Pascual <ric@users.sourceforge.net>
 *
 *  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.
 */

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

#include "bookmarks-tree-model.h"
#include "galeon-debug.h"
#include <gtk/gtktreednd.h>
#include <glib-object.h>

#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);

#define DEBUG_PATH(path) \
	{ \
		gchar *s_patch = gtk_tree_path_to_string (path); \
		g_print (#path " = %s\n", s_patch); \
		g_free (s_patch); \
	}


	

#define VALID_ITER(iter, tree_model)	(iter!= NULL && iter->user_data != NULL \
					&& tree_model->stamp == iter->stamp)

static void		gb_tree_model_init		(GbTreeModel *btree_model);
static void		gb_tree_model_class_init	(GbTreeModelClass *btree_model_class);
static void		gb_tree_model_tree_model_init	(GtkTreeModelIface *iface);
static void		gb_tree_model_drag_source_init	(GtkTreeDragSourceIface *iface);
static void		gb_tree_model_drag_dest_init	(GtkTreeDragDestIface *iface);
static void		gb_tree_model_finalize_impl	(GObject *object);
static guint		gb_tree_model_get_flags		(GtkTreeModel *tree_model);
static gint		gb_tree_model_get_n_columns	(GtkTreeModel *tree_model);
static GType		gb_tree_model_get_column_type	(GtkTreeModel *tree_model,
							 gint index);
static gboolean		gb_tree_model_get_iter		(GtkTreeModel *tree_model,
							 GtkTreeIter *iter,
							 GtkTreePath *path);
static GtkTreePath *	gb_tree_model_get_path		(GtkTreeModel *tree_model,
							 GtkTreeIter *iter);
static void		gb_tree_model_get_value		(GtkTreeModel *tree_model,
							 GtkTreeIter *iter,
							 gint column,
							 GValue *value);
static gboolean		gb_tree_model_iter_next		(GtkTreeModel *tree_model,
							 GtkTreeIter *iter);
static gboolean		gb_tree_model_iter_children	(GtkTreeModel *tree_model,
							 GtkTreeIter *iter,
							 GtkTreeIter *parent);
static gboolean		gb_tree_model_iter_has_child	(GtkTreeModel *tree_model,
							 GtkTreeIter *iter);
static gint		gb_tree_model_iter_n_children	(GtkTreeModel *tree_model,
							 GtkTreeIter *iter);
static gboolean		gb_tree_model_iter_nth_child	(GtkTreeModel *tree_model,
							 GtkTreeIter *iter,
							 GtkTreeIter *parent,
							 gint n);
static gboolean		gb_tree_model_iter_parent	(GtkTreeModel *tree_model,
							 GtkTreeIter *iter,
							 GtkTreeIter *child);

static void		gb_tree_model_set_roots		(GbTreeModel *btree_model,
							 GSList *roots);


/* DND interfaces */
static gboolean		gb_tree_model_drag_data_delete	(GtkTreeDragSource *drag_source,
							 GtkTreePath *path);
static gboolean		gb_tree_model_drag_data_get	(GtkTreeDragSource *drag_source,
							 GtkTreePath *path,
							 GtkSelectionData *selection_data);
static gboolean		gb_tree_model_drag_data_received (GtkTreeDragDest *drag_dest,
							  GtkTreePath *dest,
							  GtkSelectionData *selection_data);
static gboolean		gb_tree_model_row_drop_possible	(GtkTreeDragDest *drag_dest,
							 GtkTreePath *dest_path,
							 GtkSelectionData *selection_data);

/* helper functions */
static gboolean		gb_tree_model_is_root		(GbTreeModel *m, GbBookmark *f, int *i);
static GbBookmark *	gb_tree_model_get_nth_root	(GbTreeModel *m, int i);

static void		gb_tree_model_descendant_modified_cb (GbFolder *f, GbBookmark *b, gpointer data);
static void		gb_tree_model_modified_cb (GbBookmark *b, gpointer data);
static void 		gb_tree_model_descendant_added_cb (GbFolder *f, GbFolder *p, GbBookmark *b,
							   int pos, gpointer data);
static void 		gb_tree_model_descendant_removed_cb (GbFolder *f, GbFolder *p, GbBookmark *b, 
							     int pos, gpointer data);


static GObjectClass *parent_class = NULL;


GtkType
gb_tree_model_get_type (void)
{
	static GType btree_model_type = 0;
	
	if (!btree_model_type)
	{
		static const GTypeInfo btree_model_info =
			{
				sizeof (GbTreeModelClass),
				NULL,		/* base_init */
				NULL,		/* base_finalize */
				(GClassInitFunc) gb_tree_model_class_init,
				NULL,		/* class_finalize */
				NULL,		/* class_data */
				sizeof (GbTreeModel),
				0, /* n_preallocs */
				(GInstanceInitFunc) gb_tree_model_init
			};
		
		static const GInterfaceInfo tree_model_info =
			{
				(GInterfaceInitFunc) gb_tree_model_tree_model_init,
				NULL,
				NULL
			};
		
		static const GInterfaceInfo drag_source_info =
			{
				(GInterfaceInitFunc) gb_tree_model_drag_source_init,
				NULL,
				NULL
			};
		
		static const GInterfaceInfo drag_dest_info =
			{
				(GInterfaceInitFunc) gb_tree_model_drag_dest_init,
				NULL,
				NULL
			};
		
		btree_model_type = g_type_register_static (G_TYPE_OBJECT, "GbTreeModel", 
							   &btree_model_info, 0);
		
		g_type_add_interface_static (btree_model_type,
					     GTK_TYPE_TREE_MODEL,
					     &tree_model_info);
		g_type_add_interface_static (btree_model_type,
					     GTK_TYPE_TREE_DRAG_SOURCE,
					     &drag_source_info);
		g_type_add_interface_static (btree_model_type,
					     GTK_TYPE_TREE_DRAG_DEST,
					     &drag_dest_info);
		
	}
	
	return btree_model_type;
}

static void
gb_tree_model_class_init (GbTreeModelClass *class)
{
	GObjectClass *object_class;
	
	parent_class = g_type_class_peek_parent (class);
	object_class = (GObjectClass *) class;
	
	object_class->finalize = gb_tree_model_finalize_impl;
}

static void
gb_tree_model_tree_model_init (GtkTreeModelIface *iface)
{
	iface->get_flags = gb_tree_model_get_flags;
	iface->get_n_columns = gb_tree_model_get_n_columns;
	iface->get_column_type = gb_tree_model_get_column_type;
	iface->get_iter = gb_tree_model_get_iter;
	iface->get_path = gb_tree_model_get_path;
	iface->get_value = gb_tree_model_get_value;
	iface->iter_next = gb_tree_model_iter_next;
	iface->iter_children = gb_tree_model_iter_children;
	iface->iter_has_child = gb_tree_model_iter_has_child;
	iface->iter_n_children = gb_tree_model_iter_n_children;
	iface->iter_nth_child = gb_tree_model_iter_nth_child;
	iface->iter_parent = gb_tree_model_iter_parent;
}

static void
gb_tree_model_drag_source_init (GtkTreeDragSourceIface *iface)
{
	iface->drag_data_delete = gb_tree_model_drag_data_delete;
	iface->drag_data_get = gb_tree_model_drag_data_get;
}

static void
gb_tree_model_drag_dest_init (GtkTreeDragDestIface *iface)
{
	iface->drag_data_received = gb_tree_model_drag_data_received;
	iface->row_drop_possible = gb_tree_model_row_drop_possible;
}

static gboolean
gb_tree_model_default_check_func (GbBookmark *b, gpointer data)
{
	return FALSE;
}

static void
gb_tree_model_init (GbTreeModel *btree_model)
{
	btree_model->roots = NULL;
	btree_model->only_folders = FALSE;
	btree_model->hide_roots = FALSE;
	btree_model->check_func = gb_tree_model_default_check_func;
	btree_model->check_func_data = NULL;
	do
	{
		btree_model->stamp = g_random_int ();
	}
	while (btree_model->stamp == 0);
}
#include <string.h>

static void
gb_tree_model_set_only_folders_recursively_emit_signals (GbTreeModel *tm, GbBookmark *lb, int rootindex)
{
	GtkTreeIter iter;
	GtkTreePath *path;
#if 0
	static int ind = 0;
#define D(msg,n,path) { \
	gchar *t = g_strdup (n); \
	int j;\
	if (strlen (t) > 10) t[10] = '\0'; \
	for (j = 0; j < ind; ++j) g_print ("    ");\
	g_print (msg, t); \
	for (j = 10 - ind; j > 0; --j) g_print ("    ");\
	DEBUG_PATH (path); \
	g_free (t);\
	ind++;\
}
#endif
	
	iter.user_data = lb;
	iter.user_data2 = GINT_TO_POINTER (rootindex);
	iter.stamp = tm->stamp;
	
	path = gb_tree_model_get_path ((GtkTreeModel *) tm, &iter);

#if 0
	{
		GtkTreeIter ij;
		gb_tree_model_get_iter ((GtkTreeIter *) tm, &ij, path);
		g_assert (ij.stamp == iter.stamp);
		g_assert (ij.user_data == iter.user_data);
		D ("+%-10s ", lb->name, path);
	}
#endif	
	gtk_tree_model_row_has_child_toggled ((GtkTreeModel *) tm,
					      path,
					      &iter);
	
	if (GB_IS_FOLDER (lb) && (!gb_bookmark_is_alias (lb) || gb_tree_model_get_nth_root (tm, rootindex) == lb))
	{
		GbBookmark *bi;
		gtk_tree_path_append_index (path, 0);
		for (bi = GB_FOLDER (lb)->child; bi; bi = bi->next)
		{
			GtkTreeIter iterbi;
			iterbi.user_data = bi;
			iterbi.stamp = tm->stamp;

			if (GB_IS_FOLDER (bi))
			{
				gtk_tree_model_row_has_child_toggled ((GtkTreeModel *) tm,
								      path,
								      &iterbi);
				gtk_tree_path_next (path);
			}
			else
			{
				if (tm->only_folders)
				{
					gtk_tree_model_row_deleted ((GtkTreeModel *) tm, path);
				}
				else
				{
					
					gtk_tree_model_row_inserted ((GtkTreeModel *) tm, path, &iterbi);
					gtk_tree_path_next (path);
				}
			}
		}
		gtk_tree_path_up (path);

		for (bi = GB_FOLDER (lb)->child; bi; bi = bi->next)
		{
			if (GB_IS_FOLDER (bi))
			{
				gb_tree_model_set_only_folders_recursively_emit_signals (tm, bi, rootindex);
			}
		}
	}
	
	gtk_tree_path_free (path);
#if 0
	ind--;
#endif
}

void
gb_tree_model_set_only_folders (GbTreeModel *tm, gboolean value)
{
	if (tm->only_folders != value)
	{
		int i = 0;
		GSList *l;
		tm->only_folders = value;
		
		for (l = tm->roots; l; l = l->next)
		{
			{
				int j;
				g_assert (gb_tree_model_is_root (tm, l->data, &j));
				g_assert (i == j);
			}
			gb_tree_model_set_only_folders_recursively_emit_signals (tm, l->data, i);
			++i;
		}
	}
}

gboolean
gb_tree_model_get_only_folders (GbTreeModel *tm)
{
	return tm->only_folders;
}

void
gb_tree_model_set_hide_roots (GbTreeModel *tm, gboolean value)
{
	if (tm->hide_roots != value)
	{
		GSList *l;
		tm->hide_roots = value;
		
		for (l = tm->roots; l; l = l->next)
		{
			/* TODO: should notify the listeners of the lots of changes! */
		}
	}
}

gboolean
gb_tree_model_get_hide_roots (GbTreeModel *tm)
{
	return tm->hide_roots;
}

/**
 * gb_tree_model_new:
 * @root: the root bookmark for this model
 *
 * Return value: a new #GbTreeModel
 **/
GbTreeModel *
gb_tree_model_new (GSList *roots)
{
	GbTreeModel *retval;
	
	retval = GB_TREE_MODEL (g_object_new (GB_TYPE_TREE_MODEL, NULL));
	gb_tree_model_set_roots (retval, roots);
	
	return retval;
}

GbTreeModel *
gb_tree_model_new_for_set (GbBookmarkSet *set)
{
	GbTreeModel *ret;
	GSList *l;
	
	g_return_val_if_fail (GB_IS_BOOKMARK_SET (set), NULL);

	l = g_slist_prepend (NULL, set->root);
	ret = gb_tree_model_new (l);
	g_slist_free (l);
	return ret;
}

static void 
gb_tree_model_set_roots (GbTreeModel *btree_model, GSList *roots)
{
	GSList *l;

	/* call this only once, without NULL as roots */
	g_return_if_fail (btree_model->roots == NULL);
	g_return_if_fail (roots != NULL);

	btree_model->roots = g_slist_copy (roots);
	if (btree_model->roots)
	{
		if (GB_BOOKMARK (btree_model->roots->data)->set)
		{
			btree_model->set = GB_BOOKMARK (btree_model->roots->data)->set;
			g_object_ref (G_OBJECT (btree_model->set));
		}
	}
	else
	{
		btree_model->set = NULL;
	}

	for (l = btree_model->roots; l != NULL; l = l->next)
	{
		if (GB_IS_FOLDER (l->data))
		{
			g_signal_connect (l->data, "descendant-modified", 
					  G_CALLBACK (gb_tree_model_descendant_modified_cb), btree_model);
			g_signal_connect (l->data, "descendant-added", 
					  G_CALLBACK (gb_tree_model_descendant_added_cb), btree_model);
			g_signal_connect (l->data, "descendant-removed", 
					  G_CALLBACK (gb_tree_model_descendant_removed_cb), btree_model);
		}
		g_signal_connect (l->data, "modified", 
				  G_CALLBACK (gb_tree_model_modified_cb), btree_model);
		g_object_ref (l->data);
	}
}

static void
gb_tree_model_finalize_impl (GObject *object)
{
	GSList *l;
	GbTreeModel *m = GB_TREE_MODEL (object);
	
	LOG ("Finalizing a GbTreeModel");

	if (m->set)
	{
		g_object_unref (G_OBJECT (m->set));
	}
	for (l = m->roots; l != NULL; l = l->next)
	{
		g_signal_handlers_disconnect_matched (l->data, G_SIGNAL_MATCH_DATA, 0, 0, 
						      NULL, NULL, m);
		g_object_unref (G_OBJECT (l->data));
	}
	g_slist_free (m->roots);
	(* parent_class->finalize) (object);
}

/* fulfill the GtkTreeModel requirements */

static guint
gb_tree_model_get_flags (GtkTreeModel *tree_model)
{
	return 0;
}

static gint
gb_tree_model_get_n_columns (GtkTreeModel *tree_model)
{
	return GB_TREE_MODEL_NUM_COLUMS;
}

static GType
gb_tree_model_get_column_type (GtkTreeModel *tree_model,
			       gint index)
{
	g_return_val_if_fail (GB_IS_TREE_MODEL (tree_model), G_TYPE_INVALID);
	g_return_val_if_fail ((index < GB_TREE_MODEL_NUM_COLUMS) && (index >= 0), G_TYPE_INVALID);
	
	switch (index)
	{
	case GB_TREE_MODEL_COL_ICON:
		return GDK_TYPE_PIXBUF;
		break;
	case GB_TREE_MODEL_COL_TITLE:
	case GB_TREE_MODEL_COL_URL:
		return G_TYPE_STRING;
		break;
	case GB_TREE_MODEL_COL_CHECK:
		return G_TYPE_BOOLEAN;
	default:
		g_assert_not_reached ();
		return G_TYPE_INVALID;
		break;
	}
}

static gboolean
gb_tree_model_get_iter (GtkTreeModel *tree_model,
			GtkTreeIter *iter,
			GtkTreePath *path)
{
	GbTreeModel *btree_model = (GbTreeModel *) tree_model;
	GtkTreeIter parent;
	gint *indices;
	gint depth, i;
	
	g_return_val_if_fail (GB_IS_TREE_MODEL (btree_model), FALSE);
	
	indices = gtk_tree_path_get_indices (path);
	depth = gtk_tree_path_get_depth (path);
	
	iter->stamp = btree_model->stamp;
	iter->user_data = btree_model->roots;
	iter->user_data2 = NULL; /* does not really matter here */
	
	for (i = 0; i < depth; i++)
	{
		parent = *iter;
		if (! gb_tree_model_iter_nth_child (tree_model, iter, &parent, indices[i]))
		{
			return FALSE;
		}
	}
	return TRUE;
}

static int 
gb_tree_model_bookmark_child_index (GbTreeModel *btree_model, GbBookmark *child)
{
	GbFolder *parent = child->parent;

	if (btree_model->only_folders)
	{
		GbBookmark *c;
		gint i = 0;
		for  (c = parent->child; c && c != child; c = c->next)
		{
			if (GB_IS_FOLDER (c))
			{
				++i;
			}
		}
		return i;
	}
	else
	{
		return gb_folder_get_child_index (parent, child);
	}
}

static GtkTreePath *
gb_tree_model_get_path (GtkTreeModel *tree_model,
			GtkTreeIter *iter)
{
	GtkTreePath *retval;
	GbBookmark *b_it;
	int rootindex;
	GbBookmark *root;
	GbBookmark *realroot;
	gint i = 0;
	GbTreeModel *btree_model = (GbTreeModel *) tree_model;
	
	g_return_val_if_fail (GB_IS_TREE_MODEL (tree_model), NULL);
	g_return_val_if_fail (iter != NULL, NULL);
	g_return_val_if_fail (iter->user_data != NULL, NULL);
	g_return_val_if_fail (iter->stamp == btree_model->stamp, NULL);
	
	if (iter->user_data == btree_model->roots)
	{
		return gtk_tree_path_new ();
	}
	
	b_it = iter->user_data;
	rootindex = GPOINTER_TO_INT (iter->user_data2);
	root = gb_tree_model_get_nth_root (btree_model, rootindex);
	realroot = gb_bookmark_real_bookmark (root);

	if (root == b_it || realroot == b_it)
	{
		retval = gtk_tree_path_new ();
		g_return_val_if_fail (!btree_model->hide_roots, retval);
		gtk_tree_path_append_index (retval, rootindex);
	}
	else
	{
		GbFolder *parent = b_it->parent;
		GtkTreeIter tmp_iter = *iter;

		g_assert (parent != NULL);
		g_assert (!gb_bookmark_is_alias (parent));

		tmp_iter.user_data = parent;
		tmp_iter.user_data2 = GINT_TO_POINTER (rootindex);
		
		if (btree_model->hide_roots && ((((GbBookmark *) parent) == root)
						||((GbBookmark *) parent) == realroot))
		{
			int nprevs = 0;
			GSList *sli;
			GtkTreeIter riter;
			int rrootindex = 0;
			riter.stamp = btree_model->stamp;
			for (sli = btree_model->roots; 
			     sli && gb_bookmark_real_bookmark (sli->data) != (GbBookmark *) parent; 
			     sli = sli->next)
			{
				riter.user_data = sli->data;
				riter.user_data2 = GINT_TO_POINTER (rrootindex);
				nprevs += gb_tree_model_iter_n_children (tree_model, &riter);
				rrootindex++;
			}
			g_assert (rrootindex == rootindex);
			retval = gtk_tree_path_new ();
			gtk_tree_path_append_index (retval, 
						    nprevs + gb_tree_model_bookmark_child_index (btree_model, b_it));
		}
		else
		{
			retval = gb_tree_model_get_path (tree_model, &tmp_iter);
		
			if (retval != NULL) 
			{
				i = gb_tree_model_bookmark_child_index (btree_model, b_it);
				gtk_tree_path_append_index (retval, i);
			}
		}
	}

	return retval;
}

static void
gb_tree_model_get_value (GtkTreeModel *tree_model,
			 GtkTreeIter *iter,
			 gint column,
			 GValue *value)
{
	GbBookmark *b;
	GdkPixbuf *pb;
	GbTreeModel *btree_model = (GbTreeModel *) tree_model;
	g_return_if_fail (GB_IS_TREE_MODEL (btree_model));
	g_return_if_fail (iter != NULL);
	g_return_if_fail (iter->stamp == btree_model->stamp);
	g_return_if_fail (GB_IS_BOOKMARK (iter->user_data));
	g_return_if_fail (column < GB_TREE_MODEL_NUM_COLUMS);
	
	b = iter->user_data;
	
	switch (column) {
	case GB_TREE_MODEL_COL_ICON:
		g_value_init (value, GDK_TYPE_PIXBUF);
		pb = gb_bookmark_get_icon (b);
		g_value_set_object (value, pb);
		break;
	case GB_TREE_MODEL_COL_TITLE:
		g_value_init (value, G_TYPE_STRING);
		if (GB_IS_SEPARATOR (b))
		{
			g_value_set_string (value, "-----");
		}
		else
		{
			g_value_set_string (value, b->name);
		}
		break;
	case GB_TREE_MODEL_COL_URL:
		g_value_init (value, G_TYPE_STRING);
		if (GB_IS_SITE (b))
		{
			g_value_set_string (value, ((GbSite *) b)->url);
		}
		break;
	case GB_TREE_MODEL_COL_CHECK:
		g_value_init (value, G_TYPE_BOOLEAN);
		g_value_set_boolean (value, btree_model->check_func (b, btree_model->check_func_data));
		break;
	default:
		g_assert_not_reached ();
		break;
	}
}

static gboolean
gb_tree_model_iter_next (GtkTreeModel *tree_model,
			 GtkTreeIter *iter)
{
	GbBookmark *b;
	int rootindex;
	GbTreeModel *btree_model = (GbTreeModel *) tree_model;
	g_return_val_if_fail (iter != NULL, FALSE);
	g_return_val_if_fail (iter->user_data != NULL, FALSE);
	g_return_val_if_fail (iter->stamp == GB_TREE_MODEL (tree_model)->stamp, FALSE);
	
	b = iter->user_data;
	rootindex = GPOINTER_TO_INT (iter->user_data2);
	
	if (b == gb_tree_model_get_nth_root (btree_model, rootindex))
	{
		GbBookmark *nb;
		++rootindex;
		nb = gb_tree_model_get_nth_root (btree_model, rootindex);

		g_return_val_if_fail (!btree_model->hide_roots, FALSE);
		
		if (nb != NULL) 
		{
			iter->user_data = nb;
			iter->user_data2 = GINT_TO_POINTER (rootindex);
			return TRUE;
		}
		else
		{
			return FALSE;
		}
	}
	else
	{
		GbBookmark *n = b->next;
	search:
		if (btree_model->only_folders)
		{
			for ( ; n && !GB_IS_FOLDER (n); n = n->next) ;
		}
		
		if (n != NULL) 
		{
			iter->user_data = n;
			iter->user_data2 = GINT_TO_POINTER (rootindex);
			return TRUE;
		}
		else
		{
			if (btree_model->hide_roots
			    && ((GbBookmark *) b->parent) == gb_tree_model_get_nth_root (btree_model, rootindex))
			{
				GbBookmark *nb;
				++rootindex;
				nb = gb_tree_model_get_nth_root (btree_model, rootindex);
				
				if (nb != NULL && GB_IS_FOLDER (nb)) 
				{
					n = GB_FOLDER (nb)->child;
					goto search;
				}
			}
			return FALSE;
		}
	}
}

static gboolean
gb_tree_model_iter_children (GtkTreeModel *tree_model,
			     GtkTreeIter *iter,
			     GtkTreeIter *parent)
{
	GbTreeModel *btree_model = (GbTreeModel *) tree_model;
	GbBookmark *c1;
	int rootindex;

	g_return_val_if_fail (parent == NULL || parent->user_data != NULL, FALSE);
	g_return_val_if_fail (parent == NULL || parent->stamp == btree_model->stamp, FALSE);
	
	if (!parent || parent->user_data == btree_model->roots) 
	{
		if (btree_model->hide_roots)
		{
			GSList *li;
			rootindex = 0;
			c1 = NULL;
			for (li = btree_model->roots; li; li = li->next)
			{
				if (GB_IS_FOLDER (li->data))
				{
					c1 = GB_FOLDER (li->data)->child;
					break;
				}
				++rootindex;
			}
		}
		else
		{
			c1 = btree_model->roots->data;
			rootindex = 0;
		}
	}
	else
	{
		GbFolder *p = parent->user_data;
		rootindex = GPOINTER_TO_INT (parent->user_data2);
		if (!gb_bookmark_is_alias (p) || gb_tree_model_get_nth_root (btree_model, rootindex) == (GbBookmark *) p)
		{
			if (btree_model->only_folders)
			{
				for (c1 = p->child; c1 && !GB_IS_FOLDER (c1); c1 = c1->next) ;
			}
			else
			{
				c1 = p->child;
			}
		}
		else 
		{
			c1 = NULL;
		}
	}
	
	if (c1)
	{
		iter->stamp = GB_TREE_MODEL (tree_model)->stamp;
		iter->user_data = c1;
		iter->user_data2 = GINT_TO_POINTER (rootindex);
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

static gboolean
gb_tree_model_iter_has_child (GtkTreeModel *tree_model,
			      GtkTreeIter *iter)
{
	GbTreeModel *btree_model = (GbTreeModel *) tree_model;
	GbFolder *b_it;
	int rootindex;
	
	g_return_val_if_fail (GB_IS_TREE_MODEL (btree_model), FALSE);
	g_return_val_if_fail (iter->stamp == btree_model->stamp, FALSE);
	g_return_val_if_fail (iter->user_data != NULL, FALSE);
	
	if (!GB_IS_FOLDER (iter->user_data))
		return FALSE;

	b_it = iter->user_data;
	rootindex = GPOINTER_TO_INT (iter->user_data2);

	if (gb_bookmark_is_alias (b_it) && gb_tree_model_get_nth_root (btree_model, rootindex) != (GbBookmark *) b_it)
	{
		return FALSE;
	}
	else if (btree_model->only_folders)
	{
		GbBookmark *b;
		for (b = b_it->child; b; b = b->next)
		{
			if (GB_IS_FOLDER (b))
			{
				return TRUE;
			}
		}
		return FALSE;
	}
	else
	{
		return b_it->child != NULL;
	}
}

static int 
gb_tree_model_bookmark_n_children (GbTreeModel *btree_model, GbBookmark *b, int rootindex)
{
	int i = 0;
	if (GB_IS_FOLDER (b)
	    && (!gb_bookmark_is_alias (b) || gb_tree_model_get_nth_root (btree_model, rootindex) == b))
	{
		gboolean no_foders = ! btree_model->only_folders;
		GbBookmark *c = GB_FOLDER (b)->child;
		while (c != NULL)
		{
			if (no_foders || GB_IS_FOLDER (c))
			{
				i++;
			}
			c = c->next;
		}
	}
	return i;
}

static gint
gb_tree_model_iter_n_children (GtkTreeModel *tree_model,
			       GtkTreeIter *iter)
{
	GbTreeModel *btree_model = (GbTreeModel *) tree_model;

	g_return_val_if_fail (GB_IS_TREE_MODEL (tree_model), 0);
	g_return_val_if_fail (iter == NULL || iter->user_data != NULL, 0);
	
	if (iter == NULL || iter->user_data == GB_TREE_MODEL (tree_model)->roots)
	{
		if (btree_model->hide_roots)
		{
			GSList *l;
			int nch = 0;
			int rootindex = 0;
			for (l = btree_model->roots; l; l = l->next)
			{
				nch += gb_tree_model_bookmark_n_children (btree_model, l->data, rootindex);
				++rootindex;
			}
			return nch;
		}
		else
		{
			if (GB_TREE_MODEL (tree_model)->only_folders)
			{
				int i = 0;
				GSList *l;

				for (l = btree_model->roots; l; l = l->next)
				{
					if (GB_IS_FOLDER (l->data))
					{
						++i;
					}
				}
				return i;
			}
			else
			{
				return g_slist_length (btree_model->roots);
			}
		}
	}
	else 
	{
		int rootindex = GPOINTER_TO_INT (iter->user_data2);
		return gb_tree_model_bookmark_n_children (btree_model, iter->user_data, rootindex);
	}
}

static GbBookmark *
gb_tree_model_bookmark_nth_child (GbTreeModel *btree_model, GbFolder *parent, gint index)
{
	gboolean no_folders = ! btree_model->only_folders;
	int i = 0;
	GbBookmark *c;
		
	for (c = parent->child; c; c = c->next)
	{
		if (i == index && (no_folders || GB_IS_FOLDER (c)))
		{
			break;
		}
		else if (no_folders || GB_IS_FOLDER (c))
		{
			i++;
		}
	}
	return c;
}

static gboolean
gb_tree_model_iter_nth_child (GtkTreeModel *tree_model,
			      GtkTreeIter *iter,
			      GtkTreeIter *parent,
			      gint n)
{
	GbTreeModel *btree_model = (GbTreeModel *) tree_model;
	g_return_val_if_fail (GB_IS_TREE_MODEL (tree_model), FALSE);
	g_return_val_if_fail (parent == NULL || parent->user_data != NULL, FALSE);
	g_return_val_if_fail (iter != NULL, FALSE);
	
	if (parent == NULL || parent->user_data == GB_TREE_MODEL (tree_model)->roots)
	{
		if (btree_model->hide_roots)
		{
			GbBookmark *c = NULL;
			GSList *sli;
			int rootindex = 0;
			for (sli = btree_model->roots; sli && !c && n >= 0; sli = sli->next)
			{
				c = gb_tree_model_bookmark_nth_child (btree_model, sli->data, n);
				if (c)
				{
					iter->stamp = btree_model->stamp;
					iter->user_data = c;
					iter->user_data2 = GINT_TO_POINTER (rootindex);
					return TRUE;
				}
				else
				{
					n -= gb_tree_model_bookmark_n_children (btree_model, sli->data, rootindex);
					++rootindex;
				}
			}
			return FALSE;
		}
		else
		{
			GSList *l = g_slist_nth (btree_model->roots, n);
			if (l != NULL)
			{
				iter->user_data = l->data;
				iter->user_data2 = GINT_TO_POINTER (n);
				iter->stamp = btree_model->stamp;
				return TRUE;
			}
			else
			{
				return FALSE;
			}
		}
	}
	else 
	{
		GbFolder *b = parent->user_data;
		int rootindex = GPOINTER_TO_INT (parent->user_data2);
		if (GB_IS_FOLDER (b) && (!gb_bookmark_is_alias (b)
					 || gb_tree_model_get_nth_root (btree_model, rootindex) == (GbBookmark *) b))
		{
			GbBookmark *c = gb_tree_model_bookmark_nth_child (btree_model, b, n);

			if (c)
			{
				iter->user_data = c;
				iter->user_data2 = parent->user_data2;
				iter->stamp = btree_model->stamp;
				return TRUE;
			}
			else
			{
				return FALSE;
			}
		}
		else 
		{
			return FALSE;
		}
	}
}

static gboolean
gb_tree_model_iter_parent (GtkTreeModel *tree_model,
			   GtkTreeIter *iter,
			   GtkTreeIter *child)
{
	GbTreeModel *btree_model = (GbTreeModel *) tree_model;
	g_return_val_if_fail (iter != NULL, FALSE);
	g_return_val_if_fail (child != NULL, FALSE);
	g_return_val_if_fail (child->user_data != NULL, FALSE);
	g_return_val_if_fail (child->stamp == GB_TREE_MODEL (tree_model)->stamp, FALSE);
	
	if (child->user_data == btree_model->roots)
	{
		return FALSE;
	}
	else 
	{
		GbBookmark *bit = child->user_data;
		int rootindex = GPOINTER_TO_INT (child->user_data2);
		
		if (gb_tree_model_get_nth_root (btree_model, rootindex) == bit)
		{
			iter->user_data = btree_model->roots;
			iter->stamp = btree_model->stamp;
			
			g_return_val_if_fail (!btree_model->hide_roots, TRUE);
			return TRUE;
		}
		else 
		{
			GbFolder *parent = GB_FOLDER (bit->parent);
			g_assert (parent != NULL);
			
			if (btree_model->hide_roots
			    && ((GbBookmark *) parent) == gb_tree_model_get_nth_root (btree_model, rootindex))
			{
				iter->user_data = btree_model->roots;
				iter->stamp = btree_model->stamp;
			}
			else
			{
				iter->user_data = parent;
				iter->user_data2 = GINT_TO_POINTER (rootindex);
				iter->stamp = btree_model->stamp;
			}
			return TRUE;
		}
	}
}

/**
 * gb_tree_model_iter_depth:
 * @btree_model: A #GbTreeModel
 * @iter: A valid #GtkTreeIter
 * 
 * Returns the depth of @iter. This will be 0 for anything on the root level, 1
 * for anything down a level, etc.
 * 
 * Return value: The depth of @iter
 **/
gint
gb_tree_model_iter_depth (GbTreeModel *btree_model,
			  GtkTreeIter *iter)
{
	gint depth = 0;
	GbBookmark *b_it;
	GbBookmark *root;
	gint rootindex;

	g_return_val_if_fail (GB_IS_TREE_MODEL (btree_model), 0);
	g_return_val_if_fail (VALID_ITER (iter, btree_model), 0);
	
	b_it = iter->user_data;
	rootindex = GPOINTER_TO_INT (iter->user_data2);
	root = gb_tree_model_get_nth_root (btree_model, rootindex);
	while (b_it->parent && !(b_it == root
				 || (btree_model->hide_roots 
				     && ((GbBookmark *) b_it->parent) == root)))
	{
		depth++;
		b_it = (GbBookmark *) b_it->parent;
	}
	return depth;
}




/* DND */


static gboolean
gb_tree_model_drag_data_delete (GtkTreeDragSource *drag_source,
				GtkTreePath *path)
{
	GtkTreeIter iter;
	GbTreeModel *tm;

	g_return_val_if_fail (GB_IS_TREE_MODEL (drag_source), FALSE);

	tm = GB_TREE_MODEL (drag_source);

	if (gb_tree_model_get_iter (GTK_TREE_MODEL (tm), &iter, path))
	{
		GbBookmark *b = gb_tree_model_bookmark_from_iter (tm, &iter);
		gb_bookmark_unparent_safe (b);
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

static gboolean
gb_tree_model_drag_data_get (GtkTreeDragSource *drag_source,
			     GtkTreePath *path,
			     GtkSelectionData *selection_data)
{
	g_return_val_if_fail (GB_IS_TREE_MODEL (drag_source), FALSE);
	
	/* Note that we don't need to handle the GTK_TREE_MODEL_ROW
	 * target, because the default handler does it for us, but
	 * we do anyway for the convenience of someone maybe overriding the
	 * default handler.
	 */
	
	if (gtk_tree_set_row_drag_data (selection_data,
					GTK_TREE_MODEL (drag_source),
					path))
	{
		return TRUE;
	}
	else
	{
		/* export XBEL */
	}

	return FALSE;
}


static gboolean
gb_tree_model_drag_data_received (GtkTreeDragDest *drag_dest,
				  GtkTreePath *dest,
				  GtkSelectionData *selection_data)
{
	GbTreeModel *gtm;
	GtkTreeModel *src_model = NULL;
	GtkTreePath *src_path = NULL;
	GbBookmark *b = NULL;
	GbBookmark *bparent;
	gint bindex;
	
	g_return_val_if_fail (GB_IS_TREE_MODEL (drag_dest), FALSE);

	LOG ("in gb_tree_model_drag_data_received");

	gtm = GB_TREE_MODEL (drag_dest);
	
	/* get the bookmark */
	if (gtk_tree_get_row_drag_data (selection_data,
					&src_model,
					&src_path))
	{
		/* in the same process */

		/* Copy the given row to a new position */
		GtkTreeIter src_iter;
		GbBookmark *srcb;
		
		if (!gtk_tree_model_get_iter (src_model, &src_iter, src_path))
		{
			gtk_tree_path_free (src_path);
			return FALSE;
		}

		srcb = gb_tree_model_bookmark_from_iter (gtm, &src_iter);
		if (gb_bookmark_is_alias (srcb))
		{
			/* if the source bookmark is an alias, we create an alias to it instead 
			   of copying it. This avoid turning aliases into normal bookmarks when 
			   moving them */
			b = gb_bookmark_alias_create (srcb, NULL);
		}
		else
		{
			b = gb_bookmark_copy (srcb);
		}
		gtk_tree_path_free (src_path);
	}
	else
	{
		/* import XBEL */
		NOT_IMPLEMENTED;
	}

	if (!b)
	{
		return FALSE;
	}

	/*
	  DEBUG_PATH (dest);
	*/
		
	/* get the destination */
	{
		GtkTreePath *dest_parent_path = gtk_tree_path_copy (dest);
		GtkTreeIter dest_parent_iter; 
		if (gtk_tree_path_up (dest_parent_path))
		{
			if ((gtk_tree_path_get_depth (dest_parent_path) > 0)
			    && (gtk_tree_model_get_iter (GTK_TREE_MODEL (gtm),
							 &dest_parent_iter, dest_parent_path)))
			{
				bparent = gb_tree_model_bookmark_from_iter (gtm, &dest_parent_iter);
			}
			else
			{
				bparent = NULL;
			}
		}
		else
		{
			bparent = NULL;
		}
		gtk_tree_path_free (dest_parent_path);
	}

	if (!bparent)
	{
		
		bparent = gb_tree_model_get_first_root (gtm);
		LOG ("Defaulting the parent to the first root...");

		/* this is broken, specially when hide roots is TRUE */
	}

	g_return_val_if_fail (bparent, FALSE);

	if (!GB_IS_FOLDER (bparent))
	{
		bparent = (GbBookmark *) bparent->parent;
	}

	g_return_val_if_fail (GB_IS_FOLDER (bparent), FALSE);

	if (g_object_get_data (G_OBJECT (gtm),
			       "gtk-tree-model-drop-append"))
	{
		bindex = -1;
		g_object_set_data (G_OBJECT (gtm), "gtk-tree-model-drop-append", NULL);
	}
	else if (gtk_tree_path_get_depth (dest) > 0)
	{
		bindex = gtk_tree_path_get_indices (dest)[gtk_tree_path_get_depth (dest) - 1];
	}
	else
	{
		bindex = -1;
	}
	
	if (bindex > 0 && gtm->only_folders)
	{
		gint realindex = 0;
		GbBookmark *c;
		for (c = GB_FOLDER (bparent)->child; c && bindex > 0; c = c->next)
		{
			++realindex;
			if (GB_IS_FOLDER (c))
			{
				--bindex;
			}
		}
		bindex = realindex;
	}

	LOG ("will insert into %s (at %d)", bparent->name, bindex);

	gb_folder_add_child (GB_FOLDER (bparent), b, bindex);

	return TRUE;
}

static gboolean
gb_tree_model_row_drop_possible (GtkTreeDragDest *drag_dest,
				 GtkTreePath *dest_path,
				 GtkSelectionData *selection_data)
{
	GtkTreeModel *src_model = NULL;
	GtkTreePath *src_path = NULL;
	GtkTreePath *tmp = NULL;
	gboolean retval = TRUE;
	
	if (gtk_tree_get_row_drag_data (selection_data,
					&src_model,
					&src_path))
	{	
		/* in-process drag */

		if (src_model == GTK_TREE_MODEL (drag_dest))
		{
			/* in the same tree model */
	
			/* Can't drop into ourself. */
			if (gtk_tree_path_is_ancestor (src_path,
						       dest_path))
			{
				retval = FALSE;
			}
			else
			{
				retval = TRUE;
			}
		}
		else
		{
			retval = TRUE;
		}
		gtk_tree_path_free (src_path);
	}

	if (retval)
	{
		/* Can't drop if dest_path's parent doesn't exist and is a folder */

		GtkTreeIter iter;
		
		if (gtk_tree_path_get_depth (dest_path) > 1)
		{
			tmp = gtk_tree_path_copy (dest_path);
			gtk_tree_path_up (tmp);
			
			if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_dest),
						      &iter, tmp))
			{
				retval = GB_IS_FOLDER (gb_tree_model_bookmark_from_iter 
						       (GB_TREE_MODEL (drag_dest), &iter));
			}
			else
			{
				retval = TRUE;
			}
			gtk_tree_path_free (tmp);
		}
		else
		{
			retval = TRUE;
		}
	}
	
	LOG ("gb_tree_model_row_drop_possible () = %s", retval ? "TRUE" : "FALSE");
	
	return retval;
	
}

static GbBookmark *
gb_tree_model_get_nth_root (GbTreeModel *m, int i)
{
	GSList *sli = g_slist_nth (m->roots, i);
	return sli ? sli->data : NULL;
}

static gboolean
gb_tree_model_is_root (GbTreeModel *m, GbBookmark *b, int *i)
{
	GSList *l = m->roots;
	int j = 0;
	while (l != NULL)
	{
		if (l->data == b)
		{
			if (i != NULL)
			{
				*i = j;
			}
			return TRUE;
		}
		j++;
		l = l->next;
	}
	return FALSE;
}

GbBookmark *
gb_tree_model_bookmark_from_iter (GbTreeModel *m, GtkTreeIter *iter)
{
	if (iter->user_data == m->roots)
	{
		return NULL;
	}
	else
	{
		return GB_BOOKMARK (iter->user_data);
	}
}

gboolean
gb_tree_model_iter_from_bookmark (GbTreeModel *model, GbBookmark *b, GtkTreeIter *iter)
{
	int rootindex = 0;
	GSList *sli;
	gboolean ret = FALSE;

	if (model->only_folders && !GB_IS_FOLDER (b))
	{
		return FALSE;
	}

	for (sli = model->roots; sli; sli = sli->next)
	{
		GbBookmark *r = sli->data;
		if (r == b || (GB_IS_FOLDER (r) && gb_folder_is_ancestor (GB_FOLDER (r), b)))
		{
			ret = TRUE;
			break;
		}
		else
		{
			++rootindex;
		}
	}

	if (ret)
	{
		iter->stamp = model->stamp;
		iter->user_data = b;
		iter->user_data2 = GINT_TO_POINTER (rootindex);
	}
	return ret;
}

static void
gb_tree_model_row_changed_helper (GbTreeModel *m, GtkTreeIter *it)
{
	GtkTreePath *p = gb_tree_model_get_path ((GtkTreeModel *) m, it);
	gtk_tree_model_row_changed ((GtkTreeModel *) m, p, it);
	gtk_tree_path_free (p);
}

static void 
gb_tree_model_descendant_modified_cb (GbFolder *f, GbBookmark *b, gpointer data)
{
	GbTreeModel *m = data;

	if (!m->only_folders || GB_IS_FOLDER (b))
	{
		int rootindex = g_slist_index (m->roots, f);
		GtkTreeIter it;
		
		g_assert (rootindex >= 0);

		it.user_data = b;
		it.user_data2 = GINT_TO_POINTER (rootindex);
		it.stamp = m->stamp;

		gb_tree_model_row_changed_helper (m, &it);
	}
}

static void
gb_tree_model_modified_cb (GbBookmark *b, gpointer data)
{
	GbTreeModel *m = data;

	if (!m->hide_roots)
	{
		int rootindex = g_slist_index (m->roots, b);
		GtkTreeIter it;

		it.user_data = b;
		it.user_data2 = GINT_TO_POINTER (rootindex);
		it.stamp = m->stamp;

		gb_tree_model_row_changed_helper (m, &it);
	}	
}

static void
gb_tree_model_row_inserted_helper (GbTreeModel *m, GtkTreeIter *it)
{
	GtkTreePath *path = gb_tree_model_get_path ((GtkTreeModel *) m, it);
	gtk_tree_model_row_inserted ((GtkTreeModel *) m, path, it);
	gtk_tree_path_free (path);
}

static void
gb_tree_model_descendant_added_cb (GbFolder *f, GbFolder *p, GbBookmark *b, int pos, gpointer data)
{
	GbTreeModel *m = data;
	int rootindex = g_slist_index (m->roots, f);
	GtkTreeIter it;

	g_assert (rootindex >= 0);
		
	if ((!gb_bookmark_is_alias (p) || f == p) 
	    && (!m->only_folders || GB_IS_FOLDER (b)))
	{
		it.user_data = b;
		it.user_data2 = GINT_TO_POINTER (rootindex);
		it.stamp = m->stamp;
		
		gb_tree_model_row_inserted_helper (m, &it);

		/* probably this could be done less inneficient */
		if (GB_IS_FOLDER (b) && !gb_bookmark_is_alias (b)) /* note that b can't be root */
		{
			GbBookmark *bi;
			int i = 0;
			for (bi = GB_FOLDER (b)->child; bi; bi = bi->next)
			{
				gb_tree_model_descendant_added_cb (f, bi->parent, bi, i, data);
				++i;
			}
		}
	}

	/* the following is needed because the check_func value may change when a child is added */
	if (!(m->hide_roots && f == p))
	{
		it.user_data = p;
		it.user_data2 = GINT_TO_POINTER (rootindex);
		it.stamp = m->stamp;

		gb_tree_model_row_changed_helper (m, &it);
	}
}

static void
gb_tree_model_descendant_removed_cb (GbFolder *f, GbFolder *p, GbBookmark *b, int pos, gpointer data)
{
	GbTreeModel *m = data;
	int rootindex = g_slist_index (m->roots, f);
	GtkTreeIter it;
	
	g_assert (rootindex >= 0);
	
	if (!(m->only_folders && !GB_IS_FOLDER (b))
	    && (!gb_bookmark_is_alias (p) || p == f))
	{
		GtkTreePath *path;
		gint realpos;

		if (m->only_folders)
		{
			int i = 0;
			GbBookmark *x;
			realpos = 0;
			
			for (x = p->child; x && i < pos; x = x->next)
			{
				++i;
				if (GB_IS_FOLDER (x))
				{
					++realpos;
				}
			}
		}
		else
		{
			realpos = pos;
		}

		if (f == p && m->hide_roots)
		{
			it.user_data = m->roots;
		}
		else 
		{
			it.user_data = p;
			it.user_data2 = GINT_TO_POINTER (rootindex);
		}
		
		it.stamp = m->stamp;
			
		path = gb_tree_model_get_path ((GtkTreeModel *) m, &it);
		
		gtk_tree_path_append_index (path, realpos);
		
		gtk_tree_model_row_deleted ((GtkTreeModel *) m, path);
		
		gtk_tree_path_up (path);
		gtk_tree_model_row_has_child_toggled ((GtkTreeModel *) m, path, &it);
		
		gtk_tree_path_free (path);
	}

	/* the following is needed because the check_func value may change when a child is removed */
	if (!(m->hide_roots && f == p))
	{
		it.user_data = p;
		it.user_data2 = GINT_TO_POINTER (rootindex);
		it.stamp = m->stamp;

		gb_tree_model_row_changed_helper (m, &it);
	}
}

GbBookmark *
gb_tree_model_get_first_root (GbTreeModel *model)
{
	if (model->roots)
	{
		return model->roots->data;
	}
	else
	{
		return NULL;
	}
}

GbBookmarkSet *
gb_tree_model_get_set (GbTreeModel *model)
{
	return model->set;
}

const GSList *
gb_tree_model_get_roots (GbTreeModel *model)
{
	return model->roots;
}

void
gb_tree_model_set_check (GbTreeModel *model, GbTreeModelCheckFunc cf, gpointer data)
{
	model->check_func = cf ? cf : gb_tree_model_default_check_func;
	model->check_func_data = data;
}

