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

/*
 *  Copyright (C) 2003 Hiroyuki Ikezoe
 *  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-bookmark.c 641 2004-01-25 09:50:30Z ikezoe $
 */

#include "kz-bookmark.h"

#include <string.h>
#include "gobject-utils.h"
#include "intl.h"
#include "egg-marshalers.h"

#include "kz-downloader.h"
#include "kz-io.h"

/* bookmark file types */
#include "kz-xbel.h"
#include "kz-rss.h"
#include "kz-nsbookmark.h"
#include "kz-lirs.h"
#include "kz-hinadi.h"
#include "kz-google.h"

enum {
	INSERT_CHILD_SIGNAL,
	REMOVE_CHILD_SIGNAL,
	MOVE_CHILD_SIGNAL,
	CHILDREN_REORDERED_SIGNAL,
	LOAD_START_SIGNAL,
	LOAD_COMPLETED_SIGNAL,
	SAVE_START_SIGNAL,
	SAVE_COMPLETED_SIGNAL,
	LAST_SIGNAL
};

enum {
	PROP_0,
	PROP_TYPE,
	PROP_TITLE,
	PROP_DOC_TITLE,
	PROP_LINK,
	PROP_BOOKMARK_LOCATION,
	PROP_FILE_TYPE,
	PROP_DESCRIPTION,
	PROP_LAST_MODIFIED,
	PROP_INTERVAL,
	PROP_EDITABLE
};

#define ADD_BOOKMARK_FILE_TYPE(func) 					\
{									\
	KzBookmarkFileType *type;					\
	gint i;								\
	for (i = 0; (type = func(i)); i++)				\
		file_types = g_list_append(file_types, type);		\
}

static void kz_bookmark_class_init   (KzBookmarkClass *klass);
static void kz_bookmark_init         (KzBookmark      *bookmark);
static void kz_bookmark_dispose      (GObject         *object);
static void kz_bookmark_set_property (GObject         *object,
				      guint            prop_id,
				      const GValue    *value,
				      GParamSpec      *pspec);
static void kz_bookmark_get_property (GObject         *object,
				      guint            prop_id,
				      GValue          *value,
				      GParamSpec      *pspec);

static void kz_bookmark_real_insert_child (KzBookmark *bookmark,
					   KzBookmark *child,
					   KzBookmark *sibling);
static void kz_bookmark_real_remove_child (KzBookmark *bookmark,
					   KzBookmark *child);

static void kz_bookmark_set_state          (KzBookmark *bookmark,
					    KzBookmarkState state);

/* load/save */
static void kz_bookmark_load_completed   (KzBookmark   *bookmark,
					  KzDownloader *dl,
					  gboolean      error);
static KzBookmarkFileType *
            kz_bookmark_detect_file_type (KzBookmark   *bookmark,
					  gchar        *buf);
static void cb_downloader_load_complete  (KzDownloader *dl,
					  KzBookmark   *bookmark);
static void cb_downloader_load_error     (KzDownloader *dl,
					  KzBookmark   *bookmark);

static GObjectClass *parent_class = NULL;
static gint kz_bookmark_signals[LAST_SIGNAL] = {0};

static GList *file_types = NULL;

static GQuark title_quark             = 0;
static GQuark doc_title_quark         = 0;
static GQuark link_quark              = 0;
static GQuark location_quark          = 0;
static GQuark file_type_quark         = 0;
static GQuark description_quark       = 0;
static GQuark last_mod_quark          = 0;
static GQuark parent_quark            = 0;
static GQuark children_quark          = 0;
static GQuark interval_quark          = 0;
static GQuark timer_quark             = 0;
static GQuark state_quark             = 0;


GType
kz_bookmark_type_get_type (void)
{
	static GType etype = 0;
	if (etype == 0)
	{
		static const GEnumValue values[] = {
			{ KZ_BOOKMARK_NORMAL,      "KZ_BOOKMARK_NORMAL",      "normal" },
			{ KZ_BOOKMARK_FOLDER,      "KZ_BOOKMARK_FOLDER",      "folder" },
			{ KZ_BOOKMARK_PURE_FOLDER, "KZ_BOOKMARK_PURE_FOLDER", "pure-folder" },
			{ KZ_BOOKMARK_SEPARATOR,   "KZ_BOOKMARK_SEPARATOR",   "separator" },
			{ 0, NULL, NULL }
		};
		etype = g_enum_register_static ("KzBookmarkType", values);
	}
	return etype;
}


KZ_OBJECT_GET_TYPE(kz_bookmark, "KzBookmark", KzBookmark,
		   kz_bookmark_class_init, kz_bookmark_init,
		   G_TYPE_OBJECT)


