/*
 *  Copyright (C) 2002  Ricardo Fernández Pascual
 *
 *  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.h"
#include "bookmarks-io.h"
#include "bookmarks-toolbar-widgets.h"
#include "bookmarks-icon-provider.h"
#include "bookmarks-util.h"
#include "bookmarks-iterator.h"
#include "galeon-marshal.h"
#include "galeon-debug.h"
#include "pixbuf-cache.h"
#include "gul-general.h"
#include "gul-string.h"
#include "gul-gobject-misc.h"
#include <libgnomevfs/gnome-vfs-utils.h>
#include <string.h>
#include "galeon-autocompletion-source.h"
#include <time.h>

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

/**
 * Private functions, only availble from this file
 */
static void		gb_bookmark_class_init		(GbBookmarkClass *klass);
static void		gb_bookmark_init		(GbBookmark *b);
static void		gb_bookmark_set_class_init	(GbBookmarkSetClass *klass);
static void		gb_bookmark_set_init		(GbBookmarkSet *b);
static void		gb_bookmark_set_autocompletion_source_init (GaleonAutocompletionSourceIface *iface);
static void		gb_bookmark_set_autocompletion_source_foreach 
							(GaleonAutocompletionSource *source,
							 const gchar *basic_key,
							 GaleonAutocompletionSourceForeachFunc func,
							 gpointer data);
static void		gb_bookmark_set_autocompletion_source_set_basic_key
							(GaleonAutocompletionSource *source,
							 const gchar *basic_key);
static void		gb_bookmark_set_emit_autocompletion_source_data_changed (GbBookmarkSet *gh);
static void		gb_bookmark_set_tree_changed	(GbBookmarkSet *set);
static void		gb_site_class_init		(GbSiteClass *klass);
static void		gb_site_init			(GbSite *b);
static void		gb_folder_class_init		(GbFolderClass *klass);
static void		gb_folder_init			(GbFolder *b);
static void 		gb_separator_init 		(GbSeparator *b);
static void		gb_separator_class_init		(GbSeparatorClass *klass);
static void		gb_smart_site_class_init	(GbSmartSiteClass *klass);
static void		gb_smart_site_init		(GbSmartSite *b);
static void		gb_alias_placeholder_class_init	(GbAliasPlaceholderClass *klass);
static void		gb_alias_placeholder_init	(GbAliasPlaceholder *b);
static void		gb_auto_folder_class_init	(GbAutoFolderClass *klass);
static void		gb_auto_folder_init		(GbAutoFolder *b);
static void		gb_bookmark_set_set		(GbBookmark *b, GbBookmarkSet *set);
static GbBookmark *	gb_bookmark_copy_impl		(GbBookmark *b);
static void 		gb_bookmark_copy_impl_do	(GbBookmark *b, GbBookmark *c);
static GbBookmark *	gb_site_copy_impl		(GbBookmark *b);
static void 		gb_site_copy_impl_do		(GbSite *b, GbSite *c);
static GbBookmark *	gb_folder_copy_impl		(GbBookmark *b);
static void		gb_folder_copy_impl_do		(GbFolder *b, GbFolder *c);
static GbBookmark *	gb_smart_site_copy_impl		(GbBookmark *b);
static void		gb_smart_site_copy_impl_do	(GbSmartSite *b, GbSmartSite *c);
static GbBookmark *	gb_alias_placeholder_copy_impl	(GbBookmark *b);
static void		gb_alias_placeholder_copy_impl_do (GbAliasPlaceholder *b, GbAliasPlaceholder *c);
static GbBookmark *	gb_separator_copy_impl		(GbBookmark *b);
static GbBookmark *	gb_auto_folder_copy_impl	(GbBookmark *b);
static void		gb_bookmark_set_set_impl	(GbBookmark *b, GbBookmarkSet *set);
static void		gb_site_set_set_impl		(GbBookmark *b, GbBookmarkSet *set);
static void		gb_folder_set_set_impl		(GbBookmark *b, GbBookmarkSet *set);
static void		gb_alias_placeholder_set_set_impl (GbBookmark *b, GbBookmarkSet *set);
static GbBookmark *	gb_bookmark_alias_create_impl	(GbBookmark *b, GbAliasPlaceholder *ap);
static void		gb_bookmark_alias_create_impl_do (GbBookmark *b, GbAliasPlaceholder *ap, 
							  GbBookmark *alias);
static GbBookmark *	gb_site_alias_create_impl	(GbBookmark *b, GbAliasPlaceholder *ap);
static void		gb_site_alias_create_impl_do	(GbBookmark *b, GbAliasPlaceholder *ap, 
							 GbSite *site);
static GbBookmark *	gb_folder_alias_create_impl	(GbBookmark *b, GbAliasPlaceholder *ap);
static void		gb_folder_alias_create_impl_do	(GbBookmark *b, GbAliasPlaceholder *ap, 
							 GbFolder *alias);
static GbBookmark *	gb_auto_folder_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap);
static GbBookmark *	gb_smart_site_alias_create_impl	(GbBookmark *b, GbAliasPlaceholder *ap);
static GbBookmark *	gb_separator_alias_create_impl	(GbBookmark *b, GbAliasPlaceholder *ap);
static GbBookmark *	gb_alias_placeholder_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap);
static void		gb_folder_emit_child_modified	(GbFolder *f, GbBookmark *b);
static void		gb_folder_emit_child_removed	(GbFolder *f, GbBookmark *b, gint pos);
static void		gb_folder_emit_child_added	(GbFolder *f, GbBookmark *b, gint pos);
static void		gb_folder_emit_descendant_modified (GbFolder *f, GbBookmark *b);
static void		gb_folder_emit_descendant_added (GbFolder *f, GbFolder *p, GbBookmark *b,
							 gint pos);
static void		gb_folder_emit_descendant_removed (GbFolder *f, GbFolder *p, GbBookmark *b,
							   gint pos);
static void		gb_bookmark_unparent_internal	(GbBookmark *b, gboolean emit_signals);


static void		gb_bookmark_set_finalize_impl	(GObject *o);
static void		gb_bookmark_set_dispose_impl	(GObject *o);
static void		gb_bookmark_finalize_impl	(GObject *o);
static void		gb_site_finalize_impl		(GObject *o);
static void		gb_folder_finalize_impl		(GObject *o);
static void		gb_folder_dispose_impl		(GObject *o);
static void		gb_smart_site_finalize_impl	(GObject *o);
static void		gb_alias_placeholder_finalize_impl (GObject *o);
static GbTbWidget *	gb_bookmark_create_toolbar_widget_impl (GbBookmark *b);
static GbTbWidget *	gb_folder_create_toolbar_widget_impl (GbBookmark *b);
static GbTbWidget *	gb_site_create_toolbar_widget_impl (GbBookmark *b);
static GbTbWidget *	gb_separator_create_toolbar_widget_impl (GbBookmark *b);
static GbTbWidget *	gb_smart_site_create_toolbar_widget_impl (GbBookmark *b);
static GbTbWidget *	gb_alias_placeholder_create_toolbar_widget_impl (GbBookmark *b);
static void		gb_bookmark_init_icons		(void);
static gboolean		gb_bookmark_set_set_auto_save_cb (gpointer data);
#define gb_bookmark_set_needs_saving(b) if (b && ((GbBookmark *) b)->set) \
						((GbBookmark *) b)->set->needs_saving = TRUE;

static void		gb_bookmark_set_fix_galeon1_mess_recursive 	(GbFolder *f);
static void 		gb_bookmark_set_fix_galeon1_mess_item		(GbBookmark *b);
static char *		gb_bookmark_set_fix_galeon1_mess_string		(const gchar *s);
static gchar *		gb_smart_site_get_smarturl_only		(GbSmartSite *b);
static void		gb_smart_site_set_smarturl_full		(GbSmartSite *b, const gchar *url);
static gchar *		gb_smart_site_get_options		(GbSmartSite *b);
static void		gb_smart_site_set_options		(GbSmartSite *b, gchar *options);
static GSList *		gb_folder_list_children_reversed	(GbFolder *f);
static GbBookmark *	gb_folder_get_child_or_alias		(GbFolder *f, GbBookmark *c);

#define BOOKMARKS_NUM_BACKUPS 5

/**
 * Bookmark icons
 */

static GbIconProvider *icon_provider = NULL;

/* #define DEBUG_REF */
#ifdef DEBUG_REF
static int live_bookmarks = 0;
#endif

/**
 * Signals enums and ids
 */
enum GbBookmarkSetSignalsEnum {
	GB_BOOKMARK_SET_TOOLBAR,
	GB_BOOKMARK_SET_CONTEXT_MENU,
	GB_BOOKMARK_SET_LAST_SIGNAL
};
static gint GbBookmarkSetSignals[GB_BOOKMARK_SET_LAST_SIGNAL];

enum GbBookmarkSignalsEnum {
	GB_BOOKMARK_MODIFIED,
	GB_BOOKMARK_NAME_MODIFIED,
	GB_BOOKMARK_LAST_SIGNAL
};
static gint GbBookmarkSignals[GB_BOOKMARK_LAST_SIGNAL];

enum GbSiteSignalsEnum {
	GB_SITE_URL_MODIFIED,
	GB_SITE_LAST_SIGNAL
};
static gint GbSiteSignals[GB_SITE_LAST_SIGNAL];

enum GbFolderSignalsEnum {
	GB_FOLDER_CHILD_MODIFIED,
	GB_FOLDER_CHILD_ADDED,
	GB_FOLDER_CHILD_REMOVED, 
	GB_FOLDER_CHILD_MOVED,
	GB_FOLDER_DESCENDANT_MODIFIED,
	GB_FOLDER_DESCENDANT_ADDED,
	GB_FOLDER_DESCENDANT_REMOVED,
	GB_FOLDER_LAST_SIGNAL
};
static gint GbFolderSignals[GB_FOLDER_LAST_SIGNAL];

enum GbSmartSiteSignalsEnum {
	GB_SMART_SITE_ENTRY_WIDTH_CHANGED,
	GB_SMART_SITE_VISIBILITY_CHANGED,
	GB_SMART_SITE_HISTORY_CHANGED,
	GB_SMART_SITE_LAST_SIGNAL
};
static gint GbSmartSiteSignals[GB_SMART_SITE_LAST_SIGNAL];

/**
 * Type objects
 */
static gpointer g_object_class;
static gpointer gb_bookmark_class;
static gpointer gb_site_class;
static gpointer gb_smart_site_class;
static gpointer gb_folder_class;
static gpointer gb_separator_class;
static gpointer gb_auto_folder_class;
static gpointer gb_alias_placeholder_class;
static gpointer gb_bookmark_set_class;

/**
 * Bookmark object
 */

MAKE_GET_TYPE (gb_bookmark, "GbBookmark", GbBookmark, gb_bookmark_class_init, 
	       gb_bookmark_init, G_TYPE_OBJECT);

static void
gb_bookmark_class_init (GbBookmarkClass *klass)
{
	klass->gb_bookmark_copy = gb_bookmark_copy_impl;
	klass->gb_bookmark_set_set = gb_bookmark_set_set_impl;
	klass->gb_bookmark_alias_create = gb_bookmark_alias_create_impl;
	klass->gb_bookmark_create_toolbar_widget = gb_bookmark_create_toolbar_widget_impl;
	G_OBJECT_CLASS (klass)->finalize = gb_bookmark_finalize_impl;

	GbBookmarkSignals[GB_BOOKMARK_MODIFIED] = g_signal_new (
		"modified", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbBookmarkClass, gb_bookmark_modified), 
		NULL, NULL, 
		galeon_marshal_VOID__VOID,
		G_TYPE_NONE, 0);
	GbBookmarkSignals[GB_BOOKMARK_NAME_MODIFIED] = g_signal_new (
		"name-modified", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbBookmarkClass, gb_bookmark_name_modified), 
		NULL, NULL, 
		galeon_marshal_VOID__STRING,
		G_TYPE_NONE, 1, G_TYPE_STRING);
	
	g_object_class = g_type_class_peek_parent (klass);
	gb_bookmark_class = klass;
	
	gb_bookmark_init_icons ();
}

static void
gb_bookmark_init_icons (void)
{
	if (icon_provider == NULL)
	{
		GbIconProvider *ip = gb_icon_provider_new ();
		gb_system_set_icon_provider (GB_ICON_PROVIDER (ip));
		g_object_unref (ip);
	}
}

void
gb_system_set_icon_provider (GbIconProvider *ip)
{
	if (icon_provider)
	{
		g_object_unref (icon_provider);
	}
	icon_provider = g_object_ref (ip);
}

GbIconProvider *
gb_system_get_icon_provider (void)
{
	if (!icon_provider)
	{
		gb_bookmark_init_icons ();
	}
	return icon_provider;
}

static void
gb_bookmark_finalize_impl (GObject *o)
{
	GbBookmark *b = (GbBookmark *) o;

#ifdef DEBUG_REF
	g_print ("live_bookmarks: %d- %-.20s\n", --live_bookmarks, b->name);
#endif
	
	g_assert (b->parent == NULL);

	if (!gb_bookmark_is_alias (b))
	{
		if (b->set && b->id)
		{
			g_hash_table_remove (b->set->id_to_bookmark, b->id);
		}

		if (b->set)
		{
			gb_bookmark_set_emit_autocompletion_source_data_changed (b->set);
		}

#ifdef NICK_HASHTABLE
		if (b->set)
		{
			g_hash_table_remove (b->set->nick_to_bookmark, b->nick);
		}
#endif
		if (b->alias)
		{
			b->alias->alias_of = NULL;
#ifdef NICK_HASHTABLE
			if (b->alias->set)
			{
				g_hash_table_insert (b->alias->set->nick_to_bookmark, 
						     b->alias->nick, b->alias);
			}
#endif
		}
		else
		{
			g_free (b->id);
			g_free (b->name);
			g_free (b->nick);
			g_free (b->pixmap_file);
			g_free (b->notes);
		}
	}
	else
	{
		b->alias_of->alias = b->alias;
		if (b->alias) 
		{
			b->alias->alias_of = b->alias_of;
		}
	}
	
	G_OBJECT_CLASS (g_object_class)->finalize (o);
}

