/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2003 Takuro Ashie
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 *  $Id: kz-xbel.c 656 2004-01-27 13:43:01Z makeinu $
 */

#include "kazehakase.h"
#include "kz-xbel.h"
#include "kz-xml.h"

#include <string.h>
#include <stdlib.h>

static gboolean kz_xbel_is_supported       (KzBookmark  *bookmark,
					    const gchar *buf);
static void     kz_xbel_init               (KzBookmark  *bookmark);
static gboolean kz_xbel_from_string        (KzBookmark  *bookmark,
					    const gchar *buffer,
					    guint        length,
					    GError     **error);
static gchar   *kz_xbel_to_string          (KzBookmark  *bookmark);

static void     kz_xbel_notify             (GObject     *object,
					    GParamSpec  *pspec,
					    KzXML       *xml);

static void     kz_xbel_build_tree         (KzBookmark  *bookmark);
static void     kz_xbel_insert_xml_node    (KzBookmark  *bookmark,
					    KzBookmark  *parent,
					    KzBookmark  *sibling);
static void     kz_xbel_remove_xml_node    (KzBookmark  *bookmark);
static void     kz_xbel_connect_signals    (KzBookmark  *bookmark);
static void     kz_xbel_disconnect_signals (KzBookmark  *bookmark);
static void     cb_bookmark_insert_child   (KzBookmark  *bookmark,
					    KzBookmark  *child,
					    KzBookmark  *sibling);
static void     cb_bookmark_remove_child   (KzBookmark  *bookmark,
					    KzBookmark  *child);
static void     cb_bookmark_notify         (GObject     *object,
					    GParamSpec  *spec);

static void     xml_node_set_title         (KzXMLNode   *parent,
					    const gchar *title);


static GQuark xml_quark = 0;
static GQuark node_quark = 0;
static GQuark building_quark = 0;


#define BOOKMARK_SET_BUILDING(b) \
	g_object_set_qdata(G_OBJECT(b), building_quark, GINT_TO_POINTER(TRUE))
#define BOOKMARK_UNSET_BUILDING(b) \
	g_object_set_qdata(G_OBJECT(b), building_quark, GINT_TO_POINTER(FALSE))
#define BOOKMARK_IS_BUILDING(b) \
	GPOINTER_TO_INT(g_object_get_qdata(G_OBJECT(b), building_quark))


static KzBookmarkFileType xbel_file_type =
{
	priority_hint: 0,
	file_type:     "XBEL",
	init:          kz_xbel_init,
	is_supported:  kz_xbel_is_supported,
	from_string:   kz_xbel_from_string,
	to_string:     kz_xbel_to_string,
};


KzBookmarkFileType *
kz_xbel_get_file_types (gint idx)
{
	if (idx == 0)
		return &xbel_file_type;
	else
		return NULL;
}


static gboolean
kz_xbel_is_supported(KzBookmark *bookmark, const gchar *buf)
{
	const gchar *pos;

	g_return_val_if_fail(buf, FALSE);

	if (strncmp(buf, "<?xml", 5)) return FALSE;

	pos = buf;

	/* find first element */
	do
		pos = strchr(pos + 1, '<');
	while (pos && pos[1] == '!');

	if (pos && !strncmp(pos, "<xbel", 5))
		return TRUE;

	return FALSE;
}


static void
kz_xbel_init (KzBookmark *bookmark)
{
	KzXML *xml;

	if (!xml_quark)
		xml_quark = g_quark_from_string("KzXBEL::KzXML");
	if (!node_quark)
		node_quark = g_quark_from_string("KzXBEL::XMLNode");
	if (!building_quark)
		building_quark = g_quark_from_string("KzXBEL::Building");

	xml = kz_xml_new();
	g_object_set_qdata_full(G_OBJECT(bookmark), xml_quark,
				xml, (GDestroyNotify) g_object_unref);

	bookmark->flags |= KZ_BOOKMARK_FILE_FLAG;
	g_object_set(G_OBJECT(bookmark),
		     "type", KZ_BOOKMARK_PURE_FOLDER,
		     NULL);

	g_signal_connect(G_OBJECT(bookmark), "notify",
			 G_CALLBACK(kz_xbel_notify), xml);
	g_signal_connect(G_OBJECT(bookmark), "insert-child",
			 G_CALLBACK(cb_bookmark_insert_child), xml);
	g_signal_connect(G_OBJECT(bookmark), "remove-child",
			 G_CALLBACK(cb_bookmark_remove_child), xml);
}


