/* -*- 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-file.c 2256 2005-07-20 04:50:48Z ikezoe $
 */

#include "kz-bookmark-file.h"

#include <string.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include "gobject-utils.h"
#include "glib-utils.h"
#include "utils.h"
#include "eggregex.h"

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

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

enum {
	LOAD_START_SIGNAL,
	LOAD_COMPLETED_SIGNAL,
	SAVE_START_SIGNAL,
	SAVE_COMPLETED_SIGNAL,
	ERROR_SIGNAL,
	LAST_SIGNAL
};

enum {
	PROP_0,
	PROP_BOOKMARK_FILE_LOCATION,
	PROP_FILE_TYPE,
	PROP_INTERVAL,
	PROP_XMLRPC,
	PROP_XMLRPC_USER,
	PROP_XMLRPC_PASS,
	PROP_EDITABLE,
	PROP_PREVIOUS_LAST_MODIFIED
};


#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_file_class_init   (KzBookmarkFileClass *klass);
static void kz_bookmark_file_init         (KzBookmarkFile      *bookmark_file);
static void kz_bookmark_file_dispose      (GObject             *object);
static void kz_bookmark_file_set_property (GObject             *object,
				           guint                prop_id,
				           const GValue        *value,
				           GParamSpec          *pspec);
static void kz_bookmark_file_get_property (GObject             *object,
				           guint                prop_id,
				           GValue              *value,
				           GParamSpec          *pspec);

static void kz_bookmark_file_set_state    (KzBookmarkFile      *bookmark_file,
					   KzBookmarkFileState  state);
static void kz_bookmark_file_set_editable (KzBookmarkFile      *bookmark_file,
					   gboolean             editable);

static gboolean kz_bookmark_file_is_loading_all_children (KzBookmark *bookmark);

static void     cb_io_load_complete       (KzIO *io,
					   GError *error,
					   KzBookmarkFile *bookmark_file);
static gboolean idle_load_complete        (gpointer data);

static KzBookmarkFileType *
            kz_bookmark_file_detect_file_type  (KzBookmarkFile *bookmark_file,
					        gchar          *buf);

static KzBookmarkClass *parent_class = NULL;
static gint kz_bookmark_file_signals[LAST_SIGNAL] = {0};

static GList *file_types = NULL;

static GQuark location_quark          = 0;
static GQuark file_type_quark         = 0;
static GQuark interval_quark          = 0;
static GQuark xmlrpc_quark            = 0;
static GQuark xmlrpc_user_quark       = 0;
static GQuark xmlrpc_pass_quark       = 0;
static GQuark timer_quark             = 0;
static GQuark state_quark             = 0;
static GQuark io_quark                = 0;
static GQuark io_signal_id_quark      = 0;
static GQuark p_last_modified_quark   = 0;


KZ_OBJECT_GET_TYPE(kz_bookmark_file, "KzBookmarkFile", KzBookmarkFile,
		   kz_bookmark_file_class_init, kz_bookmark_file_init,
		   KZ_TYPE_BOOKMARK)