GbBookmark *
gb_bookmark_copy (GbBookmark *b)
{
	GbBookmarkClass *klass = GB_BOOKMARK_GET_CLASS (b);
	return klass->gb_bookmark_copy (b);
}

GbTbWidget *
gb_bookmark_create_toolbar_widget (GbBookmark *b)
{
	GbBookmarkClass *klass = GB_BOOKMARK_GET_CLASS (b);
	return klass->gb_bookmark_create_toolbar_widget (b);
}

static GbBookmark *
gb_bookmark_copy_impl (GbBookmark *b)
{
	g_warning ("Should not be reached!");
	return NULL;
}

static void 
gb_bookmark_copy_impl_do (GbBookmark *b, GbBookmark *c)
{
	c->set = b->set;
	c->parent = NULL;
	c->next = NULL;
	c->prev = NULL;
	c->alias_of = NULL;
	c->alias = NULL;
	c->id = NULL;
	g_free (c->name);
	c->name = g_strdup (b->name);
	g_free (c->nick);
	c->nick = g_strdup (b->nick);
	g_free (c->pixmap_file);
	c->pixmap_file = g_strdup (b->pixmap_file);
	g_free (c->notes);
	c->notes = g_strdup (b->notes);
	c->add_to_context_menu = b->add_to_context_menu;
	c->time_added = b->time_added;
	c->time_modified = b->time_modified;
}

static GbTbWidget *
gb_bookmark_create_toolbar_widget_impl (GbBookmark *b)
{
	g_warning ("Should not be reached!");
	return NULL;
}

static GbBookmark *
gb_bookmark_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap)
{
	g_warning ("Should not be reached!");
	return NULL;
}

GbBookmark *
gb_bookmark_alias_create (GbBookmark *b, GbAliasPlaceholder *ap)
{
	GbBookmarkClass *klass = GB_BOOKMARK_GET_CLASS (b);
	return klass->gb_bookmark_alias_create (b, ap);
}

static void
gb_bookmark_alias_create_impl_do (GbBookmark *b, GbAliasPlaceholder *ap, 
				  GbBookmark *alias)
{
	GbBookmark *alias_data = (GbBookmark *) ap;
	
	/* make sure the bookmark has an id */
	if (b->id == NULL)
	{
		static gulong last_generated_id = 0;
		char id[32];
		do {
			/* ID's have to begin with a Letter to be valid XML */
			g_snprintf (id, sizeof(id), "id%lu", last_generated_id++);
			if (!b->set 
			    || g_hash_table_lookup (b->set->id_to_bookmark, 
						    id) == NULL)
			{
				break;
			}
		} while (last_generated_id != 0);
		if (last_generated_id == 0)
		{
			g_warning ("Could not find an unique id. You have a lot of bookmarks!");
		}
		gb_bookmark_set_id (b, id);
	}

	if (alias->set)
	{
		gb_bookmark_set_emit_autocompletion_source_data_changed (alias->set);
	}

	alias->set = b->set;
	alias->id = b->id;

	alias->alias_of = b;
	alias->alias = b->alias;
	if (b->alias)
	{
		b->alias->alias_of = alias;
	}
	b->alias = alias;

	g_free (alias->name);
	alias->name = b->name;
	
	g_free (alias->nick);
	alias->nick = b->nick;

	g_free (alias->pixmap_file);
	alias->pixmap_file = b->pixmap_file;

	g_free (alias->notes);
	alias->notes = b->notes;

	if (alias_data)
	{
		alias->add_to_context_menu = alias_data->add_to_context_menu;
	}
	else
	{
		alias->add_to_context_menu = b->add_to_context_menu;
	}

	alias->time_modified = b->time_modified;

	if (alias_data)
		alias->time_added = alias_data->time_added;
	else
		alias->time_added = b->time_added;
}


static void 
gb_bookmark_init (GbBookmark *b)
{
	b->parent = NULL;
	b->next = NULL;
	b->id = NULL;
	b->name = g_strdup ("");
	b->nick = g_strdup ("");
	b->notes = g_strdup ("");
	b->pixmap_file = g_strdup ("");
	b->set = NULL;
	b->xbel_node = NULL;
	
#ifdef DEBUG_REF
	g_print ("live_bookmarks: %d+\n", ++live_bookmarks);
#endif
}

static void
gb_bookmark_set_set (GbBookmark *b, GbBookmarkSet *set)
{
	GbBookmarkClass *klass = GB_BOOKMARK_GET_CLASS (b);
	return klass->gb_bookmark_set_set (b, set);
}

static void
gb_bookmark_set_set_impl (GbBookmark *b, GbBookmarkSet *set)
{
	GbBookmarkSet *oldset;
	g_return_if_fail (GB_IS_BOOKMARK (b));
	g_return_if_fail (!set || GB_IS_BOOKMARK_SET (set));
	
	oldset = b->set;

	if (oldset && b->id && !gb_bookmark_is_alias (b))
	{
		g_hash_table_remove (oldset->id_to_bookmark, b->id);
		gb_bookmark_set_emit_autocompletion_source_data_changed (oldset);
	}

#ifdef NICK_HASHTABLE
	if (oldset && b->nick && !gb_bookmark_is_alias (b))
	{
		g_hash_table_remove (oldset->nick_to_bookmark, b->nick);
	}
#endif

	gb_bookmark_set_xbel_node (b, NULL);

	if (oldset && b->add_to_context_menu)
	{
		g_slist_free (oldset->context_bookmarks);
		oldset->context_bookmarks = NULL;
		g_signal_emit (oldset, GbBookmarkSetSignals[GB_BOOKMARK_SET_CONTEXT_MENU], 0);
	}

	b->set = set;

	if (set && b->add_to_context_menu)
	{
		g_slist_free (set->context_bookmarks);
		set->context_bookmarks = NULL;
		g_signal_emit (set, GbBookmarkSetSignals[GB_BOOKMARK_SET_CONTEXT_MENU], 0);
	}

	/* add to hashtables... */
	if (b->set && b->id && !gb_bookmark_is_alias (b))
	{
		/* FIXME: i should check that the id is unique... */
		g_hash_table_insert (set->id_to_bookmark, b->id, b);
		gb_bookmark_set_emit_autocompletion_source_data_changed (b->set);
	}

#ifdef NICK_HASHTABLE
	if (b->set && b->nick && !gb_bookmark_is_alias (b))
	{
		g_hash_table_insert (set->nick_to_bookmark, b->nick, b);
	}
#endif
}

void
gb_bookmark_set_id (GbBookmark *b, const gchar *val)
{
	gchar *newval;
	g_return_if_fail (GB_IS_BOOKMARK (b));
	
	b = gb_bookmark_real_bookmark (b);

	if (GB_IS_BOOKMARK_SET (b->set)) 
	{
		if (b->id != NULL)
		{
			g_hash_table_remove (b->set->id_to_bookmark, b->id);
		}
	}
	
	g_free (b->id);
	newval = val ? g_strdup (val) : NULL;
	b->id = newval;

	if (GB_IS_BOOKMARK_SET (b->set)) 
	{
		if (b->id != NULL)
		{
			g_hash_table_insert (b->set->id_to_bookmark, b->id, b);
		}
	}

	gb_bookmark_set_needs_saving (b);

	do {
		b->id = newval;
		g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (b->parent, b);
	} while ((b = b->alias) != NULL);
}

void
gb_bookmark_set_xbel_node (GbBookmark *b, xmlNodePtr node)
{
	g_return_if_fail (GB_IS_BOOKMARK (b));
	g_return_if_fail (GB_IS_BOOKMARK_SET (b->set) || node == NULL);
	g_return_if_fail (node == NULL 
			  || node->doc == b->set->xbel_doc);

	b->xbel_node = node;
}

void
gb_bookmark_set_name (GbBookmark *b, const gchar *val)
{
	gchar *newval;

	g_return_if_fail (GB_IS_BOOKMARK (b));

	b = gb_bookmark_real_bookmark (b);

	g_free (b->name);
	newval = val ? g_strdup (val) : g_strdup ("");

	gb_bookmark_set_needs_saving (b);

	if (b->set)
	{
		gb_bookmark_set_emit_autocompletion_source_data_changed (b->set);
	}

	do {
		b->name = newval;
		g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_NAME_MODIFIED], 0, b->name);
		g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (b->parent, b);
	} while ((b = b->alias) != NULL);
}

void
gb_bookmark_set_nick (GbBookmark *b, const gchar *val)
{
	gchar *newval;

	g_return_if_fail (GB_IS_BOOKMARK (b));

	b = gb_bookmark_real_bookmark (b);
	
#ifdef NICK_HASHTABLE
	if (GB_IS_BOOKMARK_SET (b->set)) 
	{
		if (b->nick)
		{
			g_hash_table_remove (b->set->nick_to_bookmark, b->nick);
		}
	}
#endif	
	g_free (b->nick);
	newval = val ? g_strdup (val) : g_strdup ("");

#ifdef NICK_HASHTABLE
	if (GB_IS_BOOKMARK_SET (b->set)) 
	{
		if (val)
		{
			g_hash_table_insert (b->set->nick_to_bookmark, newval, b);
		}
	}
#endif
	gb_bookmark_set_needs_saving (b);

	do {
		b->nick = newval;
		g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (b->parent, b);
	} while ((b = b->alias) != NULL);


}

void
gb_bookmark_set_notes (GbBookmark *b, const gchar *val)
{
	gchar *newval;

	g_return_if_fail (GB_IS_BOOKMARK (b));

	b = gb_bookmark_real_bookmark (b);
	
	g_free (b->notes);
	newval = val ? g_strdup (val) : g_strdup ("");

	gb_bookmark_set_needs_saving (b);

	do {
		b->notes = newval;
		g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (b->parent, b);
	} while ((b = b->alias) != NULL);

}

void
gb_bookmark_set_pixmap (GbBookmark *b, const gchar *val)
{
	gchar *newval;

	g_return_if_fail (GB_IS_BOOKMARK (b));
	
	b = gb_bookmark_real_bookmark (b);
	
	g_free (b->pixmap_file);
	newval = val ? g_strdup (val) : g_strdup ("");

	gb_bookmark_set_needs_saving (b);

	do {
		b->pixmap_file = newval;
		g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (b->parent, b);
	} while ((b = b->alias) != NULL);

}

void
gb_bookmark_emit_changed (GbBookmark *b)
{
	b = gb_bookmark_real_bookmark (b);
	
	do {
		g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (b->parent, b);
	} while ((b = b->alias) != NULL);
}

/**
 * Return the bookmark icon, maybe a favicon...
 */
GdkPixbuf *
gb_bookmark_get_icon (GbBookmark *b)
{
	return gb_icon_provider_get_icon (icon_provider, b);
}

GdkPixbuf *
gb_bookmark_get_image (GbBookmark *b)
{
	/* return the pixmap data, or null if no pixmap file found */
	if (b->pixmap_file && b->pixmap_file[0])
	{
		return gul_pixbuf_cache_get (b->pixmap_file);
	}
	else
	{
		return NULL;
	}
}


void
gb_bookmark_set_add_to_context_menu (GbBookmark *b, gboolean val)
{
	g_return_if_fail (GB_IS_BOOKMARK (b));

	val = !!val;

	if (b->add_to_context_menu != val)
	{
		b->add_to_context_menu = val;
		
		g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (b->parent, b);

		if (b->set)
		{
			g_slist_free (b->set->context_bookmarks);
			b->set->context_bookmarks = NULL;
			g_signal_emit (b->set, GbBookmarkSetSignals[GB_BOOKMARK_SET_CONTEXT_MENU], 0);
		}
		
		gb_bookmark_set_needs_saving (b);
	}
}

void
gb_bookmark_set_time_added (GbBookmark *b, GTime val)
{
	g_return_if_fail (GB_IS_BOOKMARK (b));
	
	b->time_added = val;

	g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
	gb_folder_emit_child_modified (b->parent, b);

	gb_bookmark_set_needs_saving (b);
}

static GTime 
gb_system_get_current_time (void)
{
	return time (NULL);
}

void
gb_bookmark_set_time_added_now (GbBookmark *b)
{
	gb_bookmark_set_time_added (b, gb_system_get_current_time ());
}

void
gb_bookmark_set_time_modified (GbBookmark *b, GTime val)
{
	g_return_if_fail (GB_IS_BOOKMARK (b));
	
	b = gb_bookmark_real_bookmark (b);

	gb_bookmark_set_needs_saving (b);
	
	do {
		b->time_modified = val;
		g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (b->parent, b);
	} while ((b = b->alias) != NULL);

}

void
gb_bookmark_set_time_modified_now (GbBookmark *b)
{
	gb_bookmark_set_time_modified (b, gb_system_get_current_time ());
}

void
gb_bookmark_unparent (GbBookmark *b)
{
	gb_bookmark_unparent_internal (b, TRUE);
}