static gboolean
kz_xbel_from_string (KzBookmark *bookmark,
		     const gchar *buffer, guint length,
		     GError **error)
{
	KzXML *xml;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	xml = g_object_get_qdata(G_OBJECT(bookmark), xml_quark);
	g_return_val_if_fail(KZ_IS_XML(xml), FALSE);

	if (!kz_xml_load_xml(xml, buffer, length) ||
	    !kz_xml_get_root_element(xml))
	{
		KzXMLNode *node, *title_node, *title, *space;
		const gchar *bookmark_title;

		/*
		 *  <xbel version=1.0 folded=none>\n (space1)
		 *  <title>Bookmarks</title>\n       (space2)
		 *  </xbel>
		 */
		node = kz_xml_element_node_new("xbel");
		kz_xml_node_set_attr(node, "version", "1.0");
		kz_xml_node_set_attr(node, "folded", "none");	
		/* kz_xml_node_set_attr(node, "id", "????????"); */
		kz_xml_node_append_child(xml->root, node);

		/* space1 */
		space = kz_xml_text_node_new("\n");
		kz_xml_node_append_child(node, space);

		{
			title_node = kz_xml_element_node_new("title");
			kz_xml_node_append_child(node, title_node);

			bookmark_title = kz_bookmark_get_title(bookmark);
			if(bookmark_title)
				title = kz_xml_text_node_new(bookmark_title);
			else
				title = kz_xml_text_node_new("Bookmarks");
			kz_xml_node_append_child(title_node, title);
		}

		/* space2 */
		space = kz_xml_text_node_new("\n");
		kz_xml_node_append_child(node, space);
	}

	kz_xbel_build_tree(bookmark);

	return TRUE;
}


static gchar *
kz_xbel_to_string (KzBookmark *bookmark)
{
	KzXML *xml;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	xml = g_object_get_qdata(G_OBJECT(bookmark), xml_quark);
	g_return_val_if_fail(KZ_IS_XML(xml), NULL);

	kz_xml_node_arrange_indent(xml->root, 0);

	return kz_xml_node_to_xml(xml->root);
}


static void
kz_xbel_notify (GObject *object, GParamSpec *pspec, KzXML *xml)
{
	KzBookmark *bookmark;
	KzXMLNode *node;
	const gchar *prop;
        GValue value = { 0 };

	g_return_if_fail(KZ_IS_BOOKMARK(object));
	g_return_if_fail(KZ_IS_XML(xml));

	bookmark = KZ_BOOKMARK(object);

	if (BOOKMARK_IS_BUILDING(bookmark)) return;

	node = kz_xml_get_root_element(xml);
	if (!node) return;
	g_return_if_fail(kz_xml_node_name_is(node, "xbel"));

	prop = g_param_spec_get_name(pspec);
	g_return_if_fail(prop);

        g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(pspec));
        g_object_get_property(object, prop, &value);

	/*
	 * FIXME! We should use hash table.
	 */
	if (!strcmp(prop, "title"))
	{
		gchar *title = g_value_dup_string(&value);

		xml_node_set_title(node, title);
		g_free(title);
	}
#if 0
	else if (!strcmp(prop, "link"))
	{
		gchar *uri = g_value_dup_string(&value);

		kz_xml_node_set_attr(node, "href", uri);
		g_free(uri);
	}
	else if (!strcmp(prop, "description"))
	{
		gchar *desc = g_value_dup_string(&value);

		xml_node_set_description(node, desc);
		g_free(desc);
	}
#endif
}