static void
kz_bookmark_file_class_init (KzBookmarkFileClass *klass)
{
	GObjectClass *object_class;

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

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

	object_class->dispose      = kz_bookmark_file_dispose;
	object_class->set_property = kz_bookmark_file_set_property;
	object_class->get_property = kz_bookmark_file_get_property;

	klass->load_start         = NULL;
	klass->load_completed     = NULL;
	klass->save_start         = NULL;
	klass->save_completed     = NULL;

	g_object_class_install_property(
		object_class,
		 PROP_BOOKMARK_FILE_LOCATION,
		 g_param_spec_string(
			 "location",
			 _("Location of bookmark_file file itself"),
			 _("The location of the bookmark_file 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 file"),
			 NULL,
			 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_file is writable or not"),
			 TRUE,
			 G_PARAM_READWRITE));
	
	g_object_class_install_property(
		object_class,
		 PROP_XMLRPC,
		 g_param_spec_string(
			 "xmlrpc",
			 _("XMLRPC"),
			 _("The interface address of the XML-RPC for shared bookmark_file"),
			 NULL,
			 G_PARAM_READWRITE));
	g_object_class_install_property(
		object_class,
		 PROP_XMLRPC_USER,
		 g_param_spec_string(
			 "xmlrpc-user",
			 _("XMLRPC User"),
			 _("The user name for XMLRPC"),
			 NULL,
			 G_PARAM_READWRITE));
	g_object_class_install_property(
		object_class,
		 PROP_XMLRPC_PASS,
		 g_param_spec_string(
			 "xmlrpc-pass",
			 _("XMLRPC Password"),
			 _("The password for XMLRPC"),
			 NULL,
			 G_PARAM_READWRITE));
	
	g_object_class_install_property(
		object_class,
		 PROP_PREVIOUS_LAST_MODIFIED,
		 g_param_spec_uint(
			 "previous-last-modified",
			 _("Previous Last Modified"),
			 _("The time of previous last modified"),
			 0,
			 G_MAXUINT,
			 0,
			 G_PARAM_READWRITE));

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

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

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

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

	kz_bookmark_file_signals[ERROR_SIGNAL]
		= g_signal_new("error",
			       G_TYPE_FROM_CLASS(klass),
			       G_SIGNAL_RUN_LAST,
			       G_STRUCT_OFFSET (KzBookmarkFileClass,
						error),
			       NULL, NULL,
			       g_cclosure_marshal_VOID__STRING,
			       G_TYPE_NONE, 1, G_TYPE_STRING);

	location_quark        = g_quark_from_string("KzBookmarkFile::BookmarkFileLocation");
	file_type_quark       = g_quark_from_string("KzBookmarkFile::FileType");
	interval_quark        = g_quark_from_string("KzBookmarkFile::Interval");
	timer_quark           = g_quark_from_string("KzBookmarkFile::Timer");
	state_quark           = g_quark_from_string("KzBookmarkFile::State");
	io_quark              = g_quark_from_string("KzBookmarkFile::KzIO");
	io_signal_id_quark    = g_quark_from_string("KzBookmarkFile::KzIOSignalID");
	xmlrpc_quark          = g_quark_from_string("KzBookmarkFile::XMLRPC");
	xmlrpc_user_quark     = g_quark_from_string("KzBookmarkFile::XMLRPCUser");
	xmlrpc_pass_quark     = g_quark_from_string("KzBookmarkFile::XMLRPCPassword");
	p_last_modified_quark = g_quark_from_string("KzBookmarkFile::PreviousLastModified");
}


static void
kz_bookmark_file_init (KzBookmarkFile *bookmark_file)
{
}