static GbBookmark *
gb_bookmark_get_parented_alias (GbBookmark *b)
{
	GbBookmark *alias;
	g_return_val_if_fail (!gb_bookmark_is_alias (b), NULL);

	for (alias = b->alias; alias; alias = alias->alias)
	{
		if (alias->parent)
		{
			return alias;
		}
	}
	return NULL;
}

static GbBookmark *
gb_bookmark_get_parented_alias_not_under (GbBookmark *b, GbFolder *f)
{
	GbBookmark *alias;
	g_return_val_if_fail (!gb_bookmark_is_alias (b), NULL);

	for (alias = b->alias; alias; alias = alias->alias)
	{
		if (alias->parent)
		{
			if (!gb_folder_is_ancestor (f, alias))
			{
				return alias;
			}
		}
	}
	return NULL;
}

static void
gb_folder_save_descendants (GbFolder *f, GbFolder *unsafe)
{
	GbBookmark *child;
	g_return_if_fail (!gb_bookmark_is_alias (f));

	LOG ("Trying to save descendants of %s not under %s", GB_BOOKMARK (f)->name, GB_BOOKMARK (unsafe)->name);
	
	for (child = f->child; child; child = child->next)
	{
		if (!gb_bookmark_is_alias (child) && gb_bookmark_has_alias (child))
		{
			GbBookmark *safe_child = gb_bookmark_get_parented_alias_not_under (child, unsafe);
			if (safe_child)
			{
				LOG ("Found a safe calias of %s under %s", 
				     child->name, GB_BOOKMARK (safe_child->parent)->name);
				gb_bookmark_alias_make_real (safe_child);
			}
			else 
			{
				LOG ("No safe parent found for %s", child->name);
				if (GB_IS_FOLDER (child))
				{
					gb_folder_save_descendants (GB_FOLDER (child), unsafe);
				}
			}
		}
		else if (!gb_bookmark_is_alias (child) && GB_IS_FOLDER (child))
		{
			g_assert (!gb_bookmark_has_alias (child));
			gb_folder_save_descendants (GB_FOLDER (child), unsafe);
		}
	}
}

void
gb_bookmark_unparent_safe (GbBookmark *b)
{
	if (!gb_bookmark_is_alias (b) && gb_bookmark_has_alias (b))
	{
		GbBookmark *parented_alias = NULL;
		if (GB_IS_FOLDER (b))
		{
			parented_alias = gb_bookmark_get_parented_alias_not_under (b, GB_FOLDER (b));
		}
		else
		{
			parented_alias = gb_bookmark_get_parented_alias (b);
		}

		if (parented_alias)
		{
			LOG ("safe parent for unparented bm: %s", 
			     GB_BOOKMARK (parented_alias->parent)->name);

			gb_bookmark_alias_make_real (parented_alias);
		}
		else
		{
			LOG ("no safe parent found");
			if (GB_IS_FOLDER (b))
			{
				gb_folder_save_descendants (GB_FOLDER (b), GB_FOLDER (b));
			}
		}
	}

	g_assert (gb_bookmark_is_alias (b) 
		  || !gb_bookmark_has_alias (b)
		  || !b->alias->parent
		  || (GB_IS_FOLDER (b) && gb_folder_is_ancestor (GB_FOLDER (b), b->alias)));

	gb_bookmark_unparent (b);
}

static void
gb_bookmark_unparent_internal (GbBookmark *b, gboolean emit_signals)
{
	g_return_if_fail (GB_IS_BOOKMARK (b));
	
	if (b->parent != NULL)
	{
		GbFolder *p = b->parent;
		GbFolder *pi;
		GbBookmark *first_child;
		gint pos;

		g_assert (GB_IS_FOLDER (p));
		g_assert (((GbBookmark *) p)->alias_of == NULL);
		
		gb_bookmark_set_needs_saving (p);
		if (GB_BOOKMARK (p)->set) gb_bookmark_set_tree_changed (GB_BOOKMARK (p)->set);
		
		/* it will be relinked when saving, if necessary */
		if (b->xbel_node)
		{
			xmlUnlinkNode (b->xbel_node);
		}
		
		if (GB_IS_FOLDER (b))
		{
			if (gb_folder_is_default_folder (GB_FOLDER (b)) 
			    && b->set && gb_folder_is_ancestor (b->set->root, b)
			    /* gb_bookmark_set_set_default_folder emits signals, we can't use
			       it always :( */
			    && emit_signals)
			{
				gb_bookmark_set_set_default_folder (b->set, b->set->root);
			}
		}
		
		pos = gb_folder_get_child_index (p, b);

		first_child = p->child == b ? b->next : p->child;

		if (b->prev)
		{
			b->prev->next = b->next;
		}

		if (b->next)
		{
			b->next->prev = b->prev;
		}
		
		b->prev = NULL;
		b->next = NULL;
		b->parent = NULL;

		pi = p;
		do {
			pi->child = first_child;
		} while ((pi = (GbFolder *) ((GbBookmark *) pi)->alias) != NULL);

		if (emit_signals)
		{
			gb_folder_emit_child_removed (p, b, pos);
			if (GB_BOOKMARK (p)->set)
			{
				gb_bookmark_set_emit_autocompletion_source_data_changed 
					(GB_BOOKMARK (p)->set);
			}
		}

		g_object_unref (G_OBJECT (b));
	}
}

GbBookmark *
gb_bookmark_real_bookmark (GbBookmark *b)
{
	while (b->alias_of)
	{
		b = b->alias_of;
	}
	return b;
}

GbBookmark *
gb_bookmark_add_alias_under (GbBookmark *b, GbFolder *newparent)
{
	GbBookmark *real = gb_bookmark_real_bookmark (b);
	if (real->parent || ((GbBookmark *) real->set->root) == real)
	{
		GbAliasPlaceholder *ap = gb_alias_placeholder_new (b->set, b->id);
		GbBookmark *new = gb_bookmark_alias_create (b, ap);
		gb_folder_add_child (newparent, new, -1);
		g_object_unref (new);
		g_object_unref (ap);
		
		g_assert (GB_IS_BOOKMARK (new));

		return new;
	}
	else
	{
		gb_folder_add_child (newparent, real, -1);
		return real;
	}
}

GbBookmark *
gb_bookmark_ensure_alias_under (GbBookmark *b, GbFolder *newparent)
{
	GbBookmark *new = gb_folder_get_child_or_alias (newparent, b);
	if (!new)
	{
		new = gb_bookmark_add_alias_under (b, newparent);
	}
	return new;
}

GSList *
gb_bookmark_get_all_alias_parents (GbBookmark *b)
{
	GSList *ret = NULL;
	for (b = gb_bookmark_real_bookmark (b); b; b = b->alias)
	{
		if (b->parent)
		{
			ret = g_slist_prepend (ret, b->parent);
		}
	}
	return ret;
}

/**
 * Makes an alias the real bookmark of its chain. To be called before
 * deleting the real bookmark.
 */
void
gb_bookmark_alias_make_real (GbBookmark *alias)
{
	GbBookmark *real;
	GSList *children_rev = NULL;
	GSList *li;
	GbBookmark *prevalias;
	GbBookmark *nextalias;
	xmlNodePtr alias_node;
	xmlNodePtr real_node;

	g_return_if_fail (gb_bookmark_is_alias (alias));
	
	real = gb_bookmark_real_bookmark (alias);

	alias_node = alias->xbel_node;
	if (alias_node)
	{
		xmlUnlinkNode (alias_node);
	}
	real_node = real->xbel_node;
	if (real_node)
	{
		xmlUnlinkNode (real_node);
	}

	if (GB_IS_FOLDER (real))
	{
		g_return_if_fail (!gb_folder_is_ancestor (GB_FOLDER (real), alias));

		children_rev = gb_folder_list_children_reversed (GB_FOLDER (real));
		for (li = children_rev; li; li = li->next)
		{
			g_object_ref (li->data);
			gb_bookmark_unparent (li->data);
		}
	}
	
	if (real->set && real->id)
	{
		g_hash_table_remove (real->set->id_to_bookmark, real->id);
	}

#ifdef NICK_HASHTABLE
	if (real->set)
	{
		g_hash_table_remove (real->set->nick_to_bookmark, real->nick);
	}
#endif

	if (GB_IS_SITE (real) && real->set)
	{
		g_hash_table_remove (real->set->url_to_bookmark, GB_SITE (real)->url);
	}

	prevalias = alias->alias_of;
	nextalias = alias->alias;

	g_assert (prevalias->alias == alias);
	g_assert (!nextalias || nextalias->alias_of == alias);
	
	prevalias->alias = nextalias;
	if (nextalias)
	{
		nextalias->alias_of = prevalias;
	}
	
	alias->alias_of = NULL;
	alias->alias = real;
	g_assert (real->alias_of == NULL);
	real->alias_of = alias;

	if (alias->set && alias->id)
	{
		g_hash_table_insert (alias->set->id_to_bookmark, alias->id, alias);
	}

#ifdef NICK_HASHTABLE
	if (alias->set)
	{
		g_hash_table_insert (alias->set->nick_to_bookmark, alias->nick);
	}
#endif

	if (GB_IS_SITE (alias) && alias->set)
	{
		g_hash_table_insert (alias->set->url_to_bookmark, GB_SITE (alias)->url, alias);
	}

	if (GB_IS_FOLDER (alias))
	{
		for (li = children_rev; li; li = li->next)
		{
			gb_folder_add_child (GB_FOLDER (alias), li->data, 0);
			g_object_unref (li->data);
		}
	}
	g_slist_free (children_rev);

	gb_bookmark_set_xbel_node (alias, real_node);
	gb_bookmark_set_xbel_node (real, alias_node);

	gb_bookmark_set_needs_saving (alias);

	do {
		g_signal_emit (alias, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (alias->parent, alias);
	} while ((alias = alias->alias) != NULL);
}


/**
 * Site object
 */ 

MAKE_GET_TYPE (gb_site, "GbSiteBookmark", GbSite, gb_site_class_init, 
	       gb_site_init, GB_TYPE_BOOKMARK);

static void
gb_site_class_init (GbSiteClass *klass)
{
	klass->parent_class.gb_bookmark_copy = gb_site_copy_impl;
	klass->parent_class.gb_bookmark_set_set = gb_site_set_set_impl;
	klass->parent_class.gb_bookmark_alias_create = gb_site_alias_create_impl;
	klass->parent_class.gb_bookmark_create_toolbar_widget = gb_site_create_toolbar_widget_impl;
	G_OBJECT_CLASS (klass)->finalize = gb_site_finalize_impl;

	GbSiteSignals[GB_SITE_URL_MODIFIED] = g_signal_new (
		"url-modified", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbSiteClass, gb_site_url_modified), 
		NULL, NULL, 
		galeon_marshal_VOID__STRING,
		G_TYPE_NONE, 1, G_TYPE_STRING);

	gb_site_class = klass;

}

static void 
gb_site_init (GbSite *b)
{
	b->url = g_strdup ("");
}

static void
gb_site_finalize_impl (GObject *o)
{
	GbBookmark *b = (GbBookmark *) o;
	GbSite *s = (GbSite *) b;
	if (!gb_bookmark_is_alias (b))
	{
		if (b->set)
		{
			g_hash_table_remove (b->set->url_to_bookmark, s->url);
		}
		
		if (b->alias && b->alias->set)
		{
			g_hash_table_insert (b->alias->set->url_to_bookmark, 
					     GB_SITE (b->alias)->url, b->alias);
		}
		else if (!b->alias)
		{
			g_free (s->url);
		}
	}
	gb_bookmark_finalize_impl (o);
}

GbSite *
gb_site_new (GbBookmarkSet *set, const char *name, 
	     const char *url)
{
	GbSite *b;
	if (name == NULL && url != NULL)
	{
		name = url;
	}

	b = g_object_new (GB_TYPE_SITE, NULL);
	gb_bookmark_set_name ((GbBookmark *) b, name);
	gb_site_set_url (b, url);
	gb_bookmark_set_set ((GbBookmark *) b, set);

	return b;
}

static void
gb_site_copy_impl_do (GbSite *b, GbSite *c)
{
	g_free (c->url);
	c->url = g_strdup (b->url);
	c->accel_key = b->accel_key;
	c->accel_mods = b->accel_mods;
	c->time_visited = b->time_visited;
}

GbBookmark *
gb_site_copy_impl (GbBookmark *b)
{
	GbBookmark *ret = g_object_new (GB_TYPE_SITE, NULL);
	gb_bookmark_copy_impl_do (b, ret);
	gb_site_copy_impl_do (GB_SITE (b), GB_SITE (ret));
 	return ret;
}

static void
gb_site_set_set_impl (GbBookmark *b, GbBookmarkSet *set)
{
	GbSite *s = (GbSite *) b;
	GbBookmarkSet *oldset;

	g_return_if_fail (GB_IS_SITE (b));
	g_return_if_fail (!set || GB_IS_BOOKMARK_SET (set));

	oldset = b->set;

	if (oldset && s->url && !gb_bookmark_is_alias (b))
	{
		g_hash_table_remove (oldset->url_to_bookmark, s->url);
	}

	gb_bookmark_set_set_impl (b, set);

	if (b->set && s->url && !gb_bookmark_is_alias (b))
	{
		g_hash_table_insert (set->url_to_bookmark, s->url, s);
	}
}

static GbBookmark *
gb_site_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap)
{
	GbSite *alias;

	g_return_val_if_fail (GB_IS_SITE (b), NULL);
	g_return_val_if_fail (!ap || GB_IS_ALIAS_PLACEHOLDER (ap), NULL);

	alias = g_object_new (GB_TYPE_SITE, NULL);

	gb_bookmark_alias_create_impl_do (b, ap, GB_BOOKMARK (alias));
	gb_site_alias_create_impl_do (b, ap, alias);

	return (GbBookmark *) alias;
}