static void
parse_metadata_node (KzBookmark *bookmark, KzXMLNode *parent)
{
	KzXMLNode *node;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	if (!kz_xml_node_is_element(parent)) return;

	for (node = kz_xml_node_first_child(parent);
	     node;
	     node = kz_xml_node_next(node))
	{
		if (!kz_xml_node_is_element(node)) continue;

		if (kz_xml_node_name_is(node, "metadata"))
		{
			const gchar *owner;

			/* We should ignore other owners such as Galeon. */
			owner = kz_xml_node_get_attr(node, "owner");
			if (!owner || strcmp(owner, KAZEHAKASE_URI))
				continue;

			parse_metadata_node(bookmark, node);
			/*
			if (kz_bookmark_get_owner(bookmark))
			{
				g_warning("metadata owner attribute is duplicated!");
				continue;
			}
			owner = kz_xml_node_get_attr(node, "owner");
			kz_bookmark_set_owner(folder, owner);
			*/
		}
		else if (kz_xml_node_name_is(node, "location"))
		{
			/*
			 * "location" element is already parsed at
			 * parse_child_node().
			 */
			if (!kz_bookmark_is_file(bookmark))
			{
				g_warning("kzXBEL: The bookmark has location, "
					  "but it's not a folder element!");
			}
		}
		else if (kz_xml_node_name_is(node, "update_interval"))
		{
			gchar *interval_str;
			guint interval;

			if (kz_bookmark_get_interval(bookmark))
			{
				g_warning("update_interval element is duplicated!");
				continue;
			}
			interval_str = kz_xml_node_to_str(node);
			interval = atoi(interval_str);
			kz_bookmark_set_interval(bookmark, interval);
			g_free(interval_str);
		}
	}	
}


static KzXMLNode *
xml_node_get_named_node (KzXMLNode *parent, const gchar *name)
{
	KzXMLNode *node;

	g_return_val_if_fail(parent, NULL);
	g_return_val_if_fail(name && *name, NULL);

	for (node = kz_xml_node_first_child(parent);
	     node;
	     node = kz_xml_node_next(node))
	{
		if (kz_xml_node_name_is(node, name))
			return node;
	}

	return NULL;
}


static KzXMLNode *
xml_node_find_metadata_node (KzXMLNode *parent)
{
	KzXMLNode *info_node, *node;

	info_node = xml_node_get_named_node(parent, "info");
	if (!info_node) return NULL;

	for (node = kz_xml_node_first_child(info_node);
	     node;
	     node = kz_xml_node_next(node))
	{
		if (kz_xml_node_name_is(node, "metadata"))
		{
			const gchar *owner;

			owner = kz_xml_node_get_attr(node, "owner");
			if (owner && !strcmp(owner, KAZEHAKASE_URI))
				return node;
		}
	}

	return NULL;
}


static gboolean
xml_node_has_location (KzXMLNode *parent)
{
	KzXMLNode *child, *gchild;

	g_return_val_if_fail(parent, FALSE);

	if (!kz_xml_node_name_is(parent, "folder"))
		return FALSE;

	child = xml_node_find_metadata_node(parent);
	if (!child) return FALSE;

	gchild = xml_node_get_named_node(child, "location");
	if (gchild) return TRUE;

	return FALSE;
}


static gchar *
xml_node_get_location (KzXMLNode *parent)
{
	KzXMLNode *child, *gchild;

	g_return_val_if_fail(parent, FALSE);

	if (!kz_xml_node_name_is(parent, "folder"))
		return NULL;

	child = xml_node_find_metadata_node(parent);
	if (!child) return NULL;

	gchild = xml_node_get_named_node(child, "location");
	if (!gchild) return NULL;

	return kz_xml_node_to_str(gchild);
}