static void
kz_bookmark_class_init (KzBookmarkClass *klass)
{
	GObjectClass *object_class;

	if (!file_types)
	{
		ADD_BOOKMARK_FILE_TYPE(kz_rss_get_file_types);
		ADD_BOOKMARK_FILE_TYPE(kz_lirs_get_file_types);
		ADD_BOOKMARK_FILE_TYPE(kz_hinadi_get_file_types);
		ADD_BOOKMARK_FILE_TYPE(kz_google_get_file_types);
		ADD_BOOKMARK_FILE_TYPE(kz_xbel_get_file_types);
		ADD_BOOKMARK_FILE_TYPE(kz_nsbookmark_get_file_types);
	}

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

	object_class->dispose      = kz_bookmark_dispose;
	object_class->set_property = kz_bookmark_set_property;
	object_class->get_property = kz_bookmark_get_property;

	klass->insert_child       = kz_bookmark_real_insert_child;
	klass->remove_child       = kz_bookmark_real_remove_child;
	klass->move_child         = NULL; /* kz_bookmark_real_move_child; */
	klass->children_reordered = NULL;
	klass->load_start         = NULL;
	klass->load_completed     = NULL;
	klass->save_start         = NULL;
	klass->save_completed     = NULL;

	g_object_class_install_property(
		object_class,
		PROP_TYPE,
		g_param_spec_enum (
			"type",
			_("Type"),
			_("Type of the bookmark"),
			KZ_TYPE_BOOKMARK_TYPE,
			KZ_BOOKMARK_NORMAL,
			G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_TITLE,
		 g_param_spec_string(
			 "title",
			 _("Title"),
			 _("The title of the bookmark"),
			 NULL,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_DOC_TITLE,
		 g_param_spec_string(
			 "document-title",
			 _("Original document title"),
			 _("The original document title of the link"),
			 NULL,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_LINK,
		 g_param_spec_string(
			 "link",
			 _("Link"),
			 _("The URI of the link"),
			 NULL,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_BOOKMARK_LOCATION,
		 g_param_spec_string(
			 "location",
			 _("Location of bookmark file itself"),
			 _("The location of the bookmark file itself"),
			 NULL,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_FILE_TYPE,
		 g_param_spec_string(
			 "file-type",
			 _("File type"),
			 _("File type of the bookmark file"),
			 NULL,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_DESCRIPTION,
		 g_param_spec_string(
			 "description",
			 _("Description"),
			 _("The description of the bookmark"),
			 NULL,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_LAST_MODIFIED,
		 g_param_spec_uint(
			 "last-modified",
			 _("Last Modified"),
			 _("Last modification time of the link"),
			 0,
			 G_MAXUINT,
			 0,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_INTERVAL,
		 g_param_spec_uint(
			 "interval",
			 _("Interval"),
			 _("Update interval"),
			 0,
			 G_MAXUINT,
			 0,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		 PROP_EDITABLE,
		 g_param_spec_boolean(
			 "editable",
			 _("Editable"),
			 _("Whether attributes of the bookmark is writable or not"),
			 TRUE,
			 G_PARAM_READWRITE));

	kz_bookmark_signals[INSERT_CHILD_SIGNAL]
		= g_signal_new ("insert-child",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (KzBookmarkClass, insert_child),
				NULL, NULL,
				_egg_marshal_VOID__OBJECT_OBJECT,
				G_TYPE_NONE, 2,
				KZ_TYPE_BOOKMARK, KZ_TYPE_BOOKMARK);

	kz_bookmark_signals[REMOVE_CHILD_SIGNAL]
		= g_signal_new ("remove-child",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (KzBookmarkClass, remove_child),
				NULL, NULL,
				g_cclosure_marshal_VOID__OBJECT,
				G_TYPE_NONE, 1,
				KZ_TYPE_BOOKMARK);

	kz_bookmark_signals[MOVE_CHILD_SIGNAL]
		= g_signal_new ("move-child",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (KzBookmarkClass, move_child),
				NULL, NULL,
				_egg_marshal_VOID__OBJECT_OBJECT,
				G_TYPE_NONE, 2,
				KZ_TYPE_BOOKMARK, KZ_TYPE_BOOKMARK);

	kz_bookmark_signals[CHILDREN_REORDERED_SIGNAL]
		= g_signal_new ("children-reordered",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_FIRST,
				G_STRUCT_OFFSET (KzBookmarkClass,
						 children_reordered),
				NULL, NULL,
				g_cclosure_marshal_VOID__VOID,
				G_TYPE_NONE, 0);

	kz_bookmark_signals[LOAD_START_SIGNAL]
		= g_signal_new("load_start",
			       G_TYPE_FROM_CLASS (klass),
			       G_SIGNAL_RUN_LAST,
			       G_STRUCT_OFFSET (KzBookmarkClass,
						load_start),
			       NULL, NULL,
			       g_cclosure_marshal_VOID__VOID,
			       G_TYPE_NONE, 0);

	kz_bookmark_signals[LOAD_COMPLETED_SIGNAL]
		= g_signal_new("load_completed",
			       G_TYPE_FROM_CLASS (klass),
			       G_SIGNAL_RUN_LAST,
			       G_STRUCT_OFFSET (KzBookmarkClass,
						load_completed),
			       NULL, NULL,
			       g_cclosure_marshal_VOID__VOID,
			       G_TYPE_NONE, 0);

	kz_bookmark_signals[SAVE_START_SIGNAL]
		= g_signal_new("save_start",
			       G_TYPE_FROM_CLASS (klass),
			       G_SIGNAL_RUN_LAST,
			       G_STRUCT_OFFSET (KzBookmarkClass,
						save_start),
			       NULL, NULL,
			       g_cclosure_marshal_VOID__VOID,
			       G_TYPE_NONE, 0);

	kz_bookmark_signals[SAVE_COMPLETED_SIGNAL]
		= g_signal_new("save_completed",
			       G_TYPE_FROM_CLASS (klass),
			       G_SIGNAL_RUN_LAST,
			       G_STRUCT_OFFSET (KzBookmarkClass,
						save_completed),
			       NULL, NULL,
			       g_cclosure_marshal_VOID__VOID,
			       G_TYPE_NONE, 0);

	/* FIXME! add other properties */
	/*
	 * candidates:
	 * 
	 *   * ID
	 *   * Last-Modified time
	 *   * Last-Accessed time
	 *   * frequency
	 *   * ......
	 */

	title_quark       = g_quark_from_string("KzBookmark::Title");
	doc_title_quark   = g_quark_from_string("KzBookmark::DocumentTitle");
	link_quark        = g_quark_from_string("KzBookmark::Link");
	location_quark    = g_quark_from_string("KzBookmark::BookmarkLocation");
	file_type_quark   = g_quark_from_string("KzBookmark::FileType");
	description_quark = g_quark_from_string("KzBookmark::Description");
	last_mod_quark    = g_quark_from_string("KzBookmark::LastModified");
	parent_quark      = g_quark_from_string("KzBookmark::Parent");
	children_quark    = g_quark_from_string("KzBookmark::Children");
	interval_quark    = g_quark_from_string("KzBookmark::Interval");
	timer_quark       = g_quark_from_string("KzBookmark::Timer");
	state_quark       = g_quark_from_string("KzBookmark::State");
}


static void
kz_bookmark_init (KzBookmark *bookmark)
{
	bookmark->type  = KZ_BOOKMARK_NORMAL;
	bookmark->flags = KZ_BOOKMARK_EDITABLE_FLAG;
}


static void
kz_bookmark_dispose (GObject *object)
{
	KzBookmark *bookmark;
	guint timer_id;

	g_return_if_fail(KZ_IS_BOOKMARK(object));

	bookmark = KZ_BOOKMARK(object);

	kz_bookmark_remove_all(bookmark);

	timer_id = GPOINTER_TO_UINT(g_object_get_qdata(object, timer_quark));
	if (timer_id)
		g_source_remove(timer_id);
	timer_id = 0;
	g_object_set_qdata(object, timer_quark, GUINT_TO_POINTER(timer_id));

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


#define CHANGE_STR(obj, quark, value) \
{ \
	g_object_set_qdata_full((obj), (quark), (value), \
				(GDestroyNotify) g_free); \
}


static void
kz_bookmark_set_property (GObject *object,
			  guint prop_id,
			  const GValue *value,
			  GParamSpec *pspec)
{
	KzBookmark *bookmark = KZ_BOOKMARK(object);

	switch (prop_id) {
	case PROP_TYPE:
		bookmark->type = g_value_get_enum(value);
		break;
	case PROP_TITLE:
		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		CHANGE_STR(object, title_quark, g_value_dup_string(value));
		break;
	case PROP_DOC_TITLE:
		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		CHANGE_STR(object, doc_title_quark, g_value_dup_string(value));
		break;
	case PROP_LINK:
		g_return_if_fail(!kz_bookmark_is_pure_folder(bookmark));
		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		CHANGE_STR(object, link_quark, g_value_dup_string(value));
		break;
	case PROP_BOOKMARK_LOCATION:
	{
		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		CHANGE_STR(object, location_quark,
			   g_value_dup_string(value));
		break;
	}
	case PROP_FILE_TYPE:
	{
		gchar *str = g_value_dup_string(value);
		KzBookmarkFileType *type;

		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		g_return_if_fail(!kz_bookmark_get_file_type(bookmark));

		CHANGE_STR(object, file_type_quark, str);

		if (str && *str)
		{
			type = kz_bookmark_detect_file_type(bookmark, NULL);
			if (type && type->init)
				type->init(bookmark);
			if (type && !type->to_string)
				kz_bookmark_set_editable(bookmark, FALSE);
		}
		break;
	}
	case PROP_DESCRIPTION:
		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		CHANGE_STR(object, description_quark,
			   g_value_dup_string(value));
		break;
	case PROP_LAST_MODIFIED:
		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		g_object_set_qdata(object, last_mod_quark,
				   GUINT_TO_POINTER(g_value_get_uint(value)));
		break;
	case PROP_INTERVAL:
	{
		guint interval, timer_id;

		interval = g_value_get_uint(value);
		timer_id = GPOINTER_TO_UINT
				(g_object_get_qdata(object, timer_quark));

		if (timer_id)
			g_source_remove(timer_id);
		timer_id = 0;

		if (interval > 0)
			timer_id = g_timeout_add(60000 * interval,
						 (gpointer) kz_bookmark_load_in_memory_start,
						 bookmark);

		g_object_set_qdata(object, interval_quark,
				   GUINT_TO_POINTER(interval));
		g_object_set_qdata(object, timer_quark,
				   GUINT_TO_POINTER(timer_id));
		break;
	}
	case PROP_EDITABLE:
		g_return_if_fail(!kz_bookmark_is_separator(bookmark));
		if (g_value_get_boolean(value))
			bookmark->flags |= KZ_BOOKMARK_EDITABLE_FLAG;
		else
			bookmark->flags &= ~KZ_BOOKMARK_EDITABLE_FLAG;
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}


static void
kz_bookmark_get_property (GObject *object,
		     guint prop_id,
		     GValue *value,
		     GParamSpec *pspec)
{
	KzBookmark *bookmark = KZ_BOOKMARK(object);
	gchar *str;
	guint time;

	switch (prop_id) {
	case PROP_TYPE:
		g_value_set_enum(value, bookmark->type);
		break;
	case PROP_TITLE:
		str = g_object_get_qdata(object, title_quark);
		g_value_set_string(value, str);
		break;
	case PROP_DOC_TITLE:
		str = g_object_get_qdata(object, doc_title_quark);
		g_value_set_string(value, str);
		break;
	case PROP_LINK:
		str = g_object_get_qdata(object, link_quark);
		g_value_set_string(value, str);
		break;
	case PROP_BOOKMARK_LOCATION:
		str = g_object_get_qdata(object, location_quark);
		g_value_set_string(value, str);
		break;
	case PROP_FILE_TYPE:
		str = g_object_get_qdata(object, file_type_quark);
		g_value_set_string(value, str);
		break;
	case PROP_DESCRIPTION:
		str = g_object_get_qdata(object, description_quark);
		g_value_set_string(value, str);
		break;
	case PROP_LAST_MODIFIED:
		time = GPOINTER_TO_UINT(g_object_get_qdata(object,
							   last_mod_quark));
		g_value_set_uint(value, time);
		break;
	case PROP_INTERVAL:
	{
		guint interval;

		interval = GPOINTER_TO_UINT
				(g_object_get_qdata(object, interval_quark));
		g_value_set_uint(value, interval);
		break;
	}
	case PROP_EDITABLE:
		if (bookmark->flags & KZ_BOOKMARK_EDITABLE_FLAG)
			g_value_set_boolean(value, TRUE);
		else
			g_value_set_boolean(value, FALSE);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}


KzBookmark *
kz_bookmark_new (void)
{
	KzBookmark *bookmark = KZ_BOOKMARK(g_object_new(KZ_TYPE_BOOKMARK,
							NULL));
	return bookmark;
}


KzBookmark *
kz_bookmark_pure_folder_new (void)
{
	KzBookmark *bookmark;

	bookmark = KZ_BOOKMARK(g_object_new(KZ_TYPE_BOOKMARK,
					    "type", KZ_BOOKMARK_PURE_FOLDER,
					    NULL));
	return bookmark;
}


KzBookmark *
kz_bookmark_separator_new (void)
{
	KzBookmark *bookmark;

	bookmark = KZ_BOOKMARK(g_object_new(KZ_TYPE_BOOKMARK,
					    "title", "-----",
					    "type",  KZ_BOOKMARK_SEPARATOR,
					    NULL));
	return bookmark;
}


KzBookmark *
kz_bookmark_new_with_attrs (const gchar *title,
			    const gchar *uri,
			    const gchar *description)
{
	KzBookmark *bookmark;

	bookmark = KZ_BOOKMARK(g_object_new(KZ_TYPE_BOOKMARK,
					    "title",       title,
					    "link",        uri,
					    "description", description,
					    NULL));
	return bookmark;
}


KzBookmark *
kz_bookmark_file_new (const gchar *location,
		      const gchar *title,
		      const gchar *file_type)
{
	KzBookmark *bookmark;

	bookmark = KZ_BOOKMARK(g_object_new(KZ_TYPE_BOOKMARK,
					    "type",      KZ_BOOKMARK_FOLDER,
					    "location",  location,
					    "title",     title,
					    "file-type", file_type,
					    NULL));
	bookmark->flags |= KZ_BOOKMARK_FILE_FLAG;
	/* kz_bookmark_load_start(bookmark); */

	return bookmark;
}


KzBookmark *
kz_bookmark_file_create_new (const gchar *location,
			     const gchar *title,
			     const gchar *file_type)
{
	KzBookmark *bookmark;
	KzBookmarkFileType *type;

	bookmark = KZ_BOOKMARK(g_object_new(KZ_TYPE_BOOKMARK,
					    "type",      KZ_BOOKMARK_FOLDER,
					    "location",  location,
					    "title",     title,
					    "file-type", file_type,
					    NULL));
	bookmark->flags |= KZ_BOOKMARK_FILE_FLAG;

	type = kz_bookmark_detect_file_type(bookmark, NULL);
	if (type && type->from_string)
	{
		if (!kz_bookmark_get_file_type(bookmark))
			kz_bookmark_set_file_type(bookmark, type->file_type);
		type->from_string(bookmark, NULL, 0, NULL);
	}

	return bookmark;
}


const gchar *
kz_bookmark_get_title (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark), title_quark);
}


const gchar *
kz_bookmark_get_document_title (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark), doc_title_quark);
}


const gchar *
kz_bookmark_get_link (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark), link_quark);
}


const gchar *
kz_bookmark_get_description (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark), description_quark);
}


const gchar *
kz_bookmark_get_location (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark), location_quark);
}


const gchar *
kz_bookmark_get_file_type (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark), file_type_quark);
}