static void
gb_site_alias_create_impl_do (GbBookmark *b, GbAliasPlaceholder *ap, GbSite *alias)
{
	GbSite *s = (GbSite *) b;

	g_free (alias->url);
	alias->url = s->url;
	alias->accel_key = s->accel_key;
	alias->accel_mods = s->accel_mods;
	alias->time_visited = s->time_visited;
}

void
gb_site_set_url (GbSite *s, const char *val)
{
	gchar *newval;
	GbBookmark *b = (GbBookmark *) s;

	g_return_if_fail (GB_IS_SITE (s));

	b = gb_bookmark_real_bookmark (b);
	s = (GbSite *) b;

	if (b->set)
	{
		if (s->url)
		{
			g_hash_table_remove (b->set->url_to_bookmark, s->url);
		}
	}
	
	g_free (s->url);
	newval = val ? g_strdup (val) : g_strdup ("");

	if (b->set)
	{
		if (val)
		{
			g_hash_table_insert (b->set->url_to_bookmark, newval, s);
		}
	}

	gb_bookmark_set_needs_saving (b);

	if (b->set)
	{
		gb_bookmark_set_emit_autocompletion_source_data_changed (b->set);
	}

	do {
		s->url = newval;
		g_signal_emit (s, GbSiteSignals[GB_SITE_URL_MODIFIED], 0, s->url);
		g_signal_emit (s, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (b->parent, b);
	} while ((s = (GbSite *) (b = b->alias)) != NULL);
}

void
gb_site_set_time_visited (GbSite *b, GTime val)
{
	g_return_if_fail (GB_IS_SITE (b));
	
	b = GB_SITE (gb_bookmark_real_bookmark ((GbBookmark *) b));

	gb_bookmark_set_needs_saving (b);

	do {
		b->time_visited = val;
		g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (((GbBookmark *) b)->parent, (GbBookmark *) b);
	} while ((b = (GbSite *) ((GbBookmark *) b)->alias) != NULL);
}

void
gb_site_set_time_visited_now (GbSite *b)
{
	gb_site_set_time_visited (b, gb_system_get_current_time ());
}

void
gb_site_set_accel (GbSite *b, guint accel_key, guint accel_mods)
{
	g_return_if_fail (GB_IS_BOOKMARK (b));
	
	b = GB_SITE (gb_bookmark_real_bookmark ((GbBookmark *) b));

	gb_bookmark_set_needs_saving (b);

	do {
		b->accel_mods = accel_mods;
		b->accel_key = accel_key;
		g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (((GbBookmark *)b)->parent, (GbBookmark *) b);
	} while ((b = (GbSite *) ((GbBookmark *) b)->alias) != NULL);
}

static GbTbWidget *
gb_site_create_toolbar_widget_impl (GbBookmark *b)
{
	g_return_val_if_fail (GB_IS_SITE (b), NULL);
	return gb_create_toolbar_widget_site (GB_SITE (b));
}

/**
 * SmartSite object
 */

MAKE_GET_TYPE (gb_smart_site, "GbSmartSite", GbSmartSite, gb_smart_site_class_init, 
	       gb_smart_site_init, GB_TYPE_SITE);

#define DEFAULT_SMART_SITE_ENTRY_SIZE 100

static void
gb_smart_site_class_init (GbSmartSiteClass *klass)
{
	klass->parent_class.parent_class.gb_bookmark_copy = gb_smart_site_copy_impl;
	klass->parent_class.parent_class.gb_bookmark_alias_create = gb_smart_site_alias_create_impl;
	klass->parent_class.parent_class.gb_bookmark_create_toolbar_widget = 
		gb_smart_site_create_toolbar_widget_impl;
	G_OBJECT_CLASS (klass)->finalize = gb_smart_site_finalize_impl;

	GbSmartSiteSignals[GB_SMART_SITE_ENTRY_WIDTH_CHANGED] = g_signal_new (
		"entry-width-changed", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbSmartSiteClass, gb_smart_site_entry_width_changed), 
		NULL, NULL, 
		galeon_marshal_VOID__INT_INT,
		G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);
	GbSmartSiteSignals[GB_SMART_SITE_VISIBILITY_CHANGED] = g_signal_new (
		"visibility-changed", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbSmartSiteClass, gb_smart_site_visibility_changed), 
		NULL, NULL, 
		galeon_marshal_VOID__VOID,
		G_TYPE_NONE, 0);
	GbSmartSiteSignals[GB_SMART_SITE_HISTORY_CHANGED] = g_signal_new (
		"history-changed", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbSmartSiteClass, gb_smart_site_history_changed), 
		NULL, NULL, 
		galeon_marshal_VOID__VOID,
		G_TYPE_NONE, 0);

	gb_smart_site_class = klass;

}

static GbBookmark *
gb_smart_site_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap)
{
	GbSmartSite *alias;
	GbSmartSite *s = (GbSmartSite *) b;

	g_return_val_if_fail (GB_IS_SMART_SITE (b), NULL);
	g_return_val_if_fail (!ap || GB_IS_ALIAS_PLACEHOLDER (ap), NULL);

	alias = g_object_new (GB_TYPE_SMART_SITE, NULL);

	gb_bookmark_alias_create_impl_do (b, ap, GB_BOOKMARK (alias));
	gb_site_alias_create_impl_do (b, ap, GB_SITE (alias));
	
	g_free (alias->smarturl);
	alias->smarturl = s->smarturl;

	return (GbBookmark *) alias;
}

GbBookmark *
gb_smart_site_copy_impl (GbBookmark *b)
{
	GbBookmark *ret = g_object_new (GB_TYPE_SMART_SITE, NULL);
	gb_bookmark_copy_impl_do (b, ret);
	gb_site_copy_impl_do (GB_SITE (b), GB_SITE (ret));
	gb_smart_site_copy_impl_do (GB_SMART_SITE (b), GB_SMART_SITE (ret));
	return ret;
}

static void
gb_smart_site_copy_impl_do (GbSmartSite *b, GbSmartSite *c)
{
	int i, n;
	g_free (c->smarturl);
	c->smarturl = g_strdup (b->smarturl);
	c->folded = b->folded;
	n = gb_smart_site_get_num_fields (b);
	if (n > 0 && b->entries_sizes)
	{
		c->entries_sizes = g_new0 (int, n);
		for (i = 0; i < n; ++i)
		{
			c->entries_sizes[i] = b->entries_sizes[i];
		}
	}
	else
	{
		c->entries_sizes = NULL;
	}
}

static void
gb_smart_site_init (GbSmartSite *b)
{
	b->smarturl = g_strdup ("");
	b->folded = TRUE;
	b->entries_sizes = NULL;
	b->history = NULL;
}

static void
gb_smart_site_finalize_impl (GObject *o)
{
	GbBookmark *b = (GbBookmark *) o;
	GbSmartSite *s = (GbSmartSite *) b;
	if (!gb_bookmark_is_alias (b) && !b->alias)
	{
		g_free (s->smarturl);
		g_slist_foreach (s->history, (GFunc) g_free, NULL);
		g_slist_free (s->history);
		g_free (s->entries_sizes);
	}
	gb_site_finalize_impl (o);
}

GbSmartSite *
gb_smart_site_new (GbBookmarkSet *set, const char *name, 
		   const char *url, const char *smarturl)
{
	GbSmartSite *b;
	
	b = g_object_new (GB_TYPE_SMART_SITE, NULL);
	gb_bookmark_set_name ((GbBookmark *) b, name);
	gb_site_set_url ((GbSite *) b, url);
	gb_smart_site_set_smarturl_full (b, smarturl);
	gb_bookmark_set_set ((GbBookmark *) b, set);
	return b;
}

GbSmartSite *
gb_smart_site_new_from_site (GbSite *site, const char *smarturl)
{
	NOT_IMPLEMENTED;
	return NULL;
}

static void
gb_smart_site_set_smarturl_full (GbSmartSite *s, const gchar *val)
{
	gchar *newval;
	GbBookmark *b = (GbBookmark *) s;

	g_return_if_fail (GB_IS_SMART_SITE (s));

	b = gb_bookmark_real_bookmark (b);
	s = (GbSmartSite *) b;

	g_free (s->smarturl);
	newval = val ? g_strdup (val) : g_strdup ("");

	gb_bookmark_set_needs_saving (b);

	do {
		s->smarturl = newval;
		g_signal_emit (s, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (b->parent, b);
	} while ((s = (GbSmartSite *) (b = b->alias)) != NULL);
}

static GbTbWidget *
gb_smart_site_create_toolbar_widget_impl (GbBookmark *b)
{
	g_return_val_if_fail (GB_IS_SITE (b), NULL);
	return gb_create_toolbar_widget_smart_site (GB_SMART_SITE (b));
}

gint
gb_smart_site_get_num_fields (GbSmartSite *b)
{
	return 1;
}

gint
gb_smart_site_get_entry_size (GbSmartSite *b, gint index)
{
	if (b->entries_sizes)
	{
		return b->entries_sizes[index];
	}
	return DEFAULT_SMART_SITE_ENTRY_SIZE;
}

void
gb_smart_site_set_entry_size (GbSmartSite *b, gint index, gint width)
{
	gint num_entries = gb_smart_site_get_num_fields (b);
	if (index >= num_entries)
	{
		return;
	}
	if (!b->entries_sizes)
	{
		b->entries_sizes = g_new0 (int, num_entries);
	}
	b->entries_sizes[index] = width;
	g_signal_emit (b, GbSmartSiteSignals[GB_SMART_SITE_ENTRY_WIDTH_CHANGED], 0, index, width);
	g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
	gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));

	gb_bookmark_set_needs_saving (b);
}

void
gb_smart_site_set_folded (GbSmartSite *b, gboolean folded)
{
	if (b->folded != folded)
	{
		b->folded = folded;
		g_signal_emit (b, GbSmartSiteSignals[GB_SMART_SITE_VISIBILITY_CHANGED], 0);
		g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (GB_BOOKMARK (b)->parent, GB_BOOKMARK (b));
		gb_bookmark_set_needs_saving (b);
	}
}

gchar *
gb_smart_site_subst_args (GbSmartSite *b, gchar **args)
{
	guint expected_args;
	guint i;
	gchar *ret;
	GString *s;
	gchar *t1;
	gchar *t2;
	gchar **encoded_args;
	gchar *encoding;
	gchar *smarturl_only;
	guint nargs;
	
	g_return_val_if_fail (GB_IS_SMART_SITE (b), NULL);
	g_return_val_if_fail (args != NULL, NULL);

	smarturl_only = gb_smart_site_get_smarturl_only (b);
	
	if (args[0] == NULL) 
	{
		return smarturl_only;
	}
	
	s = g_string_new ("");
	expected_args = gb_smart_site_get_num_fields (b);
	encoding = gb_smart_site_get_encoding (b);

	/* count the number of available args */
	for (nargs = 0; args[nargs]; nargs++) ;

	/* encode args */
	encoded_args = g_new0 (gchar *, nargs + 1);
	for (i = 0; i < nargs; i++)
	{
		gchar *arg = g_convert (args[i], strlen (args[i]), encoding, "UTF-8", NULL, NULL, NULL);
		if (arg)
		{
			encoded_args[i] = gnome_vfs_escape_string (arg);
			g_free (arg);
		}
		else
		{
			encoded_args[i] = gnome_vfs_escape_string (args[i]);
		}
	}
	encoded_args[nargs] = NULL;

	/* subst args */

	args = encoded_args;
	t1 = smarturl_only;
	for (i = 0; i < expected_args - 1; i++)
	{
		t2 = strstr (t1, "%s");
		if (!t2) 
		{
			break;
		}
		g_string_append_len (s, t1, t2 - t1);
		g_string_append (s, *args);
		args++;
		t1 = t2 + 2;

		/* repeat the last arg if there are not enough */
		if (*args == NULL)
		{
			args--;
		}
	}

	/* there may (should) be still a %s, the last one. Put all the
	   remaining args in it, separated with (url-encoded) spaces */
	t2 = strstr (t1, "%s");
	if (t2) 
	{
		g_string_append_len (s, t1, t2 - t1);
		while (*args != NULL)
		{
			g_string_append (s, *args);
			args++;
			if (*args != NULL)
			{
				g_string_append (s, "%20");
			}
		}
		t1 = t2 + 2;
	}
	g_string_append (s, t1);

	ret = s->str;
	g_string_free (s, FALSE);

	g_strfreev (encoded_args);
	g_free (encoding);
	g_free (smarturl_only);

	return ret;
}

static gchar *
gb_smart_site_get_smarturl_only (GbSmartSite *b)
{
	const gchar *openbrace;
	const gchar *closebrace;
	const gchar *c;
	
	openbrace = strchr (b->smarturl, '{');
	if (!openbrace) return g_strdup (b->smarturl);
	for (c = b->smarturl; c < openbrace; ++c)
	{
		if (!strchr (" \t\n", *c)) return g_strdup (b->smarturl);
	}

	closebrace = strchr (openbrace + 1, '}');
	if (!closebrace) return g_strdup (b->smarturl);

	return g_strdup (closebrace + 1);
}

static gchar *
gb_smart_site_get_options (GbSmartSite *b)
{
	const gchar *openbrace;
	const gchar *closebrace;
	const gchar *c;
	
	openbrace = strchr (b->smarturl, '{');
	if (!openbrace) return g_strdup ("");
	for (c = b->smarturl; c < openbrace; ++c)
	{
		if (!strchr (" \t\n", *c)) return g_strdup ("");
	}

	closebrace = strchr (openbrace + 1, '}');
	if (!closebrace) return g_strdup ("");

	return g_strndup (openbrace + 1, closebrace - (openbrace + 1));
}