static void
kz_bookmark_file_dispose (GObject *object)
{
	guint timer_id, io_id;
	KzIO *io;
	
	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));

	/* dispose KzIO object if exists */
	/* FIXME! we need more robust way */
	io = g_object_get_qdata(object, io_quark);
	if (io)
	{
		kz_io_stop(io);
	}

	io_id = GPOINTER_TO_UINT(g_object_get_qdata(object, io_signal_id_quark));
	if (io_id)
	{
		/* wait until disconnecting (i.e. loading stops  )*/
		while (g_signal_handler_is_connected(io, io_id))
			gtk_main_iteration();
	}
	g_object_set_qdata(object, io_quark, NULL);

	io_id = 0;
	g_object_set_qdata(object, io_signal_id_quark,
			   GUINT_TO_POINTER(io_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_file_set_property (GObject *object,
			       guint prop_id,
			       const GValue *value,
			       GParamSpec *pspec)
{
	KzBookmarkFile *bookmark_file = KZ_BOOKMARK_FILE(object);

	switch (prop_id) {
	case PROP_BOOKMARK_FILE_LOCATION:
	{
		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_file_get_file_type(bookmark_file));

		CHANGE_STR(object, file_type_quark, str);

		if (str && *str)
		{
			type = kz_bookmark_file_detect_file_type(bookmark_file, NULL);
			if (type && type->init)
				type->init(bookmark_file);
			if (type && !type->to_string)
			{
				kz_bookmark_file_set_editable(bookmark_file, FALSE);
				/*
				 * FIXME!!
				 * Also set all children as non-editable.
				 */
			}
		}
		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_file_load_start,
						 bookmark_file);

		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:
		if (g_value_get_boolean(value))
			bookmark_file->flags |= KZ_BOOKMARK_FILE_EDITABLE_FLAG;
		else
			bookmark_file->flags &= ~KZ_BOOKMARK_FILE_EDITABLE_FLAG;
		break;
	case PROP_XMLRPC:
		CHANGE_STR(object, xmlrpc_quark, g_value_dup_string(value));
		break;
	case PROP_XMLRPC_USER:
		CHANGE_STR(object, xmlrpc_user_quark, g_value_dup_string(value));
		break;
	case PROP_XMLRPC_PASS:
		CHANGE_STR(object, xmlrpc_pass_quark, g_value_dup_string(value));
		break;
	case PROP_PREVIOUS_LAST_MODIFIED:
	{
		guint t;

		t = g_value_get_uint(value);
		g_object_set_qdata(object, p_last_modified_quark,
				   GUINT_TO_POINTER(t));
		break;
	}
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}


static void
kz_bookmark_file_get_property (GObject *object,
		               guint prop_id,
		               GValue *value,
		               GParamSpec *pspec)
{
	KzBookmarkFile *bookmark_file = KZ_BOOKMARK_FILE(object);
	gchar *str;

	switch (prop_id) {
	case PROP_BOOKMARK_FILE_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_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_file->flags & KZ_BOOKMARK_FILE_EDITABLE_FLAG)
			g_value_set_boolean(value, TRUE);
		else
			g_value_set_boolean(value, FALSE);
		break;
	case PROP_XMLRPC:
		str = g_object_get_qdata(object, xmlrpc_quark);
		g_value_set_string(value, str);
		break;
	case PROP_XMLRPC_USER:
		str = g_object_get_qdata(object, xmlrpc_user_quark);
		g_value_set_string(value, str);
		break;
	case PROP_XMLRPC_PASS:
		str = g_object_get_qdata(object, xmlrpc_pass_quark);
		g_value_set_string(value, str);
		break;
	case PROP_PREVIOUS_LAST_MODIFIED:
	{
		guint t;

		t = GPOINTER_TO_UINT(g_object_get_qdata(object, p_last_modified_quark));
		g_value_set_uint(value, t);
		break;
	}
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}


KzBookmarkFile *
kz_bookmark_file_new (const gchar *location,
		      const gchar *title,
		      const gchar *file_type)
{
	KzBookmarkFile *bookmark_file;

	location = location ? location : "";

	bookmark_file = g_object_new(KZ_TYPE_BOOKMARK_FILE,
				     "type",                   KZ_BOOKMARK_FOLDER,
				     "location",               location,
				     "title",                  title,
				     "file-type",              file_type,
				     "previous-last-modified", 0,
				     NULL);

	return bookmark_file;
}


KzBookmarkFile *
kz_bookmark_file_create_new (const gchar *location,
			     const gchar *title,
			     const gchar *file_type)
{
	KzBookmarkFile *bookmark_file;
	KzBookmarkFileType *type;

	if (!location)
	{
		g_warning(_("kz_bookmark_file_file_create_new(): "
			    "location is not specified!"));
		location = "";
	}

	bookmark_file = g_object_new(KZ_TYPE_BOOKMARK_FILE,
				     "type",      KZ_BOOKMARK_FOLDER,
				     "location",  location,
				     "title",     title,
				     "file-type", file_type,
				     NULL);

	type = kz_bookmark_file_detect_file_type(bookmark_file, NULL);
	if (type && type->from_string)
	{
		if (!kz_bookmark_file_get_file_type(bookmark_file))
			kz_bookmark_file_set_file_type(bookmark_file, type->file_type);
		type->from_string(bookmark_file, NULL, 0, NULL);
	}

	return bookmark_file;
}


const gchar *
kz_bookmark_file_get_location (KzBookmarkFile *bookmark_file)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark_file), location_quark);
}


guint
kz_bookmark_file_get_interval (KzBookmarkFile *bookmark)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK_FILE(bookmark), 0);
	return GPOINTER_TO_UINT(g_object_get_qdata(G_OBJECT(bookmark),
						   interval_quark));
}