guint
kz_bookmark_get_last_modified (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), 0);
	return GPOINTER_TO_UINT(g_object_get_qdata(G_OBJECT(bookmark),
						   last_mod_quark));
}


guint
kz_bookmark_get_interval (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), 0);
	return GPOINTER_TO_UINT(g_object_get_qdata(G_OBJECT(bookmark),
						   interval_quark));
}


gboolean
kz_bookmark_is_editable (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);
	return bookmark->flags & KZ_BOOKMARK_EDITABLE_FLAG ? TRUE : FALSE;
}


gboolean
kz_bookmark_is_file (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);
	return bookmark->flags & KZ_BOOKMARK_FILE_FLAG ? TRUE : FALSE;
}


void
kz_bookmark_set_title (KzBookmark *bookmark,
		       const gchar *title)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "title", title, NULL);
}


void
kz_bookmark_set_document_title (KzBookmark *bookmark,
				const gchar *doc_title)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "document-title", doc_title, NULL);
}


void
kz_bookmark_set_link (KzBookmark *bookmark,
		      const gchar *uri)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "link", uri, NULL);
}


void
kz_bookmark_set_description (KzBookmark *bookmark,
			     const gchar *description)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "description", description, NULL);
}


void
kz_bookmark_set_location (KzBookmark *bookmark,
			  const gchar *location)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "location", location, NULL);
}