static void
gb_smart_site_set_options (GbSmartSite *b, gchar *options)
{
	const gchar *openbrace;
	const gchar *closebrace;
	const gchar *c;
	gchar *new_smarturl;
		
	openbrace = strchr (b->smarturl, '{');
	if (!openbrace) goto no_previous_options;
	for (c = b->smarturl; c < openbrace; ++c)
	{
		if (!strchr (" \t\n", *c)) goto no_previous_options;
	}

	closebrace = strchr (openbrace + 1, '}');
	if (!closebrace) goto no_previous_options;

	new_smarturl = g_strconcat ("{", options, "}", closebrace + 1, NULL);
	gb_smart_site_set_smarturl_full (b, new_smarturl);
	g_free (new_smarturl);
	return;

 no_previous_options:
	new_smarturl = g_strconcat ("{", options, "}", b->smarturl, NULL);
	gb_smart_site_set_smarturl_full (b, new_smarturl);
	g_free (new_smarturl);
}

gchar *
gb_smart_site_get_encoding (GbSmartSite *b)
{
	gchar *ret;
	gchar *options;

	g_return_val_if_fail (GB_IS_SMART_SITE (b), NULL);
	g_return_val_if_fail (b->smarturl, NULL);
	
	options = gb_smart_site_get_options (b);
	ret = gb_util_options_get (options, "encoding");
	g_free (options);
	
	if (!ret)
	{
		ret = g_strdup ("UTF-8");
	}
	return ret;
}

void
gb_smart_site_set_encoding (GbSmartSite *b, const gchar *encoding)
{
	gchar *newoptions;
	gchar *options;

	g_return_if_fail (GB_IS_SMART_SITE (b));
	g_return_if_fail (b->smarturl);
	
	options = gb_smart_site_get_options (b);
	newoptions = gb_util_options_set (options, "encoding", encoding);
	g_free (options);

	gb_smart_site_set_options (b, newoptions);
}

gchar *
gb_smart_site_get_smarturl (GbSmartSite *b)
{
	return gb_smart_site_get_smarturl_only (b);
}

void
gb_smart_site_set_smarturl (GbSmartSite *b, const gchar *url)
{
	gchar *options;
	gchar *new_smarturl;

	g_return_if_fail (GB_IS_SMART_SITE (b));
	options = gb_smart_site_get_options (b);

	new_smarturl = g_strconcat ("{", options, "}", url, NULL);
	gb_smart_site_set_smarturl_full (b, new_smarturl);
	g_free (new_smarturl);
	g_free (options);
}

GSList *
gb_smart_site_get_history (GbSmartSite *b, int entry_index)
{
	GSList *ret = NULL;
	GSList *li;

	g_return_val_if_fail (GB_IS_SMART_SITE (b), NULL);
	
	for (li = b->history; li; li = li->next)
	{
		ret = g_slist_prepend (ret, g_strdup (li->data));
	}

	return g_slist_reverse (ret);
}

void
gb_smart_site_set_history (GbSmartSite *s, int entry_index, const GSList *history)
{
	GSList *newval = NULL;
	const GSList *li;
	GbBookmark *b = (GbBookmark *) s;

	g_return_if_fail (GB_IS_SMART_SITE (s));

	b = gb_bookmark_real_bookmark (b);
	s = (GbSmartSite *) b;
	
	g_slist_foreach (s->history, (GFunc) g_free, NULL);
	g_slist_free (s->history);
	for (li = history; li; li = li->next)
	{
		newval = g_slist_prepend (newval, g_strdup (li->data));
	}
	newval = g_slist_reverse (newval);

	gb_bookmark_set_needs_saving (b);

	do {
		s->history = newval;
		g_signal_emit (s, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		g_signal_emit (s, GbSmartSiteSignals[GB_SMART_SITE_HISTORY_CHANGED], 0);
		gb_folder_emit_child_modified (b->parent, b);
	} while ((s = (GbSmartSite *) (b = b->alias)) != NULL);
}

void
gb_smart_site_prepend_history (GbSmartSite *b, int entry_index, const gchar *item)
{
	GSList *newhistory = NULL;
	GSList *li;
	gint max_smart_site_history_items;
	int items = 0;

	g_return_if_fail (GB_IS_SMART_SITE (b));
	LOG ("in gb_smart_site_prepend_history");

	max_smart_site_history_items = (GB_BOOKMARK (b)->set) 
		? GB_BOOKMARK (b)->set->max_smart_site_history_items
		: 15;
	
	for (li = b->history; li; li = li->next)
	{
		if (items < (max_smart_site_history_items - 1)
		    && strcmp (li->data, item))
		{
			newhistory = g_slist_prepend (newhistory, g_strdup (li->data));
			items++;
		}
	}

	newhistory = g_slist_reverse (newhistory);
	newhistory = g_slist_prepend (newhistory, g_strdup (item));

	gb_smart_site_set_history (b, entry_index, newhistory);

	g_slist_foreach (newhistory, (GFunc) g_free, NULL);
	g_slist_free (newhistory);

	LOG ("%d history items", g_slist_length (b->history));
}

/**
 * Folder object
 */

MAKE_GET_TYPE (gb_folder, "GbFolderBookmark", GbFolder, gb_folder_class_init, 
	       gb_folder_init, GB_TYPE_BOOKMARK);

static void
gb_folder_class_init (GbFolderClass *klass)
{
	klass->parent_class.gb_bookmark_copy = gb_folder_copy_impl;
	klass->parent_class.gb_bookmark_set_set = gb_folder_set_set_impl;
	klass->parent_class.gb_bookmark_alias_create = gb_folder_alias_create_impl;
	klass->parent_class.gb_bookmark_create_toolbar_widget = gb_folder_create_toolbar_widget_impl;
	G_OBJECT_CLASS (klass)->finalize = gb_folder_finalize_impl;
	G_OBJECT_CLASS (klass)->dispose = gb_folder_dispose_impl;

	GbFolderSignals[GB_FOLDER_CHILD_MODIFIED] = g_signal_new (
		"child-modified", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbFolderClass, gb_folder_descendant_modified), 
		NULL, NULL, 
		galeon_marshal_VOID__OBJECT,
		G_TYPE_NONE, 1, GB_TYPE_BOOKMARK);
	GbFolderSignals[GB_FOLDER_CHILD_ADDED] = g_signal_new (
		"child-added", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbFolderClass, gb_folder_child_added), 
		NULL, NULL, 
		galeon_marshal_VOID__OBJECT_INT,
		G_TYPE_NONE, 2, GB_TYPE_BOOKMARK, G_TYPE_INT);
	GbFolderSignals[GB_FOLDER_CHILD_REMOVED] = g_signal_new (
		"child-removed", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbFolderClass, gb_folder_child_removed), 
		NULL, NULL, 
		galeon_marshal_VOID__OBJECT_INT,
		G_TYPE_NONE, 2, GB_TYPE_BOOKMARK, G_TYPE_INT);
	GbFolderSignals[GB_FOLDER_DESCENDANT_MODIFIED] = g_signal_new (
		"descendant-modified", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbFolderClass, gb_folder_descendant_modified), 
		NULL, NULL, 
		galeon_marshal_VOID__OBJECT,
		G_TYPE_NONE, 1, GB_TYPE_BOOKMARK);
	GbFolderSignals[GB_FOLDER_DESCENDANT_ADDED] = g_signal_new (
		"descendant-added", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbFolderClass, gb_folder_descendant_added), 
		NULL, NULL, 
		galeon_marshal_VOID__OBJECT_OBJECT_INT,
		G_TYPE_NONE, 3, GB_TYPE_BOOKMARK, GB_TYPE_BOOKMARK, G_TYPE_INT);
	GbFolderSignals[GB_FOLDER_DESCENDANT_REMOVED] = g_signal_new (
		"descendant-removed", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbFolderClass, gb_folder_descendant_removed), 
		NULL, NULL, 
		galeon_marshal_VOID__OBJECT_OBJECT_INT,
		G_TYPE_NONE, 3, GB_TYPE_BOOKMARK, GB_TYPE_BOOKMARK, G_TYPE_INT);

	gb_folder_class = klass;
}

static void 
gb_folder_init (GbFolder *b)
{
	b->child = NULL;
	b->create_toolbar = FALSE;
}

static void
gb_folder_set_set_impl (GbBookmark *b, GbBookmarkSet *set)
{
	GbFolder *f = (GbFolder *) b;
	GbBookmark *bi;
	GbBookmarkSet *oldset;

	g_return_if_fail (GB_IS_FOLDER (b));
	g_return_if_fail (!set || GB_IS_BOOKMARK_SET (set));

	oldset = b->set;
	
	if (oldset && f->create_toolbar)
	{
		oldset->toolbars = g_slist_remove (oldset->toolbars, f);
		g_signal_emit (oldset, GbBookmarkSetSignals[GB_BOOKMARK_SET_TOOLBAR], 0, b);
	}

	gb_bookmark_set_set_impl (b, set);

	if (set && f->create_toolbar)
	{
		set->toolbars = g_slist_prepend (set->toolbars, f);
		g_signal_emit (set, GbBookmarkSetSignals[GB_BOOKMARK_SET_TOOLBAR], 0, b);
	}

	/* set the set of the children too */
	if (!gb_bookmark_is_alias (b))
	{
		for (bi = f->child; bi; bi = bi->next)
		{
			gb_bookmark_set_set (bi, set);
		}
	}
}

static GbBookmark *
gb_folder_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap)
{
	GbFolder *alias;

	g_return_val_if_fail (GB_IS_FOLDER (b), NULL);
	g_return_val_if_fail (!ap || GB_IS_ALIAS_PLACEHOLDER (ap), NULL);

	alias = g_object_new (GB_TYPE_FOLDER, NULL);

	gb_bookmark_alias_create_impl_do (b, ap, GB_BOOKMARK (alias));
	gb_folder_alias_create_impl_do (b, ap, alias);

	return (GbBookmark *) alias;
}

static void
gb_folder_dispose_impl (GObject *o)
{
	GbBookmark *b = (GbBookmark *) o;
	GbFolder *f = (GbFolder *) b;

	LOG ("in gb_folder_dispose_impl");

	if (!gb_bookmark_is_alias (b))
	{
		/* when the real bookmark is destroyed, its children are unparented, even if 
		   there was an alias of the parent. This is suboptimal. */
		while (f->child) 
		{
			/* teorically, we can emit signals here. But I rather don't, 
			   because some objects might be already finalized */
			gb_bookmark_unparent_internal (f->child, FALSE);
		}
	}
	G_OBJECT_CLASS (gb_bookmark_class)->dispose (o);
}

static void
gb_folder_finalize_impl (GObject *o)
{
	LOG ("in gb_folder_finalize_impl");

	g_assert ((((GbFolder *) o)->child == NULL) || gb_bookmark_is_alias (o));
	
	gb_bookmark_finalize_impl (o);
}

static void
gb_folder_alias_create_impl_do (GbBookmark *b, GbAliasPlaceholder *ap, GbFolder *alias)
{
	GbFolder *f = (GbFolder *) b;
	if (ap)
		alias->create_toolbar = ap->create_toolbar;
	else
		alias->create_toolbar = f->create_toolbar;
	alias->child = f->child;
}

static void
gb_folder_copy_impl_do (GbFolder *b, GbFolder *c)
{
	GbBookmark *last_copied = NULL;
	GbBookmark *bi;
	c->create_toolbar = b->create_toolbar;
	c->expanded = b->expanded;
	c->child = NULL;

	for (bi = b->child; bi; bi = bi->next)
	{
		/* this is not correct, it should copy aliases... */
		if (!gb_bookmark_is_alias (bi) || !GB_IS_FOLDER (bi))
		{
			GbBookmark *ci = gb_bookmark_copy (bi);
			if (last_copied)
			{
				last_copied->next = ci;
				ci->prev = last_copied;
			}
			else
			{
				c->child = ci;
			}
			ci->parent = GB_FOLDER (c);
			last_copied = ci;
		}
	}
}

GbBookmark *
gb_folder_copy_impl (GbBookmark *b)
{
	GbBookmark *ret = g_object_new (GB_TYPE_FOLDER, NULL);
	gb_bookmark_copy_impl_do (b, ret);
	gb_folder_copy_impl_do (GB_FOLDER (b), GB_FOLDER (ret));
	return ret;
}

GbFolder *
gb_folder_new (GbBookmarkSet *set, const gchar *name)
{
	GbFolder *b;

	b = g_object_new (GB_TYPE_FOLDER, NULL);
	gb_bookmark_set_name ((GbBookmark *) b, name);
	gb_bookmark_set_set ((GbBookmark *) b, set);

	if (!set->root)
	{
		gb_bookmark_set_set_root (set, b);
	}

	return b;
}

void
gb_folder_set_create_toolbar (GbFolder *b, gboolean val)
{
	g_return_if_fail (GB_IS_FOLDER (b));

	val = !!val;

	if (b->create_toolbar != val)
	{
		GbBookmarkSet *set = GB_BOOKMARK (b)->set;
		b->create_toolbar = val;
		
		if (val && set)
		{
			set->toolbars = g_slist_prepend (set->toolbars, b);
		} 
		else if (set)
		{
			set->toolbars = g_slist_remove (set->toolbars, b);
		}

		g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (((GbBookmark *) b)->parent, (GbBookmark *) b);
		g_signal_emit (set, GbBookmarkSetSignals[GB_BOOKMARK_SET_TOOLBAR], 0, b);

		gb_bookmark_set_needs_saving (b);
	}
}