const gchar *
kz_bookmark_file_get_xmlrpc (KzBookmarkFile *bookmark_file)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark_file), xmlrpc_quark);
}


const gchar *
kz_bookmark_file_get_xmlrpc_user (KzBookmarkFile *bookmark_file)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark_file), xmlrpc_user_quark);
}


const gchar *
kz_bookmark_file_get_xmlrpc_pass (KzBookmarkFile *bookmark_file)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark_file), xmlrpc_pass_quark);
}


const gchar *
kz_bookmark_file_get_file_type (KzBookmarkFile *bookmark_file)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file), NULL);
	return g_object_get_qdata(G_OBJECT(bookmark_file), file_type_quark);
}


gboolean
kz_bookmark_file_is_editable (KzBookmarkFile *bookmark_file)
{
	KzBookmarkFileType *type;

	g_return_val_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file), FALSE);
	
	if (!kz_bookmark_file_get_file_type(bookmark_file))
		return FALSE;

	if (g_str_has_prefix(kz_bookmark_file_get_location(bookmark_file), "http://") &&
	    !kz_bookmark_file_get_xmlrpc(bookmark_file))
		return FALSE;

	type = kz_bookmark_file_detect_file_type(bookmark_file, NULL);
	if (type && type->to_string)
		return TRUE;

	return TRUE;
}


void
kz_bookmark_file_set_location (KzBookmarkFile *bookmark_file,
			       const gchar *location)
{
	g_return_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file));
	g_object_set(G_OBJECT(bookmark_file), "location", location, NULL);
}


void
kz_bookmark_file_set_file_type (KzBookmarkFile *bookmark_file,
			        const gchar *file_type)
{
	g_return_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file));
	g_object_set(G_OBJECT(bookmark_file), "file-type", file_type, NULL);
}


void
kz_bookmark_file_set_interval (KzBookmarkFile *bookmark_file, guint interval)
{
	g_return_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file));
	g_object_set(G_OBJECT(bookmark_file), "interval", interval, NULL);
}

void
kz_bookmark_file_set_xmlrpc (KzBookmarkFile *bookmark_file,
		             const gchar *xmlrpc)
{
	g_return_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file));
	g_object_set(G_OBJECT(bookmark_file), "xmlrpc", xmlrpc, NULL);
}

void
kz_bookmark_file_set_xmlrpc_user (KzBookmarkFile *bookmark_file,
		                  const gchar *user)
{
	g_return_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file));
	g_object_set(G_OBJECT(bookmark_file), "xmlrpc-user", user, NULL);
}

void
kz_bookmark_file_set_xmlrpc_pass (KzBookmarkFile *bookmark_file,
		                  const gchar *pass)
{
	g_return_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file));
	g_object_set(G_OBJECT(bookmark_file), "xmlrpc-pass", pass, NULL);
}


static void
kz_bookmark_file_set_editable (KzBookmarkFile *bookmark_file, gboolean editable)
{
	g_return_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file));
	g_object_set(G_OBJECT(bookmark_file), "editable", editable, NULL);
}


/*****************************************************************************
 *                                                                           *
 *                        load/save boomark file                            *
 *                                                                           *
 *****************************************************************************/
gboolean
kz_bookmark_file_load_start (KzBookmarkFile *bookmark_file)
{
	KzIO *io;
	const gchar *uri;
	guint id, last_mod;
	KzBookmarkFileState state;

	g_return_val_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file), FALSE);

	uri = kz_bookmark_file_get_location(KZ_BOOKMARK_FILE(bookmark_file));
	if (!uri) return FALSE;

	/* preventing to insert children bookmark_file twice, but silly! */
	state = kz_bookmark_file_get_state(bookmark_file);
	if (state == KZ_BOOKMARK_FILE_STATE_LOADING)
		return TRUE;
	
	/* check loading state */
	if (kz_bookmark_file_is_loading_all_children(KZ_BOOKMARK(bookmark_file)))
		return TRUE;
	kz_bookmark_file_set_state(bookmark_file, KZ_BOOKMARK_FILE_STATE_LOADING);

	last_mod = kz_bookmark_get_last_modified(KZ_BOOKMARK(bookmark_file));
	g_object_set(bookmark_file, "previous-last-modified", last_mod, NULL);

	g_signal_emit(bookmark_file,
		      kz_bookmark_file_signals[LOAD_START_SIGNAL],
		      0);

	kz_bookmark_remove_all(KZ_BOOKMARK(bookmark_file));

	io = kz_io_new(uri);
	id = g_signal_connect(io, "io_completed",
			      G_CALLBACK(cb_io_load_complete),
			      bookmark_file);

	g_object_set_qdata(G_OBJECT(bookmark_file), io_quark, io);
	g_object_set_qdata(G_OBJECT(bookmark_file), io_signal_id_quark,
			   GUINT_TO_POINTER(id));

	kz_io_load_to_buffer(io);

	return TRUE;
}