void
kz_bookmark_set_file_type (KzBookmark *bookmark,
			   const gchar *file_type)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "file-type", file_type, NULL);
}


void
kz_bookmark_set_last_modified (KzBookmark *bookmark,
			       guint time)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "last-modified", time, NULL);
}


void
kz_bookmark_set_interval (KzBookmark *bookmark, guint interval)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "interval", interval, NULL);
}


gboolean
kz_bookmark_is_separator (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	if (bookmark->type == KZ_BOOKMARK_SEPARATOR)
		return TRUE;
	else
		return FALSE;
}


void
kz_bookmark_set_editable (KzBookmark *bookmark, gboolean editable)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(bookmark), "editable", editable, NULL);
}


gboolean
kz_bookmark_is_folder(KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	if (bookmark->type == KZ_BOOKMARK_FOLDER ||
	    bookmark->type == KZ_BOOKMARK_PURE_FOLDER)
	{
		return TRUE;
	}

	return FALSE;
}


gboolean
kz_bookmark_is_pure_folder (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	if (bookmark->type == KZ_BOOKMARK_PURE_FOLDER)
		return TRUE;
	else
		return FALSE;
}


gboolean
kz_bookmark_get_folded (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	if (!kz_bookmark_is_folder(bookmark)) return TRUE;

	return bookmark->flags & KZ_BOOKMARK_FOLDED_FLAG;
}