void
gb_folder_set_expanded (GbFolder *b, gboolean val)
{
	g_return_if_fail (GB_IS_FOLDER (b));

	b->expanded = !!val;

	g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
	gb_folder_emit_child_modified (((GbBookmark *) b)->parent, (GbBookmark *) b);
}

/*void
gb_folder_set_toolbar_style (BookmarkItem *b, GtkToolbarStyle val)
{
	NOT_IMPLEMENTED;
}
*/

void
gb_folder_add_child (GbFolder *p, GbBookmark *c, gint position)
{
	GbBookmark *cnext, *cprev;
	GbBookmark *first_child;
	gint i;
	GbFolder *pi;
	
	g_return_if_fail (GB_IS_FOLDER (p));
	g_return_if_fail (GB_IS_BOOKMARK (c));
	g_return_if_fail (position >= -1);
	g_return_if_fail (GB_BOOKMARK (p)->set);
	g_return_if_fail (GB_BOOKMARK (p) != c);

	if (gb_bookmark_is_alias (c)
	    && gb_bookmark_real_bookmark (c)->parent == NULL 
	    && gb_bookmark_real_bookmark (c) != GB_BOOKMARK (gb_bookmark_real_bookmark (c)->set->root))
	{
		g_warning ("Adding an alias whose real bookmark is unparented.");
	}
	
	p = GB_FOLDER (gb_bookmark_real_bookmark ((GbBookmark *) p));

	g_object_ref (G_OBJECT (c));

	gb_bookmark_unparent (c);

	g_assert (c->parent == NULL);
	g_assert (c->next == NULL);
	g_assert (c->prev == NULL);

	if (c->set != GB_BOOKMARK (p)->set)
	{
		gb_bookmark_set_set (c, GB_BOOKMARK (p)->set);
	}
	
	gb_bookmark_set_tree_changed (GB_BOOKMARK (p)->set);

	cprev = NULL;
	cnext = p->child;
	i = 0;
	while (i != position && cnext != NULL)
	{
		i++;
		cprev = cnext;
		cnext = cnext->next;
	}

	c->parent = p;
	if (cprev == NULL)
	{
		g_assert (cnext == p->child);
		p->child = c;
	}

	c->next = cnext;
	c->prev = cprev;
	if (cprev)
	{
		cprev->next = c;
	}
	if (cnext)
	{
		cnext->prev = c;
	}
	
	gb_bookmark_set_needs_saving (p);

	pi = p;
	first_child = p->child;
	do {
		pi->child = first_child;
	} while ((pi = (GbFolder *) ((GbBookmark *) pi)->alias) != NULL);

	gb_folder_emit_child_added (p, c, i);
	if (GB_BOOKMARK (p)->set)
	{
		gb_bookmark_set_emit_autocompletion_source_data_changed (GB_BOOKMARK (p)->set);
	}
}

gint
gb_folder_get_child_index (GbFolder *p, GbBookmark *b)
{
	gint index;
	GbBookmark *c;

	g_return_val_if_fail (GB_IS_FOLDER (p), -1);
	g_return_val_if_fail (GB_IS_BOOKMARK (b), -1);
	
	c = p->child;
	index = 0;
	while (c && c != b)
	{
		c = c->next;
		index++;
	}
	
	if (c == b)
	{
		g_assert (b->parent == (GbFolder *) gb_bookmark_real_bookmark ((GbBookmark *) p));
		return index;
	}
	else 
	{
		g_assert (b->parent != p);
		return -1;
	}
}

void
gb_folder_move_child (GbFolder *p, GbBookmark *c, gint position)
{
	gint old_position;

	g_return_if_fail (GB_IS_FOLDER (p));
	g_return_if_fail (GB_IS_BOOKMARK (c));
	g_return_if_fail (p == c->parent);

	old_position = gb_folder_get_child_index (p, c);

	if (old_position != position)
	{
		g_object_ref (c);
		gb_bookmark_unparent (c);
		gb_folder_add_child (p, c, position);
		g_object_unref (c);
		gb_bookmark_set_needs_saving (p);
	}
}

static void
gb_folder_emit_child_modified (GbFolder *f, GbBookmark *b)
{
	if (f)
	{
		GbFolder *fi;
		g_return_if_fail (((GbBookmark *) f)->alias_of == NULL);

 		for (fi = f; fi; fi = (GbFolder *) ((GbBookmark *) fi)->alias)
		{
			g_signal_emit (fi, GbFolderSignals[GB_FOLDER_CHILD_MODIFIED], 0, b);
		}
		gb_folder_emit_descendant_modified (f, b);
	}
}

static void
gb_folder_emit_child_added (GbFolder *f, GbBookmark *b, gint pos)
{
	if (f)
	{
		GbFolder *fi;
		g_return_if_fail (((GbBookmark *) f)->alias_of == NULL);

 		for (fi = f; fi; fi = (GbFolder *) ((GbBookmark *) fi)->alias)
		{
			g_signal_emit (fi, GbFolderSignals[GB_FOLDER_CHILD_ADDED], 0, b, pos);
		}
		gb_folder_emit_descendant_added (f, f, b, pos);
	}
}

static void
gb_folder_emit_child_removed (GbFolder *f, GbBookmark *b, gint pos)
{
	if (f)
	{
		GbFolder *fi;
		g_return_if_fail (((GbBookmark *) f)->alias_of == NULL);

 		for (fi = f; fi; fi = (GbFolder *) ((GbBookmark *) fi)->alias)
		{
			g_signal_emit (fi, GbFolderSignals[GB_FOLDER_CHILD_REMOVED], 0, b, pos);
		}
		gb_folder_emit_descendant_removed (f, f, b, pos);
	}
}

static void
gb_folder_emit_descendant_modified (GbFolder *f, GbBookmark *b)
{
	if (f)
	{
		GbFolder *fi;
		g_return_if_fail (((GbBookmark *) f)->alias_of == NULL);

 		for (fi = f; fi; fi = (GbFolder *) ((GbBookmark *) fi)->alias)
		{
			g_signal_emit (fi, GbFolderSignals[GB_FOLDER_DESCENDANT_MODIFIED], 0, b);
		}
		gb_folder_emit_descendant_modified (((GbBookmark *) f)->parent, b);
	}
}

static void
gb_folder_emit_descendant_added (GbFolder *f, GbFolder *p, GbBookmark *b, gint pos)
{
	if (f)
	{
		GbFolder *fi;
		g_return_if_fail (((GbBookmark *) f)->alias_of == NULL);

 		for (fi = f; fi; fi = (GbFolder *) ((GbBookmark *) fi)->alias)
		{
			g_signal_emit (fi, GbFolderSignals[GB_FOLDER_DESCENDANT_ADDED], 0, p, b, pos);
		}
		gb_folder_emit_descendant_added (((GbBookmark *) f)->parent, p, b, pos);
	}
}

static void
gb_folder_emit_descendant_removed (GbFolder *f, GbFolder *p, GbBookmark *b, gint pos)
{
	if (f)
	{
		GbFolder *fi;
		g_return_if_fail (((GbBookmark *) f)->alias_of == NULL);

 		for (fi = f; fi; fi = (GbFolder *) ((GbBookmark *) fi)->alias)
		{
			g_signal_emit (fi, GbFolderSignals[GB_FOLDER_DESCENDANT_REMOVED], 0, p, b, pos);
		}
		gb_folder_emit_descendant_removed (((GbBookmark *) f)->parent, p, b, pos);
	}
}

static GbTbWidget *
gb_folder_create_toolbar_widget_impl (GbBookmark *b)
{
	g_return_val_if_fail (GB_IS_FOLDER (b), NULL);
	return gb_create_toolbar_widget_folder (GB_FOLDER (b));
}

gboolean
gb_folder_is_ancestor (GbFolder *p, GbBookmark *b)
{
	if (p == b->parent)
	{
		return TRUE;
	}
	else if ((GbBookmark *) p == b || b->parent == NULL)
	{
		return FALSE;
	}
	else
	{
		return gb_folder_is_ancestor (p, (GbBookmark *) b->parent);
	}
}

gboolean
gb_folder_is_default_folder (GbFolder *f)
{
	GbFolder *rf = GB_FOLDER (gb_bookmark_real_bookmark (GB_BOOKMARK (f)));
	
	return (GB_BOOKMARK (rf)->set) && (rf == GB_BOOKMARK (rf)->set->default_folder);
}

static GSList *
gb_folder_list_children_reversed (GbFolder *f)
{
	GSList *l = NULL;
	GbBookmark *bi;

	for (bi = f->child; bi; bi = bi->next)
	{
		l = g_slist_prepend (l, bi);
	}

	return l;
}

GSList *
gb_folder_list_children (GbFolder *f)
{
	return g_slist_reverse (gb_folder_list_children_reversed (f));
}

static gint 
gb_folder_sort_compare_func (gconstpointer a, gconstpointer b, gpointer user_data)
{
	GbBookmark *ba = GB_BOOKMARK (a);
	GbBookmark *bb = GB_BOOKMARK (b);
	gboolean folders_first = GPOINTER_TO_INT (user_data);
	if (folders_first)
	{
		if (GB_IS_FOLDER (ba) && !GB_IS_FOLDER (bb))
		{
			return -1;
		} 
		else if (!GB_IS_FOLDER (ba) && GB_IS_FOLDER (bb))
		{
			return 1;
		}
	}
	return g_utf8_collate (ba->name, bb->name);
}

void
gb_folder_sort (GbFolder *f, gboolean folders_first, gboolean recursive)
{
	GSList *l;
	GSList *li;
	gint i = 0;
	
	g_return_if_fail (GB_IS_FOLDER (f));

	l = gb_folder_list_children (f);
	l = g_slist_sort_with_data (l, gb_folder_sort_compare_func, GINT_TO_POINTER (folders_first));

	for (li = l; li; li = li->next)
	{
		gb_folder_move_child (f, li->data, i);
		++i;
		if (recursive && !gb_bookmark_is_alias (li->data) && GB_IS_FOLDER (li->data))
		{
			gb_folder_sort (li->data, folders_first, recursive);
		}
	}

	g_slist_free (l);
}

gboolean 
gb_folder_has_child_or_alias (GbFolder *f, GbBookmark *c)
{
	f = GB_FOLDER (gb_bookmark_real_bookmark (GB_BOOKMARK (f)));
	for (c = gb_bookmark_real_bookmark (c); c; c= c->alias)
	{
		if (c->parent == f)
		{
			return TRUE;
		}
	}
	return FALSE;
}

static GbBookmark *
gb_folder_get_child_or_alias (GbFolder *f, GbBookmark *c)
{
	GbBookmark *i;
	f = GB_FOLDER (gb_bookmark_real_bookmark (GB_BOOKMARK (f)));
	c = gb_bookmark_real_bookmark (c);
	for (i = f->child; i; i = i->next)
	{
		if (gb_bookmark_real_bookmark (i) == c)
		{
			return i;
		}
	}
	return NULL;
}

void
gb_folder_remove_child_or_aliases (GbFolder *f, GbBookmark *c)
{
	GbBookmark *v;
	g_object_ref (c);
	while ((v = gb_folder_get_child_or_alias (f, c)) != NULL)
	{
		gb_bookmark_unparent_safe (v);
	}
	g_object_unref (c);
}

/**
 * Separator object
 */

MAKE_GET_TYPE (gb_separator, "GbSeparator", GbSeparator, gb_separator_class_init, 
	       gb_separator_init, GB_TYPE_BOOKMARK);

GbSeparator *
gb_separator_new (GbBookmarkSet *set)
{
	GbSeparator *b = g_object_new (GB_TYPE_SEPARATOR, NULL);
	gb_bookmark_set_set ((GbBookmark *) b, set);
	return b;
}

static void 
gb_separator_init (GbSeparator *b)
{
}

static void
gb_separator_class_init (GbSeparatorClass *klass)
{
	klass->parent_class.gb_bookmark_copy = gb_separator_copy_impl;
	klass->parent_class.gb_bookmark_alias_create = gb_separator_alias_create_impl;
	klass->parent_class.gb_bookmark_create_toolbar_widget = gb_separator_create_toolbar_widget_impl;

	gb_separator_class = klass;
}

static GbBookmark *
gb_separator_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap)
{
	GbSeparator *alias;

	g_return_val_if_fail (GB_IS_SEPARATOR (b), NULL);
	g_return_val_if_fail (!ap || GB_IS_ALIAS_PLACEHOLDER (ap), NULL);

	alias = g_object_new (GB_TYPE_SEPARATOR, NULL);

	gb_bookmark_alias_create_impl_do (b, ap, GB_BOOKMARK (alias));

	return (GbBookmark *) alias;
}

static GbBookmark *
gb_separator_copy_impl (GbBookmark *b)
{
	GbBookmark *ret = g_object_new (GB_TYPE_SEPARATOR, NULL);
	gb_bookmark_copy_impl_do (b, ret);
	return ret;
}

static GbTbWidget *
gb_separator_create_toolbar_widget_impl (GbBookmark *b)
{
	g_return_val_if_fail (GB_IS_SEPARATOR (b), NULL);
	return gb_create_toolbar_widget_separator (GB_SEPARATOR (b));
}

/**
 * AutoFolder object
 */

MAKE_GET_TYPE (gb_auto_folder, "GbAutoFolderBookmark", GbAutoFolder, gb_auto_folder_class_init, 
	       gb_auto_folder_init, GB_TYPE_FOLDER);