static void
parse_child_node (KzBookmark *bookmark, KzXMLNode *parent)
{
	KzXMLNode *node;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	if (!kz_xml_node_is_element(parent)) return;

	for (node = kz_xml_node_first_child(parent);
	     node;
	     node = kz_xml_node_next(node))
	{
		if (!kz_xml_node_is_element(node)) continue;

		if (kz_xml_node_name_is(node, "title"))
		{
			gchar *title;

			if (kz_bookmark_get_title(bookmark) &&
			    !g_object_get_qdata(G_OBJECT(bookmark), xml_quark))
			{
				g_warning("title element is duplicated!");
				continue;
			}
			title = kz_xml_node_to_str(node);
			kz_bookmark_set_title(bookmark, title);
			g_free(title);
		}
		else if (kz_xml_node_name_is(node, "desc"))
		{
			gchar *desc;

			if (kz_bookmark_get_description(bookmark))
			{
				g_warning("desc element is duplicated!");
				continue;
			}
			desc = kz_xml_node_to_str(node);
			kz_bookmark_set_description(bookmark, desc);
			g_free(desc);
		}
		else if (kz_xml_node_name_is(node, "folder"))
		{
			KzBookmark *folder = kz_bookmark_pure_folder_new();
			const gchar *folded_str;
			gboolean folded;

			if (xml_node_has_location(node))
			{
#warning FIXME! If the file does not exit, we should creat it.
				gchar *location = xml_node_get_location(node);

				folder = kz_bookmark_file_new(location,
							      NULL, NULL);
				kz_bookmark_load_in_memory_start(folder);
			}
			else
			{
				folder = kz_bookmark_pure_folder_new();
			}

			folded_str = kz_xml_node_get_attr(node, "folded");
			folded = folded_str && !strcmp(folded_str, "yes");
			kz_bookmark_set_folded(folder, folded);

			BOOKMARK_SET_BUILDING(folder);
			g_object_set_qdata(G_OBJECT(folder),
					   node_quark, node);
			kz_bookmark_append(bookmark, folder);
			parse_child_node(folder, node);
			BOOKMARK_UNSET_BUILDING(folder);

			if (kz_bookmark_is_file(folder))
			{
				kz_bookmark_remove_all(folder);
				kz_bookmark_load_in_memory_start(folder);
			}

			g_object_unref(folder);
		}
		else if (kz_xml_node_name_is(node, "bookmark"))
		{
			KzBookmark *child_bookmark = kz_bookmark_new();
			const gchar *uri = kz_xml_node_get_attr(node, "href");

			BOOKMARK_SET_BUILDING(bookmark);
			g_object_set_qdata(G_OBJECT(child_bookmark),
					   node_quark, node);
			kz_bookmark_set_link(child_bookmark, uri);
			kz_bookmark_append(bookmark, child_bookmark);
			parse_child_node(child_bookmark, node);
			BOOKMARK_UNSET_BUILDING(bookmark);
			g_object_unref(child_bookmark);
		}
		else if (kz_xml_node_name_is(node, "separator"))
		{
			KzBookmark *separator = kz_bookmark_separator_new();

			BOOKMARK_SET_BUILDING(separator);
			g_object_set_qdata(G_OBJECT(separator),
					   node_quark, node);
			kz_bookmark_append(bookmark, separator);
			BOOKMARK_UNSET_BUILDING(separator);
			g_object_unref(separator);
		}
		else if (kz_xml_node_name_is(node, "alias"))
		{
			g_warning("KzXBEL::alias element is not supported yet."
				  "Please implement me!");
		}
		else if (kz_xml_node_name_is(node, "info"))
		{
			parse_metadata_node(bookmark, node);
		}
	}
}


static void
kz_xbel_build_tree (KzBookmark *bookmark)
{
	KzXML *xml;
	KzXMLNode *node;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	xml = g_object_get_qdata(G_OBJECT(bookmark), xml_quark);
	g_return_if_fail(KZ_IS_XML(xml));

	node = kz_xml_get_root_element (xml);
	if (!node) return;
	g_return_if_fail (kz_xml_node_name_is(node, "xbel"));

	BOOKMARK_SET_BUILDING(bookmark);
	g_object_set_qdata(G_OBJECT(bookmark),
			   node_quark, node);
	parse_child_node(bookmark, node);
	BOOKMARK_UNSET_BUILDING(bookmark);
}