void
kz_bookmark_set_folded (KzBookmark  *bookmark, gboolean folded)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	if (folded)
	{
		bookmark->flags |= KZ_BOOKMARK_FOLDED_FLAG;
	}
	else
	{
		bookmark->flags &= ~KZ_BOOKMARK_FOLDED_FLAG;
	}
}


static void
kz_bookmark_real_insert_child (KzBookmark *bookmark,
			       KzBookmark *child,
			       KzBookmark *sibling)
{
	GList *list, *next;

	g_return_if_fail(KZ_IS_BOOKMARK(child));
	g_return_if_fail(!sibling || KZ_IS_BOOKMARK(sibling));
	g_return_if_fail(kz_bookmark_is_folder(bookmark));

	list = g_object_get_qdata(G_OBJECT(bookmark), children_quark);
	next = g_list_find(list, sibling);

	g_object_ref(child);
	list = g_list_insert_before(list, next, child);
	g_object_set_qdata(G_OBJECT(bookmark), children_quark, list);

	g_object_set_qdata(G_OBJECT(child), parent_quark, bookmark);
}


void
kz_bookmark_insert_before (KzBookmark *bookmark,
			   KzBookmark *child,
			   KzBookmark *sibling)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	g_signal_emit(G_OBJECT(bookmark),
		      kz_bookmark_signals[INSERT_CHILD_SIGNAL], 0,
		      child, sibling);
}