void
kz_bookmark_file_load_stop (KzBookmarkFile *bookmark_file)
{
	g_warning("kz_bookmark_file_load_stop() is not implemented yet.");
}


gboolean
kz_bookmark_file_save_start (KzBookmarkFile *bookmark_file)
{
	g_warning("kz_bookmark_file_save_start() is not implemented yet.");
	return FALSE;
}


void
kz_bookmark_file_save_stop (KzBookmarkFile *bookmark_file)
{
	g_warning("kz_bookmark_file_save_stop() is not implemented yet.");
}

#warning! FIXME! this is available only for local bookmark_file.
void
kz_bookmark_file_load (KzBookmarkFile *bookmark_file)
{
	const gchar *file;
	gchar *str = NULL;
	gsize length;

	g_return_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file));

	file = kz_bookmark_file_get_location(bookmark_file);

	if (file && *file)
	{
		if (g_file_get_contents(file, &str, &length, NULL))
		{
			KzBookmarkFileType *type;
			type = kz_bookmark_file_detect_file_type(bookmark_file, str);
			if (!type)
				/* use default? */;
			if (type && type->from_string)
			{
				if (!kz_bookmark_file_get_file_type(bookmark_file))
					kz_bookmark_file_set_file_type(bookmark_file, type->file_type);
				type->from_string(bookmark_file, str, length, NULL);
			}
			g_free(str);
		}
	}
}


void
kz_bookmark_file_save (KzBookmarkFile *bookmark_file)
{
	KzBookmarkFileType *type;
	KzIO *io;
	const gchar *file;
	gchar *str;

	g_return_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file));

	type = kz_bookmark_file_detect_file_type(bookmark_file, NULL);
	if (!type || !type->to_string) return;

	file = kz_bookmark_file_get_location(bookmark_file);
	str = type->to_string(bookmark_file);

	if (file && *file && str && *str)
	{
		io = kz_io_new(file);
		kz_io_write(io, str);
		g_object_unref(io);
		g_free(str);
	}
}


static gboolean
idle_load_complete (gpointer data)
{
	KzIO *io = data;

	g_object_unref(G_OBJECT(io));

	return FALSE;
}


static void
cb_io_load_complete (KzIO *io, GError *error, KzBookmarkFile *bookmark_file)
{	
	KzBookmarkFileType *type;
	const gchar *buf;
	guint size;

	g_return_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file));

	if (error) goto ERROR;

	buf  = kz_io_get_buffer(io);
	size = kz_io_get_loaded_size(io);

	if (!buf || size == 0) goto ERROR;

	type = kz_bookmark_file_detect_file_type(bookmark_file, (gchar *)buf);
	if (!type)
		/* use default? */;
	if (type && type->from_string)
	{
		if (!kz_bookmark_file_get_file_type(bookmark_file))
			kz_bookmark_file_set_file_type(bookmark_file, type->file_type);
		type->from_string(bookmark_file, buf, size, NULL);
	}

ERROR:
	g_signal_handlers_disconnect_by_func
		(io,
		 G_CALLBACK(cb_io_load_complete),
		 bookmark_file);
	kz_bookmark_file_set_state(bookmark_file, KZ_BOOKMARK_FILE_STATE_NORMAL);
	g_object_set_qdata(G_OBJECT(bookmark_file), io_quark, NULL);
	g_object_set_qdata(G_OBJECT(bookmark_file), io_signal_id_quark,
			   GPOINTER_TO_UINT(0));

	if (error)
	{
		g_signal_emit(bookmark_file,
			      kz_bookmark_file_signals[ERROR_SIGNAL],
			      0,
			      "Load failed");
	}
	else 
	{
		g_signal_emit(bookmark_file,
			      kz_bookmark_file_signals[LOAD_COMPLETED_SIGNAL],
			      0);
	}
	g_idle_add(idle_load_complete, io);
}