static void
xml_node_append_title (KzXMLNode *node, KzBookmark *bookmark)
{
	const gchar *title = kz_bookmark_get_title(bookmark);
	KzXMLNode *title_node, *text_node, *space;

	/*
	 *  <title>title</title>\n
	 */

	/* title */
	title_node = kz_xml_element_node_new("title");
	kz_xml_node_append_child(node, title_node);
	text_node = kz_xml_text_node_new(title);
	kz_xml_node_append_child(title_node, text_node);

	/* space1 */
	space = kz_xml_text_node_new("\n");
	kz_xml_node_append_child(node, space);
}


static KzXMLNode *
xml_node_append_metadata_node (KzXMLNode *node)
{
	KzXMLNode *space, *info_node, *metadata_node;

	/*
	 *  <info>\n      (space1)
	 *  <metadata>\n  (space2)
	 *  </metadata>\n (space3)
	 *  </info>\n     (space4)
	 */

	/* info */
	info_node = xml_node_get_named_node(node, "info");
	if (!info_node)
	{
		info_node = kz_xml_element_node_new("info");
		kz_xml_node_append_child(node, info_node);

		/* space1 */
		space = kz_xml_text_node_new("\n");
		kz_xml_node_append_child(info_node, space);

		/* space4 */
		space = kz_xml_text_node_new("\n");
		kz_xml_node_append_child(node, space);
	}

	/* metadata */
	metadata_node = xml_node_find_metadata_node(node);
	if (metadata_node) return metadata_node;

	metadata_node = kz_xml_element_node_new("metadata");
	kz_xml_node_set_attr(metadata_node, "owner", KAZEHAKASE_URI);
	kz_xml_node_append_child(info_node, metadata_node);

	/* space2 */
	space = kz_xml_text_node_new("\n");
	kz_xml_node_append_child(metadata_node, space);

	/* space3 */
	space = kz_xml_text_node_new("\n");
	kz_xml_node_append_child(info_node, space);

	return metadata_node;
}


static void
xml_node_set_location (KzXMLNode *node, KzBookmark *bookmark)
{
	const gchar *location;
	KzXMLNode *metadata_node, *location_node,
		  *text_node, *space;

	location = kz_bookmark_get_location(bookmark);
	g_return_if_fail(location);

	metadata_node = xml_node_append_metadata_node(node);
	location_node = xml_node_get_named_node(metadata_node, "location");

	if (location_node)
	{
		KzXMLNode *child;

		/* remove all child node */
		child = kz_xml_node_first_child(location_node);
		while (child)
		{
			KzXMLNode *next = kz_xml_node_next(child);
			kz_xml_node_remove_child(location_node, child);
			kz_xml_node_unref(child);
			child = next;
		}
	}
	else
	{
		location_node = kz_xml_element_node_new("location");
		kz_xml_node_append_child(metadata_node, location_node);
	}

	text_node = kz_xml_text_node_new(location);
	kz_xml_node_append_child(location_node, text_node);

	space = kz_xml_text_node_new("\n");
	kz_xml_node_append_child(metadata_node, space);
}


static void
xml_node_set_interval (KzXMLNode *node, KzBookmark *bookmark)
{
	gchar *interval;
	KzXMLNode *metadata_node, *interval_node,
		  *text_node, *space;

	interval = g_strdup_printf("%d", kz_bookmark_get_interval(bookmark));

	metadata_node = xml_node_append_metadata_node(node);
	interval_node = xml_node_get_named_node(metadata_node, "update_interval");

	/* update_interval */
	if (interval_node)
	{
		KzXMLNode *child;

		/* remove all child node */
		child = kz_xml_node_first_child(interval_node);
		while (child)
		{
			KzXMLNode *next = kz_xml_node_next(child);
			kz_xml_node_remove_child(interval_node, child);
			kz_xml_node_unref(child);
			child = next;
		}
	}
	else
	{
		interval_node = kz_xml_element_node_new("update_interval");
		kz_xml_node_append_child(metadata_node, interval_node);
	}

	text_node = kz_xml_text_node_new(interval);
	kz_xml_node_append_child(interval_node, text_node);

	space = kz_xml_text_node_new("\n");
	kz_xml_node_append_child(metadata_node, space);

	g_free(interval);
}