void
kz_bookmark_append (KzBookmark *bookmark, KzBookmark *child)
{
	kz_bookmark_insert_before(bookmark, child, NULL);
}


void
kz_bookmark_prepend (KzBookmark *bookmark, KzBookmark *child)
{
	GList *list;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	list = g_object_get_qdata(G_OBJECT(bookmark), children_quark);
	if (list)
		kz_bookmark_insert_before(bookmark, child, list->data);
	else
		kz_bookmark_insert_before(bookmark, child, NULL);		
}


static void
kz_bookmark_real_remove_child (KzBookmark *bookmark, KzBookmark *child)
{
	GList *list;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_return_if_fail(KZ_IS_BOOKMARK(child));
	g_return_if_fail(kz_bookmark_is_folder(bookmark));

	list = g_object_get_qdata(G_OBJECT(bookmark), children_quark);
	if (!g_list_find(list, child)) return;

	list = g_list_remove(list, child);
	g_object_unref(child);
	g_object_set_qdata(G_OBJECT(bookmark), children_quark, list);

	g_object_set_qdata(G_OBJECT(child), parent_quark, NULL);	
}


void
kz_bookmark_remove (KzBookmark *bookmark, KzBookmark *child)
{
	g_signal_emit(G_OBJECT(bookmark),
		      kz_bookmark_signals[REMOVE_CHILD_SIGNAL], 0,
		      child);
}


void
kz_bookmark_remove_all (KzBookmark *bookmark)
{
	GList *children, *node, *prev;

	children = g_object_get_qdata(G_OBJECT(bookmark), children_quark);
	children = g_list_copy(children);
	children = g_list_last(children);

	node = children;
	while (node)
	{
		KzBookmark *child = node->data;
		prev = g_list_previous(node);
		kz_bookmark_remove (bookmark, child);
		node = prev;
	}

	g_list_free(children);
}


KzBookmark *
kz_bookmark_get_parent (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	return g_object_get_qdata(G_OBJECT(bookmark), parent_quark);
}


GList *
kz_bookmark_get_children (KzBookmark *bookmark)
{
	GList *list;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	list =  g_object_get_qdata(G_OBJECT(bookmark), children_quark);

	if (list)
		return g_list_copy(list);

	return NULL;
}


gboolean
kz_bookmark_has_children (KzBookmark  *bookmark)
{
	GList *list;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	list =  g_object_get_qdata(G_OBJECT(bookmark), children_quark);

	if (list)
		return TRUE;
	else
		return FALSE;
}