static void
gb_auto_folder_class_init (GbAutoFolderClass *klass)
{
	klass->parent_class.parent_class.gb_bookmark_copy = gb_auto_folder_copy_impl;
	klass->parent_class.parent_class.gb_bookmark_alias_create = gb_auto_folder_alias_create_impl;

	gb_auto_folder_class = klass;
}

static void 
gb_auto_folder_init (GbAutoFolder *b)
{
}

GbAutoFolder *
gb_auto_folder_new (GbBookmarkSet *set, const gchar *name)
{
	GbAutoFolder *b;

	b = g_object_new (GB_TYPE_AUTO_FOLDER, NULL);
	gb_bookmark_set_name ((GbBookmark *) b, name);
	gb_bookmark_set_set ((GbBookmark *) b, set);
	return b;
}

static GbBookmark *
gb_auto_folder_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap)
{
	GbAutoFolder *alias;

	g_return_val_if_fail (GB_IS_AUTO_FOLDER (b), NULL);
	g_return_val_if_fail (!ap || GB_IS_ALIAS_PLACEHOLDER (ap), NULL);

	alias = g_object_new (GB_TYPE_AUTO_FOLDER, NULL);

	gb_bookmark_alias_create_impl_do (b, ap, GB_BOOKMARK (alias));
	gb_folder_alias_create_impl_do (b, ap, GB_FOLDER (alias));

	return (GbBookmark *) alias;
}

void
gb_auto_folder_refresh (void)
{
	NOT_IMPLEMENTED;
}

static GbBookmark *
gb_auto_folder_copy_impl (GbBookmark *b)
{
	GbBookmark *ret = g_object_new (GB_TYPE_AUTO_FOLDER, NULL);
	gb_bookmark_copy_impl_do (b, ret);
	gb_folder_copy_impl_do (GB_FOLDER (b), GB_FOLDER (ret));
	return ret;
}

/**
 * AliasPlaceholder object
 */

MAKE_GET_TYPE (gb_alias_placeholder, "GbAliasPlaceholder", GbAliasPlaceholder, 
	       gb_alias_placeholder_class_init, gb_alias_placeholder_init, GB_TYPE_BOOKMARK);

GbAliasPlaceholder *
gb_alias_placeholder_new (GbBookmarkSet *set, const char *alias_of_id)
{
	GbAliasPlaceholder *b;
	b = g_object_new (GB_TYPE_ALIAS_PLACEHOLDER, NULL);
	gb_bookmark_set_set (GB_BOOKMARK (b), set);
	b->alias_of_id = g_strdup (alias_of_id);
	return b;
}

static void
gb_alias_placeholder_class_init (GbAliasPlaceholderClass *klass)
{
	klass->parent_class.gb_bookmark_copy = gb_alias_placeholder_copy_impl;
	klass->parent_class.gb_bookmark_set_set = gb_alias_placeholder_set_set_impl;
	klass->parent_class.gb_bookmark_alias_create = gb_alias_placeholder_alias_create_impl;
	klass->parent_class.gb_bookmark_create_toolbar_widget = gb_alias_placeholder_create_toolbar_widget_impl;
	G_OBJECT_CLASS (klass)->finalize = gb_alias_placeholder_finalize_impl;

	gb_alias_placeholder_class = klass;
}

GbBookmark *
gb_alias_placeholder_copy_impl (GbBookmark *b)
{
	GbBookmark *ret = g_object_new (GB_TYPE_ALIAS_PLACEHOLDER, NULL);
	gb_bookmark_copy_impl_do (b, ret);
	gb_alias_placeholder_copy_impl_do (GB_ALIAS_PLACEHOLDER (b),
					   GB_ALIAS_PLACEHOLDER (ret));
	return ret;
}

static void
gb_alias_placeholder_copy_impl_do (GbAliasPlaceholder *b, GbAliasPlaceholder *c)
{
	c->alias_of_id = g_strdup (b->alias_of_id);
	c->create_toolbar = b->create_toolbar;
}

static GbTbWidget *
gb_alias_placeholder_create_toolbar_widget_impl (GbBookmark *b)
{
	g_return_val_if_fail (GB_IS_ALIAS_PLACEHOLDER (b), NULL);
	return gb_create_toolbar_widget_alias_placeholder (GB_ALIAS_PLACEHOLDER (b));
}

static void
gb_alias_placeholder_set_set_impl (GbBookmark *b, GbBookmarkSet *set)
{
	GbAliasPlaceholder *ap = (GbAliasPlaceholder *) b;
	GbBookmarkSet *oldset;

	g_return_if_fail (GB_IS_ALIAS_PLACEHOLDER (ap));
	g_return_if_fail (!set || GB_IS_BOOKMARK_SET (set));

	oldset = b->set;
	if (oldset)
	{
		oldset->unresolved_aliases = g_slist_remove (oldset->unresolved_aliases, b);
	}

	gb_bookmark_set_set_impl ((GbBookmark *) b, set);

	if (set)
	{
		set->unresolved_aliases = g_slist_prepend (set->unresolved_aliases, b);
	}
}

static void
gb_alias_placeholder_finalize_impl (GObject *o)
{
	GbBookmark *b = (GbBookmark *) o;
	
	if (b->set)
	{
		b->set->unresolved_aliases = g_slist_remove (b->set->unresolved_aliases, b);
	}

	gb_bookmark_finalize_impl (o);	
}

static void 
gb_alias_placeholder_init (GbAliasPlaceholder *b)
{
}

static GbBookmark *
gb_alias_placeholder_alias_create_impl (GbBookmark *b, GbAliasPlaceholder *ap)
{
	GbSite *alias;

	g_return_val_if_fail (GB_IS_ALIAS_PLACEHOLDER (b), NULL);
	g_return_val_if_fail (!ap || GB_IS_ALIAS_PLACEHOLDER (ap), NULL);

	alias = g_object_new (GB_TYPE_ALIAS_PLACEHOLDER, NULL);

	gb_bookmark_alias_create_impl_do (b, ap, GB_BOOKMARK (alias));

	return (GbBookmark *) alias;
}

gboolean
gb_alias_placeholder_resolve (GbAliasPlaceholder *ap)
{
	GbBookmark *real;
	GbBookmark *alias;
	GbBookmarkSet *set;
	gint pos;

	g_return_val_if_fail (GB_IS_ALIAS_PLACEHOLDER (ap), FALSE);
	set = GB_BOOKMARK (ap)->set;
	g_return_val_if_fail (GB_IS_BOOKMARK_SET (set), FALSE);
	g_return_val_if_fail (GB_BOOKMARK (ap)->parent, FALSE);

	if (ap->alias_of_id)
	{
		real = g_hash_table_lookup (set->id_to_bookmark, ap->alias_of_id);
		if (!real) return FALSE;
	}
	else
	{
		return FALSE;
	}

	alias = gb_bookmark_alias_create (real, ap);
	if (!GB_IS_BOOKMARK (alias)) return FALSE;

	pos = gb_folder_get_child_index (((GbBookmark *) ap)->parent, (GbBookmark *) ap);
	gb_folder_add_child (((GbBookmark *) ap)->parent, alias, pos);
	g_object_unref (alias);
	
	gb_bookmark_set_xbel_node (alias, GB_BOOKMARK (ap)->xbel_node);
	gb_bookmark_set_xbel_node (GB_BOOKMARK (ap), NULL);

	gb_bookmark_unparent ((GbBookmark *) ap);
	set->unresolved_aliases = g_slist_remove (set->unresolved_aliases, ap);

	return TRUE;
}

void
gb_alias_placeholder_set_create_toolbar (GbAliasPlaceholder *ap, gboolean val)
{
	ap->create_toolbar = !!val;

	g_signal_emit (ap, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
	gb_folder_emit_child_modified (((GbBookmark *) ap)->parent, (GbBookmark *) ap);

	gb_bookmark_set_needs_saving (ap);
}

/**
 * BookmarkSet object
 */
MAKE_GET_TYPE_IFACE (gb_bookmark_set, "GbBookmarkSet", GbBookmarkSet,
		     gb_bookmark_set_class_init, gb_bookmark_set_init, G_TYPE_OBJECT,
		     gb_bookmark_set_autocompletion_source_init, GALEON_TYPE_AUTOCOMPLETION_SOURCE);

static void
gb_bookmark_set_class_init (GbBookmarkSetClass *klass)
{
	G_OBJECT_CLASS (klass)->finalize = gb_bookmark_set_finalize_impl;
	G_OBJECT_CLASS (klass)->dispose = gb_bookmark_set_dispose_impl;

	GbBookmarkSetSignals[GB_BOOKMARK_SET_TOOLBAR] = g_signal_new (
		"toolbar", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbBookmarkSetClass, gb_bookmark_set_toolbar), 
		NULL, NULL, 
		galeon_marshal_VOID__OBJECT,
		G_TYPE_NONE, 1, GB_TYPE_FOLDER);

	GbBookmarkSetSignals[GB_BOOKMARK_SET_CONTEXT_MENU] = g_signal_new (
		"context-menu", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbBookmarkSetClass, gb_bookmark_set_context_menu), 
		NULL, NULL, 
		galeon_marshal_VOID__VOID,
		G_TYPE_NONE, 0);

	gb_bookmark_set_class = klass;
}

static void
gb_bookmark_set_autocompletion_source_init (GaleonAutocompletionSourceIface *iface)
{
	iface->foreach = gb_bookmark_set_autocompletion_source_foreach;
	iface->set_basic_key = gb_bookmark_set_autocompletion_source_set_basic_key;
}

static void
gb_bookmark_set_init (GbBookmarkSet *b)
{
	b->root = NULL;
	b->file_format_version = GB_FILE_FORMAT_VERSION_GALEON_2;
	b->filename = NULL;
	b->io = NULL;
	b->xbel_doc = NULL;
	b->needs_saving = FALSE;
	b->default_folder = b->root;
	b->id_to_bookmark = g_hash_table_new (g_str_hash, g_str_equal);
	b->url_to_bookmark = g_hash_table_new (g_str_hash, g_str_equal);
#ifdef NICK_HASHTABLE
	b->nick_to_bookmark = g_hash_table_new (g_str_hash, g_str_equal);
#endif
/*
	b->undo_queue = NULL;
	b->redo_queue  = NULL;
*/
	b->unresolved_aliases = NULL;
	b->toolbars = NULL;
	b->autosave_timeout_id = 0;
	b->max_smart_site_history_items = 15;	
}

static void
gb_bookmark_set_dispose_impl (GObject *o)
{
	GbBookmarkSet *set = GB_BOOKMARK_SET (o);

#ifdef DEBUG_REF
	g_print ("disposing the bookmarkset\n");
#endif

	LOG ("in gb_bookmark_set_dispose_impl");

	gb_bookmark_set_set (GB_BOOKMARK (set->root), NULL);	

	G_OBJECT_CLASS (g_object_class)->dispose (o);
}

static void
gb_bookmark_set_finalize_impl (GObject *o)
{
	GbBookmarkSet *set = GB_BOOKMARK_SET (o);

#ifdef DEBUG_REF
	g_print ("finalizing the bookmarkset\n");
#endif

	LOG ("in gb_bookmark_set_finalize_impl");

	gb_bookmark_set_set_xbel_doc (set, NULL);
	
	if (set->autosave_timeout_id != 0)
	{
		g_source_remove (set->autosave_timeout_id);
	}

	if (set->io)
	{
		g_object_unref (G_OBJECT (set->io));
	}
	
	if (set->default_folder)
	{
		g_object_unref (G_OBJECT (set->default_folder));
	}

	g_object_unref (G_OBJECT (set->root));

	/* if everything is 0, then things work quite well */
	LOG ("%d entries in url_to_bookmark", g_hash_table_size (set->url_to_bookmark));
	LOG ("%d entries in id_to_bookmark", g_hash_table_size (set->id_to_bookmark));

	g_hash_table_destroy (set->url_to_bookmark);
#ifdef NICK_HASHTABLE
	g_hash_table_destroy (set->nick_to_bookmark);
#endif
	g_hash_table_destroy (set->id_to_bookmark);

	g_free (set->filename);

	/* if everything is 0, then things work quite well */
	LOG ("%d unresolved_aliases", g_slist_length (set->unresolved_aliases));
	LOG ("%d toolbars\n", g_slist_length (set->toolbars));

	g_slist_free (set->unresolved_aliases);
	g_slist_free (set->toolbars);
	g_slist_free (set->all_bookmarks_noalias);
	g_slist_free (set->all_bookmarks);

	G_OBJECT_CLASS (g_object_class)->finalize (o);
}

GbBookmarkSet *
gb_bookmark_set_new (void)
{
	return g_object_new (GB_TYPE_BOOKMARK_SET, NULL);
}

void
gb_bookmark_set_set_root (GbBookmarkSet *set, GbFolder *new_root)
{
	GbFolder *old = set->root;

	g_return_if_fail (GB_IS_FOLDER (new_root));
	g_return_if_fail (((GbBookmark *) new_root)->alias_of == NULL);
	set->root = new_root;

	if (new_root == old)
	{
		return;
	}

	if (GB_BOOKMARK (new_root)->set != set)
	{
		gb_bookmark_set_set (GB_BOOKMARK (new_root), set);
	}

	g_object_ref (G_OBJECT (new_root));

	if (old)
	{
		g_object_unref (old);
	}
}