static KzXMLNode *
create_xml_node (KzBookmark *bookmark)
{
	KzXMLNode *node = NULL, *space;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	if (kz_bookmark_is_separator(bookmark))
	{
		node = kz_xml_element_node_new("separator");
	}
	else if (kz_bookmark_is_file(bookmark))
	{
		const gchar *location = kz_bookmark_get_location(bookmark);
		guint interval        = kz_bookmark_get_interval(bookmark);

		if (!location || !*location)
		{
			/* warning? */
		}

		/*
		 * <folder>\n
		 * <title/>
		 * ...
		 * </folder>
		 */

		node = kz_xml_element_node_new("folder");
		xml_node_append_title (node, bookmark);

		space = kz_xml_text_node_new("\n");
		kz_xml_node_append_child(node, space);

		if (location)
			xml_node_set_location(node, bookmark);
		if (interval)
			xml_node_set_interval(node, bookmark);
	}
	else if (kz_bookmark_is_folder(bookmark))
	{
		/*
		 * <folder>\n
		 * <title/>
		 * ...
		 * </folder>
		 */

		node = kz_xml_element_node_new("folder");

		space = kz_xml_text_node_new("\n");
		kz_xml_node_append_child(node, space);

		xml_node_append_title (node, bookmark);
	}
	else
	{
		const gchar *uri = kz_bookmark_get_link(bookmark);

		/*
		 * <bookmark>\n
		 * <title/>
		 * </bookmark>
		 */

		node = kz_xml_element_node_new("bookmark");
		if (uri)
			kz_xml_node_set_attr(node, "href", uri);

		space = kz_xml_text_node_new("\n");
		kz_xml_node_append_child(node, space);

		xml_node_append_title (node, bookmark);
	}

	return node;
}


static void
kz_xbel_connect_signals (KzBookmark *bookmark)
{
	g_signal_connect(G_OBJECT(bookmark), "notify",
			 G_CALLBACK(cb_bookmark_notify), NULL);

	if (kz_bookmark_is_file(bookmark)) return;
	if (!kz_bookmark_is_folder(bookmark)) return;

	{
		g_signal_connect(G_OBJECT(bookmark), "insert-child",
				 G_CALLBACK(cb_bookmark_insert_child), NULL);
		g_signal_connect(G_OBJECT(bookmark), "remove-child",
				 G_CALLBACK(cb_bookmark_remove_child), NULL);
	}

	/* for children */
	{
		GList *node, *children;

		children = kz_bookmark_get_children(bookmark);
		for (node = children; node; node = g_list_next(node))
		{
			KzBookmark *child = node->data;
			kz_xbel_connect_signals(child);
		}
		g_list_free(children);
	}
}


static void
kz_xbel_disconnect_signals (KzBookmark *bookmark)
{
	g_signal_handlers_disconnect_by_func
		(G_OBJECT(bookmark),
		 G_CALLBACK(cb_bookmark_notify), NULL);

	if (kz_bookmark_is_file(bookmark)) return;
	if (!kz_bookmark_is_folder(bookmark)) return;

	{
		g_signal_handlers_disconnect_by_func
			(G_OBJECT(bookmark),
			 G_CALLBACK(cb_bookmark_insert_child), NULL);
		g_signal_handlers_disconnect_by_func
			(G_OBJECT(bookmark),
			 G_CALLBACK(cb_bookmark_remove_child), NULL);
	}

	/* for children */
	{
		GList *node, *children;

		children = kz_bookmark_get_children(bookmark);
		for (node = children; node; node = g_list_next(node))
		{
			KzBookmark *child = node->data;
			kz_xbel_disconnect_signals(child);
		}
		g_list_free(children);
	}
}


static void
cb_bookmark_insert_child (KzBookmark *bookmark,
			  KzBookmark *child, KzBookmark *sibling)
{
	kz_xbel_insert_xml_node(child, bookmark, sibling);
	kz_xbel_connect_signals(child);
}


static void
cb_bookmark_remove_child (KzBookmark *bookmark, KzBookmark *child)
{
	kz_xbel_disconnect_signals(child);
	kz_xbel_remove_xml_node(child);
}