KzBookmark *
kz_bookmark_next (KzBookmark *bookmark)
{
	KzBookmark *parent;
	GList *list, *node;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	parent = kz_bookmark_get_parent(bookmark);
	if (!parent) return NULL;

	list = g_object_get_qdata(G_OBJECT(parent), children_quark);
	node = g_list_find(list, bookmark);
	if (node && (node = g_list_next(node)))
		return node->data;

	return NULL;
}


KzBookmark *
kz_bookmark_prev (KzBookmark  *bookmark)
{
	KzBookmark *parent;
	GList *list, *node;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	parent = kz_bookmark_get_parent(bookmark);
	if (!parent) return NULL;

	list = g_object_get_qdata(G_OBJECT(parent), children_quark);
	node = g_list_find(list, bookmark);
	if (node && (node = g_list_previous(node)))
		return node->data;

	return NULL;
}


static gint 
compare_func (gconstpointer a, gconstpointer b)
{
	guint one = kz_bookmark_get_last_modified((KzBookmark*)a);
	guint two = kz_bookmark_get_last_modified((KzBookmark*)b);
	return two - one;
}


void
kz_bookmark_sort (KzBookmark *bookmark, const gchar *type)
{
	GList *children;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_return_if_fail(kz_bookmark_is_folder(bookmark));

	if (!type) return;

	children = g_object_get_qdata(G_OBJECT(bookmark), children_quark);

	/* FIXME! use hash table */
	if (!strcmp(type, KZ_BOOKMARK_SORT_LAST_MODIFIED))
		children = g_list_sort(children, compare_func);
	else
		return;

	g_object_set_qdata(G_OBJECT(bookmark), children_quark, children);

	g_signal_emit(G_OBJECT(bookmark),
		      kz_bookmark_signals[CHILDREN_REORDERED_SIGNAL], 0);
}


void
kz_bookmark_regist_sort_func (const gchar *type, GCompareFunc *func)
{
	g_warning("kz_bookmark_regist_sort_func() is not implemented yet.");
}



/*****************************************************************************
 *                                                                           *
 *                        load/save booomark file                            *
 *                                                                           *
 *****************************************************************************/
gboolean
kz_bookmark_load_start (KzBookmark *bookmark)
{
	KzBookmarkState state;
	KzDownloader *dl;
	const gchar *uri;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	uri = kz_bookmark_get_location(KZ_BOOKMARK(bookmark));
	if (!uri) return FALSE;

	state = kz_bookmark_get_state(bookmark);
	if (state == KZ_BOOKMARK_STATE_LOADING)
		return TRUE;
	kz_bookmark_set_state(bookmark, KZ_BOOKMARK_STATE_LOADING);

	g_signal_emit(G_OBJECT(bookmark),
		      kz_bookmark_signals[LOAD_START_SIGNAL],
		      0);

	kz_bookmark_remove_all(KZ_BOOKMARK(bookmark));

	dl = kz_downloader_new(uri);
	g_signal_connect(G_OBJECT(dl), "completed",
			 G_CALLBACK(cb_downloader_load_complete),
			 bookmark);
	g_signal_connect(G_OBJECT(dl), "error",
			 G_CALLBACK(cb_downloader_load_error),
			 bookmark);
	kz_downloader_to_string(dl);

	return TRUE;
}


gboolean
kz_bookmark_load_in_memory_start (KzBookmark *bookmark)
{
	KzBookmarkState state;
	KzDownloader *dl;
	const gchar *uri;

	/* FIXME! */
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	uri = kz_bookmark_get_location(KZ_BOOKMARK(bookmark));
	if (!uri) return FALSE;

	state = kz_bookmark_get_state(bookmark);
	if (state == KZ_BOOKMARK_STATE_LOADING)
		return TRUE;
	kz_bookmark_set_state(bookmark, KZ_BOOKMARK_STATE_LOADING);

	g_signal_emit(G_OBJECT(bookmark),
		      kz_bookmark_signals[LOAD_START_SIGNAL],
		      0);

	kz_bookmark_remove_all(KZ_BOOKMARK(bookmark));

	dl = kz_downloader_new_in_memory(uri);
	g_signal_connect(G_OBJECT(dl), "completed",
			 G_CALLBACK(cb_downloader_load_complete),
			 bookmark);
	g_signal_connect(G_OBJECT(dl), "error",
			 G_CALLBACK(cb_downloader_load_error),
			 bookmark);
	kz_downloader_to_string(dl);

	return TRUE;
}


void
kz_bookmark_load_stop (KzBookmark *bookmark)
{
	g_warning("kz_bookmark_load_stop() is not implemented yet.");
}