static KzBookmarkFileType *
kz_bookmark_file_detect_file_type (KzBookmarkFile *bookmark_file, gchar *buf)
{
	GList *node;
	const gchar *type_str;

	type_str = kz_bookmark_file_get_file_type(bookmark_file);
	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_file, buf))
			{
				return type;
			}
		}
	}

	return NULL;
}


static void
kz_bookmark_file_set_state (KzBookmarkFile *bookmark_file, KzBookmarkFileState state)
{
	g_return_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file));

	if (state == KZ_BOOKMARK_FILE_STATE_LOADING)
		g_object_set_qdata(G_OBJECT(bookmark_file), state_quark,
				   GINT_TO_POINTER(state));
	else
		g_object_set_qdata(G_OBJECT(bookmark_file), state_quark,
				   GINT_TO_POINTER(KZ_BOOKMARK_FILE_STATE_NORMAL));
}


KzBookmarkFileState
kz_bookmark_file_get_state (KzBookmarkFile *bookmark_file)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file),
			     KZ_BOOKMARK_FILE_STATE_NORMAL);

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

static gboolean
kz_bookmark_file_is_loading_all_children (KzBookmark *bookmark)
{
	gboolean ret = FALSE;
	GList *children, *node, *next;
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);
	
	/* check loading state */
	children = kz_bookmark_get_children(bookmark);

	node = children;
	while (node)
	{
		KzBookmark *child = node->data;
		if (kz_bookmark_has_children(child))
		{
			ret = kz_bookmark_file_is_loading_all_children(child);
			if (ret) break;
		}
		next = g_list_next(node);
		if (KZ_IS_BOOKMARK_FILE(child))
		{
			KzBookmarkFileState state;
			state = kz_bookmark_file_get_state(KZ_BOOKMARK_FILE(child));
			ret = (state == KZ_BOOKMARK_FILE_STATE_LOADING) ? TRUE : FALSE;
			if (ret) break;
		}
		node = next;
	}
	g_list_free(children);

	return ret;
}


gboolean
kz_bookmark_file_has_xmlrpc (KzBookmarkFile *bookmark_file)
{
	return (kz_bookmark_file_get_xmlrpc(bookmark_file) != NULL);
}


static void
cb_xml_rpc_completed(KzXMLRPC *xmlrpc, GError *error, gpointer data)
{
	const GList *results;
	KzBookmark *file;

	g_signal_handlers_disconnect_by_func(xmlrpc,
					     G_CALLBACK(cb_xml_rpc_completed),
					     data);
	results = kz_xml_rpc_get_results(xmlrpc);
	if (results)
	{
		const gchar *id;
		id = (const gchar*)g_list_nth_data((GList*)results, 0);
		if (data && KZ_IS_BOOKMARK(data))
		{
			kz_bookmark_set_id(KZ_BOOKMARK(data), id);
			file = kz_bookmark_get_parent_file (KZ_BOOKMARK(data));
			/* fake signal */
			g_signal_emit(file,
				      kz_bookmark_file_signals[LOAD_COMPLETED_SIGNAL],
				      0);
			kz_bookmark_file_set_state(KZ_BOOKMARK_FILE(file),
						   KZ_BOOKMARK_FILE_STATE_NORMAL);
		}
	}
	g_object_unref(xmlrpc);
}