static void
xml_node_set_title (KzXMLNode *parent, const gchar *title)
{
	KzXMLNode *title_node = NULL, *node, *child;

	g_return_if_fail(parent);

	node = kz_xml_node_first_child(parent);

	/* find title node */
	for (; node; node = kz_xml_node_next(node))
	{
		if (kz_xml_node_name_is(node, "title"))
		{
			title_node = node;
			break;
		}
	}

	g_return_if_fail(title_node);

	/* remove old title */
	child = kz_xml_node_first_child(title_node);
	while(child)
	{
		KzXMLNode *next = kz_xml_node_next(child);

		child = kz_xml_node_remove_child(title_node, child);
		kz_xml_node_unref(child);
		child = next;
	}

	/* set new title */
	child = kz_xml_text_node_new(title);
	kz_xml_node_append_child(title_node, child);
}


static void
xml_node_set_description (KzXMLNode *parent, const gchar *desc)
{
	KzXMLNode *desc_node = NULL, *node, *child;

	g_return_if_fail(parent);

	node = kz_xml_node_first_child(parent);

	/* find desc node */
	for (; node; node = kz_xml_node_next(node))
	{
		if (kz_xml_node_name_is(node, "desc"))
		{
			desc_node = node;
			break;
		}
	}

	/* remove old title, or create new desc node */
	if (desc_node)
	{
		child = kz_xml_node_first_child(node);
		while(child)
		{
			KzXMLNode *next = kz_xml_node_next(child);

			child = kz_xml_node_remove_child(node, child);
			kz_xml_node_unref(child);
			child = next;
		}
	}
	else
	{
		KzXMLNode *space;

		desc_node = kz_xml_element_node_new("desc");
		kz_xml_node_append_child(parent, desc_node);

		space = kz_xml_text_node_new("\n");
		kz_xml_node_append_child(parent, space);
	}

	/* set new description */
	child = kz_xml_text_node_new(desc);
	kz_xml_node_append_child(desc_node, child);
}


static void
xml_node_set_element (KzXMLNode *parent, const gchar *contents, const gchar *name)
{
	KzXMLNode *element_node = NULL, *node, *child;

	g_return_if_fail(parent);
	g_return_if_fail(name);

	node = kz_xml_node_first_child(parent);

	/* find node */
	for (; node; node = kz_xml_node_next(node))
	{
		if (kz_xml_node_name_is(node, name))
		{
			element_node = node;
			break;
		}
	}

	/* remove old element node, or create new element node */
	if (element_node)
	{
		child = kz_xml_node_first_child(node);
		while(child)
		{
			KzXMLNode *next = kz_xml_node_next(child);

			child = kz_xml_node_remove_child(node, child);
			kz_xml_node_unref(child);
			child = next;
		}
	}
	else
	{
		KzXMLNode *space;

		element_node = kz_xml_element_node_new(name);
		kz_xml_node_append_child(parent, element_node);

		space = kz_xml_text_node_new("\n");
		kz_xml_node_append_child(parent, space);
	}

	/* set new element */
	child = kz_xml_text_node_new(contents);
	kz_xml_node_append_child(element_node, child);
}


static void
cb_bookmark_notify (GObject *object, GParamSpec *pspec)
{
	KzBookmark *bookmark;
	KzXMLNode *node;
	const gchar *prop;
        GValue value = { 0 };

	g_return_if_fail(KZ_IS_BOOKMARK(object));
	bookmark = KZ_BOOKMARK(object);

	if (BOOKMARK_IS_BUILDING(bookmark)) return;

	node = g_object_get_qdata(G_OBJECT(bookmark), node_quark);
	g_return_if_fail(node);
	g_return_if_fail(kz_xml_node_name_is(node, "bookmark") ||
			 kz_xml_node_name_is(node, "folder"));

	prop = g_param_spec_get_name(pspec);
	g_return_if_fail(prop);

        g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(pspec));
        g_object_get_property(object, prop, &value);

	/*
	 * FIXME! We should use hash table.
	 */
	if (!strcmp(prop, "title"))
	{
		gchar *title = g_value_dup_string(&value);

		xml_node_set_title(node, title);
		g_free(title);
	}
	else if (!strcmp(prop, "link"))
	{
		gchar *uri = g_value_dup_string(&value);

		kz_xml_node_set_attr(node, "href", uri);
		g_free(uri);
	}
	else if (!strcmp(prop, "location"))
	{
		gchar *location = g_value_dup_string(&value);

		xml_node_set_location(node, bookmark);
		g_free(location);
	}
	else if (!strcmp(prop, "description"))
	{
		gchar *desc = g_value_dup_string(&value);

		xml_node_set_description(node, desc);
		g_free(desc);
	}
	/*
	else if (!strcmp(prop, "interval"))
	{
		gchar *interval = g_value_dup_string(&value);

		xml_node_set_element(node, interval, "update_interval");
		g_free(interval);
	}
	*/ 
}