gboolean
kz_bookmark_save_start (KzBookmark *bookmark)
{
	g_warning("kz_bookmark_save_start() is not implemented yet.");
	return FALSE;
}


void
kz_bookmark_save_stop (KzBookmark *bookmark)
{
	g_warning("kz_bookmark_save_stop() is not implemented yet.");
}


void
kz_bookmark_load (KzBookmark *bookmark)
{
	g_warning("kz_bookmark_load() is not implemented yet.");
}


#warning FIXME! we should use KzIO for writing contents.
void
kz_bookmark_save (KzBookmark *bookmark)
{
	KzBookmarkFileType *type;
	KzIO *io;
	const gchar *file;
	gchar *str;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	type = kz_bookmark_detect_file_type(bookmark, NULL);
	if (!type || !type->to_string) return;

	file = kz_bookmark_get_location(bookmark);
	str = type->to_string(bookmark);

	if (file && *file && str && *str)
	{
		io = kz_io_new(file, KZ_IO_WRITE);
		kz_io_set_string(io, str);
		kz_io_start(io);
		g_object_unref(io);
	}
}


static gboolean
idle_load_complete(gpointer data)
{
	KzDownloader *dl = data;

	g_object_unref(G_OBJECT(dl));

	return FALSE;
}


static void
kz_bookmark_load_completed(KzBookmark *bookmark, KzDownloader *dl,
			   gboolean error)
{
	KzBookmarkFileType *type;
	gchar *buf = NULL;
	guint size;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_return_if_fail(KZ_IS_DOWNLOADER(dl));

	if (error) goto ERROR;

	buf  = kz_downloader_get_string(dl);
	size = kz_downloader_get_current_progress(dl);

	g_return_if_fail(buf != NULL);
	if (size == 0) goto ERROR;

	type = kz_bookmark_detect_file_type(bookmark, buf);
	if (!type)
		/* use default? */;
	if (type && type->from_string)
	{
		if (!kz_bookmark_get_file_type(bookmark))
			kz_bookmark_set_file_type(bookmark, type->file_type);
		type->from_string(bookmark, buf, size, NULL);
	}

ERROR:
	g_free(buf);

	g_signal_handlers_disconnect_by_func
		(G_OBJECT(dl),
		 G_CALLBACK(cb_downloader_load_complete),
		 bookmark);
	g_signal_handlers_disconnect_by_func
		(G_OBJECT(dl),
		 G_CALLBACK(cb_downloader_load_error),
		 bookmark);
	kz_bookmark_set_state(bookmark, KZ_BOOKMARK_STATE_NORMAL);
	g_signal_emit(G_OBJECT (bookmark),
		      kz_bookmark_signals[LOAD_COMPLETED_SIGNAL],
		      0);
	g_idle_add(idle_load_complete, dl);
}


static KzBookmarkFileType *
kz_bookmark_detect_file_type(KzBookmark *bookmark, gchar *buf)
{
	GList *node;
	const gchar *type_str;

	type_str = kz_bookmark_get_file_type(bookmark);
	g_return_val_if_fail(type_str || buf, NULL);

	for (node = file_types; node; node = g_list_next(node))
	{
		KzBookmarkFileType *type = node->data;
 
		if (type_str)
		{
			/* Fixed, or already detected */

			if (type->file_type &&
			    !strcmp(type_str, type->file_type))
			{
				return type;
			}
		}
		else
		{
			/* Auto detect */

			if (type && type->is_supported &&
			    type->is_supported(bookmark, buf))
			{
				return type;
			}
		}
	}

	return NULL;
}


static void
cb_downloader_load_complete(KzDownloader *dl, KzBookmark *bookmark)
{	
	kz_bookmark_load_completed(bookmark, dl, FALSE);
}


static void
cb_downloader_load_error(KzDownloader *dl, KzBookmark *bookmark)
{	
	kz_bookmark_load_completed(bookmark, dl, TRUE);
}


static void
kz_bookmark_set_state (KzBookmark *bookmark, KzBookmarkState state)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	if (state == KZ_BOOKMARK_STATE_LOADING)
		g_object_set_qdata(G_OBJECT(bookmark), state_quark,
				   GINT_TO_POINTER(state));
	else
		g_object_set_qdata(G_OBJECT(bookmark), state_quark,
				   GINT_TO_POINTER(KZ_BOOKMARK_STATE_NORMAL));
}


KzBookmarkState
kz_bookmark_get_state (KzBookmark *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark),
			     KZ_BOOKMARK_STATE_NORMAL);

	return GPOINTER_TO_INT(g_object_get_qdata(G_OBJECT(bookmark),
						  state_quark));
}