void
kz_bookmark_file_xmlrpc_insert (KzBookmarkFile *file, 
				KzBookmark *folder,
				KzBookmark *sibling,
				KzBookmark *child)
{
	KzXMLRPC *xmlrpc;
	const gchar *xmlrpc_uri;
	const gchar *title, *link, *desc;
	const gchar *folder_id = NULL, *sibling_id = NULL;
	const gchar *type;

	xmlrpc_uri = kz_bookmark_file_get_xmlrpc(file);
	if (!xmlrpc_uri) return;

	folder_id = kz_bookmark_get_id(folder);
	if (!folder_id) folder_id = "0";
	if (sibling)
		sibling_id = kz_bookmark_get_id(sibling);
	if (!sibling_id) sibling_id = "0";

	if (kz_bookmark_is_separator(child))
		type = "separator";
	else if (kz_bookmark_is_folder(child))
		type = "folder";
	else 
		type = "bookmark";

	title = kz_bookmark_get_title(child);
	link  = kz_bookmark_get_link(child);
	desc  = kz_bookmark_get_description(child);

	xmlrpc = kz_xml_rpc_new(xmlrpc_uri);
	g_signal_connect(xmlrpc, "xml_rpc_completed", 
			 G_CALLBACK(cb_xml_rpc_completed), child);
	kz_xml_rpc_call(xmlrpc, "bookmark.insert",
			kz_bookmark_file_get_location(file), 
			"user", "pass",
			folder_id, sibling_id,
			type,
			"title", title, 
			"link", link, 
			"desc", desc,
			NULL);
	/* fake signal */
	kz_bookmark_file_set_state(file, KZ_BOOKMARK_FILE_STATE_LOADING);
	g_signal_emit(file,
		      kz_bookmark_file_signals[LOAD_START_SIGNAL],
		      0);
}


void
kz_bookmark_file_xmlrpc_remove (KzBookmarkFile *file,
				KzBookmark *child)
{
	KzXMLRPC *xmlrpc;
	const gchar *xmlrpc_uri;
	const gchar *id = NULL;

	xmlrpc_uri = kz_bookmark_file_get_xmlrpc(file);
	if (!xmlrpc_uri) return;

	id = kz_bookmark_get_id(child);

	xmlrpc = kz_xml_rpc_new(xmlrpc_uri);
	g_signal_connect(xmlrpc, "xml_rpc_completed", 
			 G_CALLBACK(cb_xml_rpc_completed), NULL);
	kz_xml_rpc_call(xmlrpc, "bookmark.remove",
			kz_bookmark_file_get_location(file), 
			"user", "pass",
			id,
			NULL);
}


void
kz_bookmark_file_xmlrpc_move (KzBookmarkFile *file, 
			      KzBookmark *folder,
			      KzBookmark *sibling,
			      KzBookmark *child)
{
	KzXMLRPC *xmlrpc;
	const gchar *xmlrpc_uri;
	const gchar *id = NULL, *folder_id = NULL, *sibling_id = NULL;

	xmlrpc_uri = kz_bookmark_file_get_xmlrpc(file);
	if (!xmlrpc_uri) return;

	id = kz_bookmark_get_id(child);
	if (!id) return;

	folder_id = kz_bookmark_get_id(folder);
	if (!folder_id) folder_id = "0";
	if (sibling)
		sibling_id = kz_bookmark_get_id(sibling);
	if (!sibling_id) sibling_id = "0";

	xmlrpc = kz_xml_rpc_new(xmlrpc_uri);
	g_signal_connect(xmlrpc, "xml_rpc_completed", 
			 G_CALLBACK(cb_xml_rpc_completed), NULL);
	kz_xml_rpc_call(xmlrpc, "bookmark.move",
			kz_bookmark_file_get_location(file), 
			"user", "pass",
			id,
			folder_id, sibling_id,
			NULL);
}

	
static guint
kz_bookmark_file_get_previous_last_modified (KzBookmarkFile *bookmark_file)
{
	g_return_val_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file), 0);

	return GPOINTER_TO_UINT(g_object_get_qdata(G_OBJECT(bookmark_file),
						   p_last_modified_quark));
}

gboolean
kz_bookmark_file_is_update (KzBookmarkFile *bookmark_file)
{
	guint last_time, time;
	
	g_return_val_if_fail(KZ_IS_BOOKMARK_FILE(bookmark_file), FALSE);

	last_time = kz_bookmark_file_get_previous_last_modified(bookmark_file);
	time = kz_bookmark_get_last_modified(KZ_BOOKMARK(bookmark_file));

	return last_time < time ? TRUE : FALSE;
}