#if 0
void
kz_xbel_save (KzXBEL *xbel)
{
	const gchar *location;;

	g_return_if_fail(KZ_IS_XBEL(xbel));

	if (xbel->xml)
		kz_xml_node_arrange_indent(xbel->xml->root, 0);

	location = kz_bookmark_get_location(KZ_BOOKMARK(xbel));
	if (location && *location)
		kz_xml_save(xbel->xml, location);
}
#endif


static void
kz_xbel_insert_xml_node (KzBookmark *bookmark,
			 KzBookmark *parent, KzBookmark *sibling)
{
	KzXMLNode *node, *space;
	KzXMLNode *parent_node, *sibling_node = NULL;

	g_return_if_fail(KZ_IS_BOOKMARK(parent));
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_return_if_fail(!sibling || KZ_IS_BOOKMARK(sibling));

	node = g_object_get_qdata(G_OBJECT(bookmark), node_quark);
	if (node) return;

	parent_node = g_object_get_qdata(G_OBJECT(parent),
					 node_quark);
	if (!parent_node)
	{
		KzXML *xml;

		g_return_if_fail(KZ_IS_BOOKMARK(parent));

		xml = g_object_get_qdata(G_OBJECT(parent), xml_quark);
		g_return_if_fail(KZ_IS_XML(xml));

		parent_node = kz_xml_get_root_element(xml);
		g_return_if_fail (kz_xml_node_name_is(parent_node, "xbel"));
	}

	if (sibling)
		sibling_node = g_object_get_qdata(G_OBJECT(sibling),
						  node_quark);

	node = create_xml_node(bookmark);
	g_object_set_qdata(G_OBJECT(bookmark), node_quark, node);

	kz_xml_node_insert_before(parent_node, node, sibling_node);
	space = kz_xml_text_node_new("\n");
	kz_xml_node_insert_before(parent_node, space, kz_xml_node_next(node));

	if (kz_bookmark_is_folder(bookmark) && !kz_bookmark_is_file(bookmark))
	{
		GList *node, *children;

		children = kz_bookmark_get_children(bookmark);
		for (node = children; node; node = g_list_next(node))
		{
			KzBookmark *child = node->data;
			kz_xbel_insert_xml_node (child, bookmark, NULL);
		}
		g_list_free(children);
	}
}


static void
kz_xbel_remove_xml_node (KzBookmark *bookmark)
{
	KzXMLNode *node, *parent;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	if (kz_bookmark_is_folder(bookmark))
	{
		GList *node, *children;

		children = kz_bookmark_get_children(bookmark);
		for (node = children; node; node = g_list_next(node))
		{
			KzBookmark *child = node->data;
			kz_xbel_remove_xml_node (child);
		}
		g_list_free(children);
	}

	node = g_object_get_qdata(G_OBJECT(bookmark), node_quark);
	if (!node) return;

	parent = kz_xml_node_parent(node);
	if (parent)
	{
		KzXMLNode *space;

		space = kz_xml_node_next(node);
		if (space && kz_xml_node_is_space(space))
		{
			space = kz_xml_node_remove_child(parent, space);
			kz_xml_node_unref(space);
		}

		node = kz_xml_node_remove_child(parent, node);
	}

	kz_xml_node_unref(node);
	g_object_set_qdata(G_OBJECT(bookmark), node_quark, NULL);
}