void
gb_bookmark_set_set_default_folder (GbBookmarkSet *set, GbFolder *default_folder)
{
	GbFolder *old;
	GbFolder *niu;
	GbBookmark *b;
	
	g_return_if_fail (GB_IS_BOOKMARK_SET (set));
	g_return_if_fail (GB_IS_FOLDER (default_folder));
	g_return_if_fail (GB_IS_FOLDER (set->root));

	old = set->default_folder;
	niu = GB_FOLDER (gb_bookmark_real_bookmark (GB_BOOKMARK (default_folder)));

	g_assert (!old || !gb_bookmark_is_alias (old));
	g_assert (!gb_bookmark_is_alias (niu));

	set->default_folder = niu;
	g_object_ref (niu);

       	if (old)
	{
		b = GB_BOOKMARK (old);
		do {
			g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
			gb_folder_emit_child_modified (b->parent, b);
		} while ((b = b->alias) != NULL);
		g_object_unref (old);
	}
		
	b = GB_BOOKMARK (niu);
       	do {
		g_signal_emit (b, GbBookmarkSignals[GB_BOOKMARK_MODIFIED], 0);
		gb_folder_emit_child_modified (b->parent, b);
	} while ((b = b->alias) != NULL);

	gb_bookmark_set_needs_saving (niu);
}

void
gb_bookmark_set_resolve_aliases (GbBookmarkSet *set)
{
	GSList *l = g_slist_copy (set->unresolved_aliases);
	GSList *li;

	gint debug_len_before = g_slist_length (l);
	gint debug_len_after;

	for (li = l; li; li = li->next)
	{
		gb_alias_placeholder_resolve (li->data);
	}

	debug_len_after = g_slist_length (set->unresolved_aliases);
	if (debug_len_after > 0)
	{
		g_warning ("%d aliases were resolved, out of %d", 
			   debug_len_before - debug_len_after, debug_len_before);
	}

	if (l) 
	{
		set->needs_saving = TRUE;
	}
	g_slist_free (l);
}

void
gb_bookmark_set_set_filename (GbBookmarkSet *set, const gchar *filename)
{
	g_free (set->filename);
	set->filename = g_strdup (filename);
	set->needs_saving = TRUE;
}

GSList *
gb_bookmark_set_get_toolbars (GbBookmarkSet *set)
{
	return g_slist_copy (set->toolbars);
}

void
gb_bookmark_set_set_io (GbBookmarkSet *set, GbIO *io)
{
	g_return_if_fail (GB_IS_IO (io));
	g_return_if_fail (GB_IS_BOOKMARK_SET (set));

	if (set->io)
	{
		g_object_unref (G_OBJECT (set->io));
	}
	if (io)
	{
		g_object_ref (G_OBJECT (io));
	}
	set->io = io;
}

void
gb_bookmark_set_discard_xml_nodes (GbBookmarkSet *set)
{
	GbIterator *i = gb_iterator_new (set);
	while (gb_iterator_has_next (i))
	{
		GbBookmark *b = gb_iterator_next (i);
		gb_bookmark_set_xbel_node (b, NULL);
	}
	g_object_unref (i);
}

void
gb_bookmark_set_set_xbel_doc (GbBookmarkSet *set, xmlDocPtr doc)
{
	g_return_if_fail (GB_IS_BOOKMARK_SET (set));

	if (set->xbel_doc && set->xbel_doc != doc)
	{
		gb_bookmark_set_discard_xml_nodes (set);
		xmlFreeDoc (set->xbel_doc);
	}

	set->xbel_doc = doc;
}

gboolean
gb_bookmark_set_check_save (GbBookmarkSet *set)
{
	if (set->needs_saving)
	{
		if (set->filename && set->io)
		{
			gchar *tmpfilename = g_strconcat (set->filename, ".tmp", NULL);
			gboolean ok = gb_io_save_to_file (set->io, set, tmpfilename);
			if (ok)
			{
				gul_backup_file (set->filename, BOOKMARKS_NUM_BACKUPS, tmpfilename);
				set->needs_saving = FALSE;
			}
			g_free (tmpfilename);
			return ok;
		}
		else
		{
			g_warning ("Not saving the bookmarks because either the filename or the IO has not been set.");
			return FALSE;
		}
	}
	else
	{
		return TRUE;
	}
}

void
gb_bookmark_set_set_needs_saving (GbBookmarkSet *set, gboolean needs_saving)
{
	set->needs_saving = needs_saving;
}

void
gb_bookmark_set_set_auto_save (GbBookmarkSet *set, guint interval)
{
	if (set->autosave_timeout_id != 0)
	{
		g_source_remove (set->autosave_timeout_id);
	}

	set->autosave_timeout_id = g_timeout_add (interval, 
						  gb_bookmark_set_set_auto_save_cb, set);
}

static gboolean
gb_bookmark_set_set_auto_save_cb (gpointer data)
{
	GbBookmarkSet *set = data;
	gb_bookmark_set_check_save (set);
	return TRUE;
}

void
gb_bookmark_set_add_default (GbBookmarkSet *set, GbBookmark *b)
{
	GbFolder *p;
	
	g_return_if_fail (GB_IS_BOOKMARK_SET (set));
	g_return_if_fail (GB_IS_BOOKMARK (b));
	
	p = set->default_folder ? set->default_folder : set->root;

	g_return_if_fail (GB_IS_FOLDER (p));

	gb_folder_add_child (p, b, -1);
}

static char *
gb_bookmark_set_fix_galeon1_mess_string (const gchar *s)
{
	char *sl = g_convert (s, -1, "ISO-8859-1", "UTF-8", NULL, NULL, NULL);
	if (sl) 
	{
		/* check if the result is valid */
		char *sll = g_locale_from_utf8 (sl, -1, NULL, NULL, NULL);
		if (sll)
		{
			g_free (sll);
			return sl;
		}
		else 
		{
			g_free (sl);
			return g_strdup (s);
		}
	}
	else
	{
		return g_strdup (s);
	}
}

static void
gb_bookmark_set_fix_galeon1_mess_item (GbBookmark *b)
{
	gchar *s;
	if (gb_bookmark_is_alias (b))
	{
		return;
	}

	s = gb_bookmark_set_fix_galeon1_mess_string (b->name);
	gb_bookmark_set_name (b, s);
	g_free (s);

	s = gb_bookmark_set_fix_galeon1_mess_string (b->notes);
	gb_bookmark_set_notes (b, s);
	g_free (s);
	
	if (GB_IS_FOLDER (b))
	{
		gb_bookmark_set_fix_galeon1_mess_recursive (GB_FOLDER (b));
	}
}

static void
gb_bookmark_set_fix_galeon1_mess_recursive (GbFolder *f)
{
	GbBookmark *bi;
	if (gb_bookmark_is_alias (f))
	{
		return;
	}
	for (bi = f->child; bi; bi = bi->next)
	{
		gb_bookmark_set_fix_galeon1_mess_item (bi);
	}
}

void
gb_bookmark_set_fix_galeon1_mess (GbBookmarkSet *set)
{
	gb_bookmark_set_fix_galeon1_mess_item (GB_BOOKMARK (set->root));
}

GbBookmark *
gb_bookmark_set_get_bookmark_by_url (GbBookmarkSet *set, const gchar *url)
{
	return g_hash_table_lookup (set->url_to_bookmark, url);
}

static GbBookmark *
gb_bookmark_set_get_bookmark_by_nick_rec (GbFolder *root, 
					  const gchar *nick)
{
	GbBookmark *bi;

	for (bi = root->child; bi; bi = bi->next)
	{
		if (GB_IS_FOLDER (bi) && !gb_bookmark_is_alias (bi))
		{
			GbBookmark *bj = gb_bookmark_set_get_bookmark_by_nick_rec (GB_FOLDER (bi), nick);
			if (bj) 
			{
				return bj;
			}
		}

		if (bi->nick && bi->nick[0] && strstr (bi->nick, nick))
		{
			/* possible match */
			gchar **nicks = gul_strsplit_multiple_delimiters_with_quotes 
				(bi->nick, " ,;", -1, "\"\'");
			gint i;
			gboolean matched = FALSE;
			for (i = 0; nicks[i] && !matched; ++i)
			{
				matched = !strcmp (nicks[i], nick);
			}
			g_strfreev (nicks);

			if (matched)
			{
				return bi;
			}
		}
	}
	
	return NULL;
}

GbBookmark *
gb_bookmark_set_get_bookmark_by_nick (GbBookmarkSet *set, 
				      const gchar *nick)
{
#ifdef NICK_HASHTABLE
	return g_hash_table_lookup (set->nick_to_bookmark, nick);
#else
	return gb_bookmark_set_get_bookmark_by_nick_rec (set->root, nick);
#endif
}

gchar *
gb_bookmark_set_get_url_by_nick_and_args (GbBookmarkSet *set, 
					  const gchar *nick_and_args)
{
	GbBookmark *b;
	gchar **tokens;
	const gchar *nick;
	gchar *ret;

	tokens = gul_strsplit_with_quotes (nick_and_args, " ", -1, "\"\'");
	nick = tokens[0];

	if (!nick)
	{
		return NULL;
	}

	b = gb_bookmark_set_get_bookmark_by_nick (set, nick);

	if (!(b && GB_IS_SITE (b)))
	{
		g_strfreev (tokens);
		return NULL;
	}
	
	if (GB_IS_SMART_SITE (b) && tokens[1])
	{
		ret = gb_smart_site_subst_args (GB_SMART_SITE (b), tokens + 1);
	}
	else
	{ 
		ret = g_strdup (GB_SITE (b)->url);
	}

	g_strfreev (tokens);

	return ret;
}

static void
gb_bookmark_set_autocompletion_source_foreach_rec (GaleonAutocompletionSource *source,
						   GbFolder *f,
						   GaleonAutocompletionSourceForeachFunc func,
						   gpointer data)
{
	GbBookmark *bi;
	for (bi = f->child; bi; bi = bi->next)
	{
		if (!gb_bookmark_is_alias (bi))
		{
			if (GB_IS_SITE (bi)) 
			{
				/* very simple scoring function. Most recent entries get higher scores.
				   Adds 100<<17 to the item because it's bookmarked... */

				func (source, ((GbSite *) bi)->url, bi->name, 
				      ((GbSite *) bi)->time_visited + (100 << 17), data);
			}
			else if (GB_IS_FOLDER (bi))
			{
				gb_bookmark_set_autocompletion_source_foreach_rec 
					(source, (GbFolder *) bi, func, data);
			}
		}
	}
}

static void
gb_bookmark_set_autocompletion_source_set_basic_key (GaleonAutocompletionSource *source,
						     const gchar *basic_key)
{
	/* nothing to do here */
}

static void
gb_bookmark_set_autocompletion_source_foreach (GaleonAutocompletionSource *source,
					       const gchar *current_text,
					       GaleonAutocompletionSourceForeachFunc func,
					       gpointer data)
{
	GbBookmarkSet *set = GB_BOOKMARK_SET (source);
	gb_bookmark_set_autocompletion_source_foreach_rec (source, set->root, func, data);
}

static void
gb_bookmark_set_emit_autocompletion_source_data_changed (GbBookmarkSet *gh)
{
	g_signal_emit_by_name (gh, "data-changed");
}

static GSList *
gb_folder_prepend_preorder_noalias (GbFolder *f, GSList *l)
{
	GbBookmark *c;
	for (c = f->child; c; c = c->next)
	{
		if (!gb_bookmark_is_alias (c))
		{
			l = g_slist_prepend (l, c);
			if (GB_IS_FOLDER (c))
			{
				l = gb_folder_prepend_preorder_noalias (GB_FOLDER (c), l);
			}
		}
	}
	return l;
}

static GSList *
gb_folder_prepend_preorder (GbFolder *f, GSList *l)
{
	GbBookmark *c;
	for (c = f->child; c; c = c->next)
	{
		l = g_slist_prepend (l, c);
		if (!gb_bookmark_is_alias (c) &&GB_IS_FOLDER (c))
		{
			l = gb_folder_prepend_preorder (GB_FOLDER (c), l);
		}
	}
	return l;
}

const GSList *
gb_bookmark_set_get_all (GbBookmarkSet *set)
{
	if (!set->all_bookmarks)
	{
		set->all_bookmarks = g_slist_reverse 
			(gb_folder_prepend_preorder (set->root, 
						     g_slist_prepend (NULL, set->root)));
	}
	return set->all_bookmarks;
}

const GSList *
gb_bookmark_set_get_all_noalias (GbBookmarkSet *set)
{
	if (!set->all_bookmarks_noalias)
	{
		set->all_bookmarks_noalias = g_slist_reverse 
			(gb_folder_prepend_preorder_noalias (set->root, 
							     g_slist_prepend (NULL, set->root)));
	}
	return set->all_bookmarks_noalias;
}

const GSList *
gb_bookmark_set_get_context_bookmarks (GbBookmarkSet *set)
{
	if (!set->context_bookmarks)
	{
		const GSList *all = gb_bookmark_set_get_all (set);
		const GSList *li;
		GSList *t = NULL;
		
		for (li = all; li; li = li->next)
		{
			GbBookmark *bi = li->data;
			if (bi->add_to_context_menu)
			{
				t = g_slist_prepend (t, bi);
			}
		}
		
		set->context_bookmarks = g_slist_reverse (t);
	}
	return set->context_bookmarks;
}

static void
gb_bookmark_set_tree_changed (GbBookmarkSet *set)
{
	if (set->all_bookmarks_noalias)
	{
		g_slist_free (set->all_bookmarks_noalias);
		set->all_bookmarks_noalias = NULL;
	}
	if (set->all_bookmarks)
	{
		g_slist_free (set->all_bookmarks);
		set->all_bookmarks = NULL;
	}
	if (set->context_bookmarks)
	{
		g_slist_free (set->context_bookmarks);
		set->context_bookmarks = NULL;
	}
}
