/*
 * Evolution calendar - Data model for ETable
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 *
 * Authors:
 *		Rodrigo Moya <rodrigo@ximian.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <glib/gi18n.h>
#include <libedataserver/e-flag.h>
#include <libedataserver/e-time-utils.h>
#include <libecal/e-cal-client-view.h>
#include <libecal/e-cal-time-util.h>
#include "comp-util.h"
#include "e-cal-model.h"
#include "itip-utils.h"
#include "misc.h"
#include "e-util/e-util.h"
#include "e-util/e-account-utils.h"
#include "e-util/e-extensible.h"
#include "e-util/e-util-enumtypes.h"

typedef struct {
	ECalClient *client;
	ECalClientView *view;

	gboolean do_query;
	GCancellable *cancellable;
} ECalModelClient;

struct _ECalModelPrivate {
	/* The list of clients we are managing. Each element is of type ECalModelClient */
	GList *clients;

	/* The default client in the list */
	ECalClient *default_client;

	/* Array for storing the objects. Each element is of type ECalModelComponent */
	GPtrArray *objects;

	icalcomponent_kind kind;
	ECalModelFlags flags;
	icaltimezone *zone;

	/* The time range to display */
	time_t start;
	time_t end;

	/* The search regular expression */
	gchar *search_sexp;

	/* The full regular expression, including time range */
	gchar *full_sexp;

	/* The default category */
	gchar *default_category;

	/* Addresses for determining icons */
	EAccountList *accounts;

	/* Whether we display dates in 24-hour format. */
        gboolean use_24_hour_format;

	/* Whether to compress weekends into one cell. */
	gboolean compress_weekend;

	/* First day of the week: 0 (Monday) to 6 (Sunday) */
	gint week_start_day;

	/* Work day timespan */
	gint work_day_start_hour;
	gint work_day_start_minute;
	gint work_day_end_hour;
	gint work_day_end_minute;

	/* callback, to retrieve start time for newly added rows by click-to-add */
	ECalModelDefaultTimeFunc get_default_time;
	gpointer get_default_time_user_data;

	/* Default reminder for events */
	gboolean use_default_reminder;
	gint default_reminder_interval;
	EDurationType default_reminder_units;

	/* Ask user to confirm before deleting components. */
	gboolean confirm_delete;

	gboolean in_added;
	gboolean in_modified;
	gboolean in_removed;

	GSList *notify_added;
	GSList *notify_modified;
	GSList *notify_removed;

	GMutex *notify_lock;

	GCancellable *loading_clients;
};

static gint ecm_column_count (ETableModel *etm);
static gint ecm_row_count (ETableModel *etm);
static gpointer ecm_value_at (ETableModel *etm, gint col, gint row);
static void ecm_set_value_at (ETableModel *etm, gint col, gint row, gconstpointer value);
static gboolean ecm_is_cell_editable (ETableModel *etm, gint col, gint row);
static void ecm_append_row (ETableModel *etm, ETableModel *source, gint row);
static gpointer ecm_duplicate_value (ETableModel *etm, gint col, gconstpointer value);
static void ecm_free_value (ETableModel *etm, gint col, gpointer value);
static gpointer ecm_initialize_value (ETableModel *etm, gint col);
static gboolean ecm_value_is_empty (ETableModel *etm, gint col, gconstpointer value);
static gchar *ecm_value_to_string (ETableModel *etm, gint col, gconstpointer value);

static const gchar *ecm_get_color_for_component (ECalModel *model, ECalModelComponent *comp_data);

static ECalModelClient *add_new_client (ECalModel *model, ECalClient *client, gboolean do_query);
static ECalModelClient *find_client_data (ECalModel *model, ECalClient *client);
static void remove_client_objects (ECalModel *model, ECalModelClient *client_data);
static void remove_client (ECalModel *model, ECalModelClient *client_data);

enum {
	PROP_0,
	PROP_COMPRESS_WEEKEND,
	PROP_CONFIRM_DELETE,
	PROP_DEFAULT_CLIENT,
	PROP_DEFAULT_REMINDER_INTERVAL,
	PROP_DEFAULT_REMINDER_UNITS,
	PROP_TIMEZONE,
	PROP_USE_24_HOUR_FORMAT,
	PROP_USE_DEFAULT_REMINDER,
	PROP_WEEK_START_DAY,
	PROP_WORK_DAY_END_HOUR,
	PROP_WORK_DAY_END_MINUTE,
	PROP_WORK_DAY_START_HOUR,
	PROP_WORK_DAY_START_MINUTE
};

enum {
	TIME_RANGE_CHANGED,
	ROW_APPENDED,
	COMPS_DELETED,
	CAL_VIEW_PROGRESS,
	CAL_VIEW_COMPLETE,
	STATUS_MESSAGE,
	TIMEZONE_CHANGED,
	LAST_SIGNAL
};

static gpointer parent_class;
static guint signals[LAST_SIGNAL];

G_DEFINE_TYPE_WITH_CODE (
	ECalModel, e_cal_model, E_TYPE_TABLE_MODEL,
	G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))

static void
cal_model_set_property (GObject *object,
                        guint property_id,
                        const GValue *value,
                        GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_COMPRESS_WEEKEND:
			e_cal_model_set_compress_weekend (
				E_CAL_MODEL (object),
				g_value_get_boolean (value));
			return;

		case PROP_CONFIRM_DELETE:
			e_cal_model_set_confirm_delete (
				E_CAL_MODEL (object),
				g_value_get_boolean (value));
			return;

		case PROP_DEFAULT_CLIENT:
			e_cal_model_set_default_client (
				E_CAL_MODEL (object),
				g_value_get_object (value));
			return;

		case PROP_DEFAULT_REMINDER_INTERVAL:
			e_cal_model_set_default_reminder_interval (
				E_CAL_MODEL (object),
				g_value_get_int (value));
			return;

		case PROP_DEFAULT_REMINDER_UNITS:
			e_cal_model_set_default_reminder_units (
				E_CAL_MODEL (object),
				g_value_get_enum (value));
			return;

		case PROP_TIMEZONE:
			e_cal_model_set_timezone (
				E_CAL_MODEL (object),
				g_value_get_pointer (value));
			return;

		case PROP_USE_24_HOUR_FORMAT:
			e_cal_model_set_use_24_hour_format (
				E_CAL_MODEL (object),
				g_value_get_boolean (value));
			return;

		case PROP_USE_DEFAULT_REMINDER:
			e_cal_model_set_use_default_reminder (
				E_CAL_MODEL (object),
				g_value_get_boolean (value));
			return;

		case PROP_WEEK_START_DAY:
			e_cal_model_set_week_start_day (
				E_CAL_MODEL (object),
				g_value_get_int (value));
			return;

		case PROP_WORK_DAY_END_HOUR:
			e_cal_model_set_work_day_end_hour (
				E_CAL_MODEL (object),
				g_value_get_int (value));
			return;

		case PROP_WORK_DAY_END_MINUTE:
			e_cal_model_set_work_day_end_minute (
				E_CAL_MODEL (object),
				g_value_get_int (value));
			return;

		case PROP_WORK_DAY_START_HOUR:
			e_cal_model_set_work_day_start_hour (
				E_CAL_MODEL (object),
				g_value_get_int (value));
			return;

		case PROP_WORK_DAY_START_MINUTE:
			e_cal_model_set_work_day_start_minute (
				E_CAL_MODEL (object),
				g_value_get_int (value));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
cal_model_get_property (GObject *object,
                        guint property_id,
                        GValue *value,
                        GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_COMPRESS_WEEKEND:
			g_value_set_boolean (
				value,
				e_cal_model_get_compress_weekend (
				E_CAL_MODEL (object)));
			return;

		case PROP_CONFIRM_DELETE:
			g_value_set_boolean (
				value,
				e_cal_model_get_confirm_delete (
				E_CAL_MODEL (object)));
			return;

		case PROP_DEFAULT_CLIENT:
			g_value_set_object (
				value,
				e_cal_model_get_default_client (
				E_CAL_MODEL (object)));
			return;

		case PROP_DEFAULT_REMINDER_INTERVAL:
			g_value_set_int (
				value,
				e_cal_model_get_default_reminder_interval (
				E_CAL_MODEL (object)));
			return;

		case PROP_DEFAULT_REMINDER_UNITS:
			g_value_set_enum (
				value,
				e_cal_model_get_default_reminder_units (
				E_CAL_MODEL (object)));
			return;

		case PROP_TIMEZONE:
			g_value_set_pointer (
				value,
				e_cal_model_get_timezone (
				E_CAL_MODEL (object)));
			return;

		case PROP_USE_24_HOUR_FORMAT:
			g_value_set_boolean (
				value,
				e_cal_model_get_use_24_hour_format (
				E_CAL_MODEL (object)));
			return;

		case PROP_USE_DEFAULT_REMINDER:
			g_value_set_boolean (
				value,
				e_cal_model_get_use_default_reminder (
				E_CAL_MODEL (object)));
			return;

		case PROP_WEEK_START_DAY:
			g_value_set_int (
				value,
				e_cal_model_get_week_start_day (
				E_CAL_MODEL (object)));
			return;

		case PROP_WORK_DAY_END_HOUR:
			g_value_set_int (
				value,
				e_cal_model_get_work_day_end_hour (
				E_CAL_MODEL (object)));
			return;

		case PROP_WORK_DAY_END_MINUTE:
			g_value_set_int (
				value,
				e_cal_model_get_work_day_end_minute (
				E_CAL_MODEL (object)));
			return;

		case PROP_WORK_DAY_START_HOUR:
			g_value_set_int (
				value,
				e_cal_model_get_work_day_start_hour (
				E_CAL_MODEL (object)));
			return;

		case PROP_WORK_DAY_START_MINUTE:
			g_value_set_int (
				value,
				e_cal_model_get_work_day_start_minute (
				E_CAL_MODEL (object)));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
cal_model_constructed (GObject *object)
{
	e_extensible_load_extensions (E_EXTENSIBLE (object));

	/* Chain up to parent's constructed() method. */
	G_OBJECT_CLASS (parent_class)->constructed (object);
}

static void
cal_model_dispose (GObject *object)
{
	ECalModelPrivate *priv;

	priv = E_CAL_MODEL (object)->priv;

	if (priv->loading_clients) {
		g_cancellable_cancel (priv->loading_clients);
		g_object_unref (priv->loading_clients);
		priv->loading_clients = NULL;
	}

	if (priv->clients) {
		while (priv->clients != NULL) {
			ECalModelClient *client_data = (ECalModelClient *) priv->clients->data;

			g_signal_handlers_disconnect_matched (client_data->client, G_SIGNAL_MATCH_DATA,
							      0, 0, NULL, NULL, object);
			if (client_data->view)
				g_signal_handlers_disconnect_matched (client_data->view, G_SIGNAL_MATCH_DATA,
								      0, 0, NULL, NULL, object);

			priv->clients = g_list_remove (priv->clients, client_data);

			g_object_unref (client_data->client);
			if (client_data->cancellable) {
				g_cancellable_cancel (client_data->cancellable);
				g_object_unref (client_data->cancellable);
			}
			if (client_data->view)
				g_object_unref (client_data->view);
			g_free (client_data);
		}

		priv->clients = NULL;
		priv->default_client = NULL;
	}

	/* Chain up to parent's dispose() method. */
	G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void
cal_model_finalize (GObject *object)
{
	ECalModelPrivate *priv;
	gint ii;

	priv = E_CAL_MODEL (object)->priv;

	g_free (priv->search_sexp);
	g_free (priv->full_sexp);

	g_free (priv->default_category);

	for (ii = 0; ii < priv->objects->len; ii++) {
		ECalModelComponent *comp_data;

		comp_data = g_ptr_array_index (priv->objects, ii);
		if (comp_data == NULL) {
			g_warning ("comp_data is null\n");
			continue;
		}
		g_object_unref (comp_data);
	}
	g_ptr_array_free (priv->objects, FALSE);

	g_mutex_free (priv->notify_lock);

	/* Chain up to parent's finalize() method. */
	G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
e_cal_model_class_init (ECalModelClass *class)
{
	GObjectClass *object_class;
	ETableModelClass *etm_class;

	parent_class = g_type_class_peek_parent (class);
	g_type_class_add_private (class, sizeof (ECalModelPrivate));

	object_class = G_OBJECT_CLASS (class);
	object_class->set_property = cal_model_set_property;
	object_class->get_property = cal_model_get_property;
	object_class->constructed = cal_model_constructed;
	object_class->dispose = cal_model_dispose;
	object_class->finalize = cal_model_finalize;

	etm_class = E_TABLE_MODEL_CLASS (class);
	etm_class->column_count = ecm_column_count;
	etm_class->row_count = ecm_row_count;
	etm_class->value_at = ecm_value_at;
	etm_class->set_value_at = ecm_set_value_at;
	etm_class->is_cell_editable = ecm_is_cell_editable;
	etm_class->append_row = ecm_append_row;
	etm_class->duplicate_value = ecm_duplicate_value;
	etm_class->free_value = ecm_free_value;
	etm_class->initialize_value = ecm_initialize_value;
	etm_class->value_is_empty = ecm_value_is_empty;
	etm_class->value_to_string = ecm_value_to_string;

	class->get_color_for_component = ecm_get_color_for_component;
	class->fill_component_from_model = NULL;

	g_object_class_install_property (
		object_class,
		PROP_COMPRESS_WEEKEND,
		g_param_spec_boolean (
			"compress-weekend",
			"Compress Weekend",
			NULL,
			FALSE,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_CONFIRM_DELETE,
		g_param_spec_boolean (
			"confirm-delete",
			"Confirm Delete",
			NULL,
			TRUE,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_DEFAULT_CLIENT,
		g_param_spec_object (
			"default-client",
			"Default ECalClient",
			NULL,
			E_TYPE_CAL_CLIENT,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_DEFAULT_REMINDER_INTERVAL,
		g_param_spec_int (
			"default-reminder-interval",
			"Default Reminder Interval",
			NULL,
			G_MININT,
			G_MAXINT,
			0,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_DEFAULT_REMINDER_UNITS,
		g_param_spec_enum (
			"default-reminder-units",
			"Default Reminder Units",
			NULL,
			E_TYPE_DURATION_TYPE,
			E_DURATION_MINUTES,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_TIMEZONE,
		g_param_spec_pointer (
			"timezone",
			"Time Zone",
			NULL,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_USE_24_HOUR_FORMAT,
		g_param_spec_boolean (
			"use-24-hour-format",
			"Use 24-Hour Format",
			NULL,
			TRUE,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_USE_DEFAULT_REMINDER,
		g_param_spec_boolean (
			"use-default-reminder",
			"Use Default Reminder",
			NULL,
			FALSE,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_WEEK_START_DAY,
		g_param_spec_int (
			"week-start-day",
			"Week Start Day",
			NULL,
			0,  /* Monday */
			6,  /* Sunday */
			0,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_WORK_DAY_END_HOUR,
		g_param_spec_int (
			"work-day-end-hour",
			"Work Day End Hour",
			NULL,
			0,
			23,
			0,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_WORK_DAY_END_MINUTE,
		g_param_spec_int (
			"work-day-end-minute",
			"Work Day End Minute",
			NULL,
			0,
			59,
			0,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_WORK_DAY_START_HOUR,
		g_param_spec_int (
			"work-day-start-hour",
			"Work Day Start Hour",
			NULL,
			0,
			23,
			0,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_WORK_DAY_START_MINUTE,
		g_param_spec_int (
			"work-day-start-minute",
			"Work Day Start Minute",
			NULL,
			0,
			59,
			0,
			G_PARAM_READWRITE));

	signals[TIME_RANGE_CHANGED] =
		g_signal_new ("time_range_changed",
			      G_TYPE_FROM_CLASS (class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ECalModelClass, time_range_changed),
			      NULL, NULL,
			      e_marshal_VOID__LONG_LONG,
			      G_TYPE_NONE, 2, G_TYPE_LONG, G_TYPE_LONG);

	signals[ROW_APPENDED] =
		g_signal_new ("row_appended",
			      G_TYPE_FROM_CLASS (class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ECalModelClass, row_appended),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__VOID,
			      G_TYPE_NONE, 0);

	signals[COMPS_DELETED] =
		g_signal_new ("comps_deleted",
			      G_TYPE_FROM_CLASS (class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ECalModelClass, comps_deleted),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__POINTER,
			      G_TYPE_NONE, 1, G_TYPE_POINTER);

	signals[CAL_VIEW_PROGRESS] =
		g_signal_new ("cal_view_progress",
			      G_TYPE_FROM_CLASS (class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ECalModelClass, cal_view_progress),
			      NULL, NULL,
			      e_marshal_VOID__STRING_INT_INT,
			      G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);

	signals[CAL_VIEW_COMPLETE] =
		g_signal_new ("cal_view_complete",
			      G_TYPE_FROM_CLASS (class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ECalModelClass, cal_view_complete),
			      NULL, NULL,
			      e_marshal_VOID__BOXED_INT,
			      G_TYPE_NONE, 2, G_TYPE_ERROR, G_TYPE_INT);

	signals[STATUS_MESSAGE] = g_signal_new (
		"status-message",
		G_TYPE_FROM_CLASS (class),
		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
		G_STRUCT_OFFSET (ECalModelClass, status_message),
		NULL, NULL,
		e_marshal_VOID__STRING_DOUBLE,
		G_TYPE_NONE, 2,
		G_TYPE_STRING, G_TYPE_DOUBLE);
	signals[TIMEZONE_CHANGED] = g_signal_new (
		"timezone-changed",
		G_TYPE_FROM_CLASS (class),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (ECalModelClass, timezone_changed),
		NULL, NULL,
		e_marshal_VOID__POINTER_POINTER,
		G_TYPE_NONE, 2,
		G_TYPE_POINTER,
		G_TYPE_POINTER);
}

static void
e_cal_model_init (ECalModel *model)
{
	model->priv = G_TYPE_INSTANCE_GET_PRIVATE (
		model, E_TYPE_CAL_MODEL, ECalModelPrivate);

	/* match none by default */
	model->priv->start = -1;
	model->priv->end = -1;
	model->priv->search_sexp = NULL;
	model->priv->full_sexp = g_strdup ("#f");

	model->priv->objects = g_ptr_array_new ();
	model->priv->kind = ICAL_NO_COMPONENT;
	model->priv->flags = 0;

	model->priv->accounts = e_get_account_list ();

	model->priv->use_24_hour_format = TRUE;

	model->priv->in_added = FALSE;
	model->priv->in_modified = FALSE;
	model->priv->in_removed = FALSE;
	model->priv->notify_added = NULL;
	model->priv->notify_modified = NULL;
	model->priv->notify_removed = NULL;
	model->priv->notify_lock = g_mutex_new ();

	model->priv->loading_clients = g_cancellable_new ();
}

/* ETableModel methods */

static gint
ecm_column_count (ETableModel *etm)
{
	return E_CAL_MODEL_FIELD_LAST;
}

static gint
ecm_row_count (ETableModel *etm)
{
	ECalModelPrivate *priv;
	ECalModel *model = (ECalModel *) etm;

	g_return_val_if_fail (E_IS_CAL_MODEL (model), -1);

	priv = model->priv;

	return priv->objects->len;
}

static gpointer
get_categories (ECalModelComponent *comp_data)
{
	icalproperty *prop;

	prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_CATEGORIES_PROPERTY);
	if (prop)
		return (gpointer) icalproperty_get_categories (prop);

	return (gpointer) "";
}

static gchar *
get_classification (ECalModelComponent *comp_data)
{
	icalproperty *prop;
	icalproperty_class class;

	prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_CLASS_PROPERTY);

	if (!prop)
		return _("Public");

	class = icalproperty_get_class (prop);

	switch (class)
	{
	case ICAL_CLASS_PUBLIC:
		return _("Public");
	case ICAL_CLASS_PRIVATE:
		return _("Private");
	case ICAL_CLASS_CONFIDENTIAL:
		return _("Confidential");
	default:
		return _("Unknown");
	}
}

static const gchar *
get_color (ECalModel *model,
           ECalModelComponent *comp_data)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);

	return e_cal_model_get_color_for_component (model, comp_data);
}

static gpointer
get_description (ECalModelComponent *comp_data)
{
	icalproperty *prop;
	static GString *str = NULL;

	if (str) {
		g_string_free (str, TRUE);
		str = NULL;
	}

	prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_DESCRIPTION_PROPERTY);
	if (prop) {
		str = g_string_new (NULL);
		do {
			str = g_string_append (str, icalproperty_get_description (prop));
		} while ((prop = icalcomponent_get_next_property (comp_data->icalcomp, ICAL_DESCRIPTION_PROPERTY)));

		return str->str;
	}

	return (gpointer) "";
}

static ECellDateEditValue *
get_dtstart (ECalModel *model,
             ECalModelComponent *comp_data)
{
	ECalModelPrivate *priv;
	struct icaltimetype tt_start;

	priv = model->priv;

	if (!comp_data->dtstart) {
		icalproperty *prop;
		icaltimezone *zone;
		gboolean got_zone = FALSE;

		prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_DTSTART_PROPERTY);
		if (!prop)
			return NULL;

		tt_start = icalproperty_get_dtstart (prop);

		if (icaltime_get_tzid (tt_start)
		    && e_cal_client_get_timezone_sync (comp_data->client, icaltime_get_tzid (tt_start), &zone, NULL, NULL))
			got_zone = TRUE;

		if (e_cal_model_get_flags (model) & E_CAL_MODEL_FLAGS_EXPAND_RECURRENCES) {
			if (got_zone) {
				tt_start = icaltime_from_timet_with_zone (comp_data->instance_start, tt_start.is_date, zone);
				if (priv->zone)
					icaltimezone_convert_time (&tt_start, zone, priv->zone);
			} else
				if (priv->zone)
					tt_start = icaltime_from_timet_with_zone (comp_data->instance_start, tt_start.is_date, priv->zone);
		}

		if (!icaltime_is_valid_time (tt_start) || icaltime_is_null_time (tt_start))
			return NULL;

		comp_data->dtstart = g_new0 (ECellDateEditValue, 1);
		comp_data->dtstart->tt = tt_start;

		if (got_zone)
			comp_data->dtstart->zone = zone;
		else
			comp_data->dtstart->zone = NULL;
	}

	return comp_data->dtstart;
}

static ECellDateEditValue *
get_datetime_from_utc (ECalModel *model,
                       ECalModelComponent *comp_data,
                       icalproperty_kind propkind,
                       struct icaltimetype (*get_value) (const icalproperty *prop),
                                                         ECellDateEditValue **buffer)
{
	ECalModelPrivate *priv;
	struct icaltimetype tt_value;
	icalproperty *prop;
	ECellDateEditValue *res;

	g_return_val_if_fail (buffer!= NULL, NULL);

	if (*buffer)
		return *buffer;

	priv = model->priv;

	prop = icalcomponent_get_first_property (comp_data->icalcomp, propkind);
	if (!prop)
		return NULL;

	tt_value = get_value (prop);

	/* these are always in UTC, thus convert to default zone, if any and done */
	if (priv->zone)
		icaltimezone_convert_time (&tt_value, icaltimezone_get_utc_timezone (), priv->zone);

	if (!icaltime_is_valid_time (tt_value) || icaltime_is_null_time (tt_value))
		return NULL;

	res = g_new0 (ECellDateEditValue, 1);
	res->tt = tt_value;
	res->zone = NULL;

	*buffer = res;

	return res;
}

static gpointer
get_summary (ECalModelComponent *comp_data)
{
	icalproperty *prop;

	prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_SUMMARY_PROPERTY);
	if (prop)
		return (gpointer) icalproperty_get_summary (prop);

	return (gpointer) "";
}

static gchar *
get_uid (ECalModelComponent *comp_data)
{
	return (gchar *) icalcomponent_get_uid (comp_data->icalcomp);
}

static gpointer
ecm_value_at (ETableModel *etm,
              gint col,
              gint row)
{
	ECalModelPrivate *priv;
	ECalModelComponent *comp_data;
	ECalModel *model = (ECalModel *) etm;

	g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);

	priv = model->priv;

	g_return_val_if_fail (col >= 0 && col < E_CAL_MODEL_FIELD_LAST, NULL);
	g_return_val_if_fail (row >= 0 && row < priv->objects->len, NULL);

	comp_data = g_ptr_array_index (priv->objects, row);
	g_return_val_if_fail (comp_data != NULL, NULL);
	g_return_val_if_fail (comp_data->icalcomp != NULL, NULL);

	switch (col) {
	case E_CAL_MODEL_FIELD_CATEGORIES :
		return get_categories (comp_data);
	case E_CAL_MODEL_FIELD_CLASSIFICATION :
		return get_classification (comp_data);
	case E_CAL_MODEL_FIELD_COLOR :
		return (gpointer) get_color (model, comp_data);
	case E_CAL_MODEL_FIELD_COMPONENT :
		return comp_data->icalcomp;
	case E_CAL_MODEL_FIELD_DESCRIPTION :
		return get_description (comp_data);
	case E_CAL_MODEL_FIELD_DTSTART :
		return (gpointer) get_dtstart (model, comp_data);
	case E_CAL_MODEL_FIELD_CREATED :
		return (gpointer) get_datetime_from_utc (model, comp_data, ICAL_CREATED_PROPERTY, icalproperty_get_created, &comp_data->created);
	case E_CAL_MODEL_FIELD_LASTMODIFIED :
		return (gpointer) get_datetime_from_utc (model, comp_data, ICAL_LASTMODIFIED_PROPERTY, icalproperty_get_lastmodified, &comp_data->lastmodified);
	case E_CAL_MODEL_FIELD_HAS_ALARMS :
		return GINT_TO_POINTER ((icalcomponent_get_first_component (comp_data->icalcomp,
									    ICAL_VALARM_COMPONENT) != NULL));
	case E_CAL_MODEL_FIELD_ICON :
	{
		ECalComponent *comp;
		icalcomponent *icalcomp;
		gint retval = 0;

		comp = e_cal_component_new ();
		icalcomp = icalcomponent_new_clone (comp_data->icalcomp);
		if (e_cal_component_set_icalcomponent (comp, icalcomp)) {
			if (e_cal_component_get_vtype (comp) == E_CAL_COMPONENT_JOURNAL) {
				g_object_unref (comp);
				return GINT_TO_POINTER (retval);
			}

			if (e_cal_component_has_recurrences (comp))
				retval = 1;
			else if (itip_organizer_is_user (comp, comp_data->client))
				retval = 3;
			else {
				GSList *attendees = NULL, *sl;

				e_cal_component_get_attendee_list (comp, &attendees);
				for (sl = attendees; sl != NULL; sl = sl->next) {
					ECalComponentAttendee *ca = sl->data;
					const gchar *text;

					text = itip_strip_mailto (ca->value);
					if (itip_address_is_user (text)) {
						if (ca->delto != NULL)
							retval = 3;
						else
							retval = 2;
						break;
					}
				}

				e_cal_component_free_attendee_list (attendees);
			}
		} else
			icalcomponent_free (icalcomp);

		g_object_unref (comp);

		return GINT_TO_POINTER (retval);
	}
	case E_CAL_MODEL_FIELD_SUMMARY :
		return get_summary (comp_data);
	case E_CAL_MODEL_FIELD_UID :
		return get_uid (comp_data);
	}

	return (gpointer) "";
}

static void
set_categories (ECalModelComponent *comp_data,
                const gchar *value)
{
	icalproperty *prop;

	prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_CATEGORIES_PROPERTY);
	if (!value || !(*value)) {
		if (prop) {
			icalcomponent_remove_property (comp_data->icalcomp, prop);
			icalproperty_free (prop);
		}
	} else {
		if (!prop) {
			prop = icalproperty_new_categories (value);
			icalcomponent_add_property (comp_data->icalcomp, prop);
		} else
			icalproperty_set_categories (prop, value);
	}
}

static void
set_classification (ECalModelComponent *comp_data,
                    const gchar *value)
{
	icalproperty *prop;

	prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_CLASS_PROPERTY);
	if (!value || !(*value)) {
		if (prop) {
			icalcomponent_remove_property (comp_data->icalcomp, prop);
			icalproperty_free (prop);
		}
	} else {
	  icalproperty_class ical_class;

	  if (!g_ascii_strcasecmp (value, "PUBLIC"))
	    ical_class = ICAL_CLASS_PUBLIC;
	  else if (!g_ascii_strcasecmp (value, "PRIVATE"))
	    ical_class = ICAL_CLASS_PRIVATE;
	  else if (!g_ascii_strcasecmp (value, "CONFIDENTIAL"))
	    ical_class = ICAL_CLASS_CONFIDENTIAL;
	  else
	    ical_class = ICAL_CLASS_NONE;

		if (!prop) {
			prop = icalproperty_new_class (ical_class);
			icalcomponent_add_property (comp_data->icalcomp, prop);
		} else
			icalproperty_set_class (prop, ical_class);
	}
}

static void
set_description (ECalModelComponent *comp_data,
                 const gchar *value)
{
	icalproperty *prop;

	/* remove old description(s) */
	prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_DESCRIPTION_PROPERTY);
	while (prop) {
		icalproperty *next;

		next = icalcomponent_get_next_property (comp_data->icalcomp, ICAL_DESCRIPTION_PROPERTY);

		icalcomponent_remove_property (comp_data->icalcomp, prop);
		icalproperty_free (prop);

		prop = next;
	}

	/* now add the new description */
	if (!value || !(*value))
		return;

	prop = icalproperty_new_description (value);
	icalcomponent_add_property (comp_data->icalcomp, prop);
}

static void
datetime_to_zone (ECalClient *client,
                  struct icaltimetype *tt,
                  icaltimezone *tt_zone,
                  const gchar *tzid)
{
	icaltimezone *from, *to;
	const gchar *tt_tzid = NULL;

	g_return_if_fail (tt != NULL);

	if (tt_zone)
		tt_tzid = icaltimezone_get_tzid (tt_zone);

	if (tt_tzid == NULL || tzid == NULL ||
	    tt_tzid == tzid || g_str_equal (tt_tzid, tzid))
		return;

	from = tt_zone;
	to = icaltimezone_get_builtin_timezone_from_tzid (tzid);
	if (!to) {
		/* do not check failure here, maybe the zone is not available there */
		e_cal_client_get_timezone_sync (client, tzid, &to, NULL, NULL);
	}

	icaltimezone_convert_time (tt, from, to);
}

/* updates time in a component, and keeps the timezone used in it, if exists */
void
e_cal_model_update_comp_time (ECalModel *model,
                              ECalModelComponent *comp_data,
                              gconstpointer time_value,
                              icalproperty_kind kind,
                              void (*set_func) (icalproperty *prop,
                                                struct icaltimetype v),
                              icalproperty * (*new_func) (struct icaltimetype v))
{
	ECellDateEditValue *dv = (ECellDateEditValue *) time_value;
	icalproperty *prop;
	icalparameter *param;
	struct icaltimetype tt;

	g_return_if_fail (model != NULL);
	g_return_if_fail (comp_data != NULL);
	g_return_if_fail (set_func != NULL);
	g_return_if_fail (new_func != NULL);

	prop = icalcomponent_get_first_property (comp_data->icalcomp, kind);
	if (prop)
		param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);
	else
		param = NULL;

	/* If we are setting the property to NULL (i.e. removing it), then
	 * we remove it if it exists. */
	if (!dv) {
		if (prop) {
			icalcomponent_remove_property (comp_data->icalcomp, prop);
			icalproperty_free (prop);
		}

		return;
	}

	tt = dv->tt;
	datetime_to_zone (comp_data->client, &tt, e_cal_model_get_timezone (model), param ? icalparameter_get_tzid (param) : NULL);

	if (prop) {
		set_func (prop, tt);
	} else {
		prop = new_func (tt);
		icalcomponent_add_property (comp_data->icalcomp, prop);
	}

	if (param) {
		const gchar *tzid = icalparameter_get_tzid (param);

		/* If the TZID is set to "UTC", we don't want to save the TZID. */
		if (tzid && strcmp (tzid, "UTC")) {
			if (param) {
				icalparameter_set_tzid (param, (gchar *) tzid);
			} else {
				param = icalparameter_new_tzid ((gchar *) tzid);
				icalproperty_add_parameter (prop, param);
			}
		} else {
			icalproperty_remove_parameter (prop, ICAL_TZID_PARAMETER);
		}
	}
}

static void
set_dtstart (ECalModel *model,
             ECalModelComponent *comp_data,
             gconstpointer value)
{
	e_cal_model_update_comp_time (
		model, comp_data, value,
		ICAL_DTSTART_PROPERTY,
		icalproperty_set_dtstart,
		icalproperty_new_dtstart);
}

static void
set_summary (ECalModelComponent *comp_data,
             const gchar *value)
{
	icalproperty *prop;

	prop = icalcomponent_get_first_property (
		comp_data->icalcomp, ICAL_SUMMARY_PROPERTY);

	if (string_is_empty (value)) {
		if (prop) {
			icalcomponent_remove_property (comp_data->icalcomp, prop);
			icalproperty_free (prop);
		}
	} else {
		if (prop)
			icalproperty_set_summary (prop, value);
		else {
			prop = icalproperty_new_summary (value);
			icalcomponent_add_property (comp_data->icalcomp, prop);
		}
	}
}

static void
ecm_set_value_at (ETableModel *etm,
                  gint col,
                  gint row,
                  gconstpointer value)
{
	ECalModelPrivate *priv;
	ECalModelComponent *comp_data;
	ECalModel *model = (ECalModel *) etm;
	GError *error = NULL;

	g_return_if_fail (E_IS_CAL_MODEL (model));

	priv = model->priv;

	g_return_if_fail (col >= 0 && col < E_CAL_MODEL_FIELD_LAST);
	g_return_if_fail (row >= 0 && row < priv->objects->len);

	comp_data = g_ptr_array_index (priv->objects, row);
	g_return_if_fail (comp_data != NULL);

	switch (col) {
	case E_CAL_MODEL_FIELD_CATEGORIES :
		set_categories (comp_data, value);
		break;
	case E_CAL_MODEL_FIELD_CLASSIFICATION :
		set_classification (comp_data, value);
		break;
	case E_CAL_MODEL_FIELD_DESCRIPTION :
		set_description (comp_data, value);
		break;
	case E_CAL_MODEL_FIELD_DTSTART :
		set_dtstart (model, comp_data, value);
		break;
	case E_CAL_MODEL_FIELD_SUMMARY :
		set_summary (comp_data, value);
		break;
	}

	/* FIXME ask about mod type */
	if (!e_cal_client_modify_object_sync (comp_data->client, comp_data->icalcomp, CALOBJ_MOD_ALL, NULL, &error)) {
		g_warning (G_STRLOC ": Could not modify the object! %s", error ? error->message : "Unknown error");

		/* FIXME Show error dialog */
		if (error)
			g_error_free (error);
	}
}

/**
 * e_cal_model_test_row_editable
 * @model: an #ECalModel
 * @row: Row of our interest. -1 is editable only when default client is
 * editable.
 *
 * Checks if component at @row is editable or not.  It doesn't check bounds
 * for @row.
 *
 * Returns: Whether @row is editable or not.
 **/
gboolean
e_cal_model_test_row_editable (ECalModel *model,
                               gint row)
{
	gboolean readonly;
	ECalClient *client = NULL;

	if (row != -1) {
		ECalModelComponent *comp_data;

		comp_data = e_cal_model_get_component_at (model, row);

		if (comp_data)
			client = comp_data->client;

	} else {
		client = e_cal_model_get_default_client (model);
	}

	readonly = client == NULL;

	if (!readonly)
		readonly = e_client_is_readonly (E_CLIENT (client));

	return !readonly;
}

static gboolean
ecm_is_cell_editable (ETableModel *etm,
                      gint col,
                      gint row)
{
	ECalModelPrivate *priv;
	ECalModel *model = (ECalModel *) etm;

	g_return_val_if_fail (E_IS_CAL_MODEL (model), FALSE);

	priv = model->priv;

	g_return_val_if_fail (col >= 0 && col <= E_CAL_MODEL_FIELD_LAST, FALSE);
	g_return_val_if_fail (row >= -1 || (row >= 0 && row < priv->objects->len), FALSE);

	if (!e_cal_model_test_row_editable (E_CAL_MODEL (etm), row))
		return FALSE;

	switch (col) {
	case E_CAL_MODEL_FIELD_CATEGORIES :
	case E_CAL_MODEL_FIELD_CLASSIFICATION :
	case E_CAL_MODEL_FIELD_DESCRIPTION :
	case E_CAL_MODEL_FIELD_DTSTART :
	case E_CAL_MODEL_FIELD_SUMMARY :
		return TRUE;
	}

	return FALSE;
}

static void
ecm_append_row (ETableModel *etm,
                ETableModel *source,
                gint row)
{
	ECalModelClass *model_class;
	ECalModelComponent *comp_data;
	ECalModel *model = (ECalModel *) etm;
	gchar *uid = NULL;
	GError *error = NULL;

	g_return_if_fail (E_IS_CAL_MODEL (model));
	g_return_if_fail (E_IS_TABLE_MODEL (source));

	comp_data = g_object_new (E_TYPE_CAL_MODEL_COMPONENT, NULL);

	comp_data->client = e_cal_model_get_default_client (model);
	if (comp_data->client)
		g_object_ref (comp_data->client);

	/* guard against saving before the calendar is open */
	if (!comp_data->client || !e_client_is_opened (E_CLIENT (comp_data->client))) {
		g_object_unref (comp_data);
		return;
	}

	comp_data->icalcomp = e_cal_model_create_component_with_defaults (model, FALSE);

	/* set values for our fields */
	set_categories (comp_data, e_table_model_value_at (source, E_CAL_MODEL_FIELD_CATEGORIES, row));
	set_classification (comp_data, e_table_model_value_at (source, E_CAL_MODEL_FIELD_CLASSIFICATION, row));
	set_description (comp_data, e_table_model_value_at (source, E_CAL_MODEL_FIELD_DESCRIPTION, row));
	set_summary (comp_data, e_table_model_value_at (source, E_CAL_MODEL_FIELD_SUMMARY, row));

	if (e_table_model_value_at (source, E_CAL_MODEL_FIELD_DTSTART, row)) {
		set_dtstart (model, comp_data, e_table_model_value_at (source, E_CAL_MODEL_FIELD_DTSTART, row));
	} else if (model->priv->get_default_time) {
		time_t tt = model->priv->get_default_time (model, model->priv->get_default_time_user_data);

		if (tt > 0) {
			struct icaltimetype itt = icaltime_from_timet_with_zone (tt, FALSE, e_cal_model_get_timezone (model));
			icalproperty *prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_DTSTART_PROPERTY);

			if (prop) {
				icalproperty_set_dtstart (prop, itt);
			} else {
				prop = icalproperty_new_dtstart (itt);
				icalcomponent_add_property (comp_data->icalcomp, prop);
			}
		}
	}

	/* call the class' method for filling the component */
	model_class = (ECalModelClass *) G_OBJECT_GET_CLASS (model);
	if (model_class->fill_component_from_model != NULL) {
		model_class->fill_component_from_model (model, comp_data, source, row);
	}

	if (!e_cal_client_create_object_sync (comp_data->client, comp_data->icalcomp, &uid, NULL, &error)) {
		g_warning (G_STRLOC ": Could not create the object! %s", error ? error->message : "Unknown error");

		/* FIXME: show error dialog */
		if (error)
			g_error_free (error);
	} else {
		if (uid)
			icalcomponent_set_uid (comp_data->icalcomp, uid);

		g_signal_emit (G_OBJECT (model), signals[ROW_APPENDED], 0);
	}

	g_free (uid);
	g_object_unref (comp_data);
}

static gpointer
ecm_duplicate_value (ETableModel *etm,
                     gint col,
                     gconstpointer value)
{
	g_return_val_if_fail (col >= 0 && col < E_CAL_MODEL_FIELD_LAST, NULL);

	switch (col) {
	case E_CAL_MODEL_FIELD_CATEGORIES :
	case E_CAL_MODEL_FIELD_CLASSIFICATION :
	case E_CAL_MODEL_FIELD_DESCRIPTION :
	case E_CAL_MODEL_FIELD_SUMMARY :
		return g_strdup (value);
	case E_CAL_MODEL_FIELD_HAS_ALARMS :
	case E_CAL_MODEL_FIELD_ICON :
	case E_CAL_MODEL_FIELD_COLOR :
		return (gpointer) value;
	case E_CAL_MODEL_FIELD_COMPONENT :
		return icalcomponent_new_clone ((icalcomponent *) value);
	case E_CAL_MODEL_FIELD_DTSTART :
	case E_CAL_MODEL_FIELD_CREATED :
	case E_CAL_MODEL_FIELD_LASTMODIFIED :
		if (value) {
			ECellDateEditValue *dv, *orig_dv;

			orig_dv = (ECellDateEditValue *) value;
			dv = g_new0 (ECellDateEditValue, 1);
			*dv = *orig_dv;

			return dv;
		}
		break;
	}

	return NULL;
}

static void
ecm_free_value (ETableModel *etm,
                gint col,
                gpointer value)
{
	g_return_if_fail (col >= 0 && col < E_CAL_MODEL_FIELD_LAST);

	switch (col) {
	case E_CAL_MODEL_FIELD_CATEGORIES :
	case E_CAL_MODEL_FIELD_DESCRIPTION :
	case E_CAL_MODEL_FIELD_SUMMARY :
		if (value)
			g_free (value);
		break;
	case E_CAL_MODEL_FIELD_CLASSIFICATION :
	case E_CAL_MODEL_FIELD_HAS_ALARMS :
	case E_CAL_MODEL_FIELD_ICON :
	case E_CAL_MODEL_FIELD_COLOR :
		break;
	case E_CAL_MODEL_FIELD_DTSTART :
	case E_CAL_MODEL_FIELD_CREATED :
	case E_CAL_MODEL_FIELD_LASTMODIFIED :
		if (value)
			g_free (value);
		break;
	case E_CAL_MODEL_FIELD_COMPONENT :
		if (value)
			icalcomponent_free ((icalcomponent *) value);
		break;
	}
}

static gpointer
ecm_initialize_value (ETableModel *etm,
                      gint col)
{
	ECalModelPrivate *priv;
	ECalModel *model = (ECalModel *) etm;

	g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);
	g_return_val_if_fail (col >= 0 && col < E_CAL_MODEL_FIELD_LAST, NULL);

	priv = model->priv;

	switch (col) {
	case E_CAL_MODEL_FIELD_CATEGORIES :
		return g_strdup (priv->default_category?priv->default_category:"");
	case E_CAL_MODEL_FIELD_CLASSIFICATION :
	case E_CAL_MODEL_FIELD_DESCRIPTION :
	case E_CAL_MODEL_FIELD_SUMMARY :
		return g_strdup ("");
	case E_CAL_MODEL_FIELD_DTSTART :
	case E_CAL_MODEL_FIELD_CREATED :
	case E_CAL_MODEL_FIELD_LASTMODIFIED :
	case E_CAL_MODEL_FIELD_HAS_ALARMS :
	case E_CAL_MODEL_FIELD_ICON :
	case E_CAL_MODEL_FIELD_COLOR :
	case E_CAL_MODEL_FIELD_COMPONENT :
		return NULL;
	}

	return NULL;
}

static gboolean
ecm_value_is_empty (ETableModel *etm,
                    gint col,
                    gconstpointer value)
{
	ECalModelPrivate *priv;
	ECalModel *model = (ECalModel *) etm;

	g_return_val_if_fail (E_IS_CAL_MODEL (model), TRUE);
	g_return_val_if_fail (col >= 0 && col < E_CAL_MODEL_FIELD_LAST, TRUE);

	priv = model->priv;

	switch (col) {
	case E_CAL_MODEL_FIELD_CATEGORIES :
		/* This could be a hack or not.  If the categories field only
		 * contains the default category, then it possibly means that
		 * the user has not entered anything at all in the click-to-add;
		 * the category is in the value because we put it there in
		 * ecm_initialize_value().
		 */
		if (priv->default_category && value && strcmp (priv->default_category, value) == 0)
			return TRUE;
		else
			return string_is_empty (value);
	case E_CAL_MODEL_FIELD_CLASSIFICATION :
	case E_CAL_MODEL_FIELD_DESCRIPTION :
	case E_CAL_MODEL_FIELD_SUMMARY :
		return string_is_empty (value);
	case E_CAL_MODEL_FIELD_DTSTART :
	case E_CAL_MODEL_FIELD_CREATED :
	case E_CAL_MODEL_FIELD_LASTMODIFIED :
		return value ? FALSE : TRUE;
	case E_CAL_MODEL_FIELD_HAS_ALARMS :
	case E_CAL_MODEL_FIELD_ICON :
	case E_CAL_MODEL_FIELD_COLOR :
	case E_CAL_MODEL_FIELD_COMPONENT :
		return TRUE;
	}

	return TRUE;
}

static gchar *
ecm_value_to_string (ETableModel *etm,
                     gint col,
                     gconstpointer value)
{
	g_return_val_if_fail (col >= 0 && col < E_CAL_MODEL_FIELD_LAST, g_strdup (""));

	switch (col) {
	case E_CAL_MODEL_FIELD_CATEGORIES :
	case E_CAL_MODEL_FIELD_CLASSIFICATION :
	case E_CAL_MODEL_FIELD_DESCRIPTION :
	case E_CAL_MODEL_FIELD_SUMMARY :
		return g_strdup (value);
	case E_CAL_MODEL_FIELD_DTSTART :
	case E_CAL_MODEL_FIELD_CREATED :
	case E_CAL_MODEL_FIELD_LASTMODIFIED :
		return e_cal_model_date_value_to_string (E_CAL_MODEL (etm), value);
	case E_CAL_MODEL_FIELD_ICON :
		if (GPOINTER_TO_INT (value) == 0)
			return g_strdup (_("Normal"));
		else if (GPOINTER_TO_INT (value) == 1)
			return g_strdup (_("Recurring"));
		else
			return g_strdup (_("Assigned"));
	case E_CAL_MODEL_FIELD_HAS_ALARMS :
		return g_strdup (value ? _("Yes") : _("No"));
	case E_CAL_MODEL_FIELD_COLOR :
	case E_CAL_MODEL_FIELD_COMPONENT :
		return g_strdup ("");
	}

	return g_strdup ("");
}

/* ECalModel class methods */

typedef struct {
	const gchar *color;
	GList *uris;
} AssignedColorData;

static const gchar *
ecm_get_color_for_component (ECalModel *model,
                             ECalModelComponent *comp_data)
{
	ESource *source;
	const gchar *color_spec;
	gint i, first_empty = 0;
	static AssignedColorData assigned_colors[] = {
		{ "#BECEDD", NULL }, /* 190 206 221     Blue */
		{ "#E2F0EF", NULL }, /* 226 240 239     Light Blue */
		{ "#C6E2B7", NULL }, /* 198 226 183     Green */
		{ "#E2F0D3", NULL }, /* 226 240 211     Light Green */
		{ "#E2D4B7", NULL }, /* 226 212 183     Khaki */
		{ "#EAEAC1", NULL }, /* 234 234 193     Light Khaki */
		{ "#F0B8B7", NULL }, /* 240 184 183     Pink */
		{ "#FED4D3", NULL }, /* 254 212 211     Light Pink */
		{ "#E2C6E1", NULL }, /* 226 198 225     Purple */
		{ "#F0E2EF", NULL }  /* 240 226 239     Light Purple */
	};

	g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);

	source = e_client_get_source (E_CLIENT (comp_data->client));
	color_spec = e_source_peek_color_spec (source);
	if (color_spec != NULL) {
		g_free (comp_data->color);
		comp_data->color = g_strdup (color_spec);
		return comp_data->color;
	}

	for (i = 0; i < G_N_ELEMENTS (assigned_colors); i++) {
		GList *l;

		if (assigned_colors[i].uris == NULL) {
			first_empty = i;
			continue;
		}

		for (l = assigned_colors[i].uris; l != NULL; l = l->next) {
			if (!strcmp ((const gchar *) l->data,
				     e_client_get_uri (E_CLIENT (comp_data->client))))
			{
				return assigned_colors[i].color;
			}
		}
	}

	/* return the first unused color */
	assigned_colors[first_empty].uris = g_list_append (assigned_colors[first_empty].uris,
							   g_strdup (e_client_get_uri (E_CLIENT (comp_data->client))));

	return assigned_colors[first_empty].color;
}

gboolean
e_cal_model_get_confirm_delete (ECalModel *model)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), FALSE);

	return model->priv->confirm_delete;
}

void
e_cal_model_set_confirm_delete (ECalModel *model,
                                gboolean confirm_delete)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));

	model->priv->confirm_delete = confirm_delete;

	g_object_notify (G_OBJECT (model), "confirm-delete");
}

icalcomponent_kind
e_cal_model_get_component_kind (ECalModel *model)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), ICAL_NO_COMPONENT);

	return model->priv->kind;
}

void
e_cal_model_set_component_kind (ECalModel *model,
                                icalcomponent_kind kind)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));

	model->priv->kind = kind;
}

ECalModelFlags
e_cal_model_get_flags (ECalModel *model)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), 0);

	return model->priv->flags;
}

void
e_cal_model_set_flags (ECalModel *model,
                       ECalModelFlags flags)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));

	model->priv->flags = flags;
}

icaltimezone *
e_cal_model_get_timezone (ECalModel *model)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);

	return model->priv->zone;
}

void
e_cal_model_set_timezone (ECalModel *model,
                          icaltimezone *zone)
{
	icaltimezone *old_zone;
	g_return_if_fail (E_IS_CAL_MODEL (model));

	if (model->priv->zone == zone)
		return;

	e_table_model_pre_change (E_TABLE_MODEL (model));
	old_zone = model->priv->zone;
	model->priv->zone = zone;

	/* the timezone affects the times shown for date fields,
	 * so we need to redisplay everything */
	e_table_model_changed (E_TABLE_MODEL (model));

	g_object_notify (G_OBJECT (model), "timezone");
	g_signal_emit (G_OBJECT (model), signals[TIMEZONE_CHANGED], 0,
		       old_zone, zone);
}

gboolean
e_cal_model_get_compress_weekend (ECalModel *model)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), FALSE);

	return model->priv->compress_weekend;
}

void
e_cal_model_set_compress_weekend (ECalModel *model,
                                  gboolean compress_weekend)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));

	model->priv->compress_weekend = compress_weekend;

	g_object_notify (G_OBJECT (model), "compress-weekend");
}

void
e_cal_model_set_default_category (ECalModel *model,
                                  const gchar *default_category)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));

	g_free (model->priv->default_category);
	model->priv->default_category = g_strdup (default_category);
}

gint
e_cal_model_get_default_reminder_interval (ECalModel *model)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), 0);

	return model->priv->default_reminder_interval;
}

void
e_cal_model_set_default_reminder_interval (ECalModel *model,
                                           gint default_reminder_interval)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));

	model->priv->default_reminder_interval = default_reminder_interval;

	g_object_notify (G_OBJECT (model), "default-reminder-interval");
}

EDurationType
e_cal_model_get_default_reminder_units (ECalModel *model)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), 0);

	return model->priv->default_reminder_units;
}

void
e_cal_model_set_default_reminder_units (ECalModel *model,
                                        EDurationType default_reminder_units)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));

	model->priv->default_reminder_units = default_reminder_units;

	g_object_notify (G_OBJECT (model), "default-reminder-units");
}

gboolean
e_cal_model_get_use_24_hour_format (ECalModel *model)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), FALSE);

	return model->priv->use_24_hour_format;
}

void
e_cal_model_set_use_24_hour_format (ECalModel *model,
                                    gboolean use_24_hour_format)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));

	if (model->priv->use_24_hour_format == use_24_hour_format)
		return;

	e_table_model_pre_change (E_TABLE_MODEL (model));
	model->priv->use_24_hour_format = use_24_hour_format;

	/* Get the views to redraw themselves. */
	e_table_model_changed (E_TABLE_MODEL (model));

	g_object_notify (G_OBJECT (model), "use-24-hour-format");
}

gboolean
e_cal_model_get_use_default_reminder (ECalModel *model)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), FALSE);

	return model->priv->use_default_reminder;
}

void
e_cal_model_set_use_default_reminder (ECalModel *model,
                                      gboolean use_default_reminder)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));

	model->priv->use_default_reminder = use_default_reminder;

	g_object_notify (G_OBJECT (model), "use-default-reminder");
}

gint
e_cal_model_get_week_start_day (ECalModel *model)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), 0);

	return model->priv->week_start_day;
}

void
e_cal_model_set_week_start_day (ECalModel *model,
                                gint week_start_day)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));
	g_return_if_fail (week_start_day >= 0);
	g_return_if_fail (week_start_day < 7);

	if (model->priv->week_start_day == week_start_day)
		return;

	model->priv->week_start_day = week_start_day;

	g_object_notify (G_OBJECT (model), "week-start-day");
}

gint
e_cal_model_get_work_day_end_hour (ECalModel *model)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), 0);

	return model->priv->work_day_end_hour;
}

void
e_cal_model_set_work_day_end_hour (ECalModel *model,
                                   gint work_day_end_hour)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));

	model->priv->work_day_end_hour = work_day_end_hour;

	g_object_notify (G_OBJECT (model), "work-day-end-hour");
}

gint
e_cal_model_get_work_day_end_minute (ECalModel *model)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), 0);

	return model->priv->work_day_end_minute;
}

void
e_cal_model_set_work_day_end_minute (ECalModel *model,
                                   gint work_day_end_minute)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));

	model->priv->work_day_end_minute = work_day_end_minute;

	g_object_notify (G_OBJECT (model), "work-day-end-minute");
}

gint
e_cal_model_get_work_day_start_hour (ECalModel *model)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), 0);

	return model->priv->work_day_start_hour;
}

void
e_cal_model_set_work_day_start_hour (ECalModel *model,
                                   gint work_day_start_hour)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));

	model->priv->work_day_start_hour = work_day_start_hour;

	g_object_notify (G_OBJECT (model), "work-day-start-hour");
}

gint
e_cal_model_get_work_day_start_minute (ECalModel *model)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), 0);

	return model->priv->work_day_start_minute;
}

void
e_cal_model_set_work_day_start_minute (ECalModel *model,
                                   gint work_day_start_minute)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));

	model->priv->work_day_start_minute = work_day_start_minute;

	g_object_notify (G_OBJECT (model), "work-day-start-minute");
}

ECalClient *
e_cal_model_get_default_client (ECalModel *model)
{
	ECalModelPrivate *priv;
	ECalModelClient *client_data;

	g_return_val_if_fail (model != NULL, NULL);
	g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);

	priv = model->priv;

	/* FIXME Should we force the client to be open? */

	/* we always return a valid ECal, since we rely on it in many places */
	if (priv->default_client)
		return priv->default_client;

	if (!priv->clients)
		return NULL;

	client_data = (ECalModelClient *) priv->clients->data;

	return client_data ? client_data->client : NULL;
}

void
e_cal_model_set_default_client (ECalModel *model,
                                ECalClient *client)
{
	ECalModelPrivate *priv;
	ECalModelClient *client_data;

	g_return_if_fail (E_IS_CAL_MODEL (model));

	if (client != NULL)
		g_return_if_fail (E_IS_CAL_CLIENT (client));

	priv = model->priv;

	if (priv->default_client) {
		client_data = find_client_data (model, priv->default_client);
		if (!client_data) {
			g_warning ("client_data is NULL\n");
		} else {
			if (!client_data->do_query)
				remove_client (model, client_data);
		}
	}

	if (client != NULL) {
		/* Make sure its in the model */
		client_data = add_new_client (model, client, FALSE);

		/* Store the default client */
		priv->default_client = client_data->client;
	} else
		priv->default_client = NULL;

	g_object_notify (G_OBJECT (model), "default-client");
}

GList *
e_cal_model_get_client_list (ECalModel *model)
{
	GList *list = NULL, *l;
	ECalClient *default_client;

	g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);

	default_client = model->priv->default_client;

	for (l = model->priv->clients; l != NULL; l = l->next) {
		ECalModelClient *client_data = (ECalModelClient *) l->data;

		/* Exclude the default client if we're not querying it. */
		if (client_data->client == default_client && !client_data->do_query)
			continue;

		list = g_list_append (list, client_data->client);
	}

	return list;
}

/**
 * e_cal_model_get_client_for_uri:
 * @model: A calendar model.
 * @uri: Uri for the client to get.
 */
ECalClient *
e_cal_model_get_client_for_uri (ECalModel *model,
                                const gchar *uri)
{
	GList *l;

	g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);
	g_return_val_if_fail (uri != NULL, NULL);

	for (l = model->priv->clients; l != NULL; l = l->next) {
		ECalModelClient *client_data = (ECalModelClient *) l->data;

		if (!strcmp (uri, e_client_get_uri (E_CLIENT (client_data->client))))
			return client_data->client;
	}

	return NULL;
}

static ECalModelClient *
find_client_data (ECalModel *model,
                  ECalClient *client)
{
	ECalModelPrivate *priv;
	GList *l;

	priv = model->priv;

	for (l = priv->clients; l != NULL; l = l->next) {
		ECalModelClient *client_data = (ECalModelClient *) l->data;

		if (client_data->client == client)
			return client_data;
	}

	return NULL;
}

static ECalModelComponent *
search_by_id_and_client (ECalModelPrivate *priv,
                         ECalClient *client,
                         const ECalComponentId *id)
{
	gint i;

	for (i = 0; i < priv->objects->len; i++) {
		ECalModelComponent *comp_data = g_ptr_array_index (priv->objects, i);

		if (comp_data) {
			const gchar *uid;
			gchar *rid = NULL;
			struct icaltimetype icalrid;
			gboolean has_rid = (id->rid && *id->rid);

			uid = icalcomponent_get_uid (comp_data->icalcomp);
			icalrid = icalcomponent_get_recurrenceid (comp_data->icalcomp);
			if (!icaltime_is_null_time (icalrid))
				rid = icaltime_as_ical_string_r (icalrid);

			if (uid && *uid) {
				if ((!client || comp_data->client == client) && !strcmp (id->uid, uid)) {
					if (has_rid) {
						if (!(rid && *rid && !strcmp (rid, id->rid))) {
							g_free (rid);
							continue;
						}
					}
					g_free (rid);
					return comp_data;
				}
			}

			g_free (rid);
		}
	}

	return NULL;
}

static void
remove_all_for_id_and_client (ECalModel *model,
			      ECalClient *client,
			      const ECalComponentId *id)
{
	ECalModelComponent *comp_data;

	while ((comp_data = search_by_id_and_client (model->priv, client, id))) {
		gint pos;
		GSList *list = NULL;

		pos = get_position_in_array (model->priv->objects, comp_data);

		if (!g_ptr_array_remove (model->priv->objects, comp_data))
			continue;

		list = g_slist_append (list, comp_data);
		g_signal_emit (G_OBJECT (model), signals[COMPS_DELETED], 0, list);

		g_slist_free (list);
		g_object_unref (comp_data);

		e_table_model_pre_change (E_TABLE_MODEL (model));
		e_table_model_row_deleted (E_TABLE_MODEL (model), pos);
	}
}

typedef struct {
	ECalClient *client;
	ECalClientView *view;
	ECalModel *model;
	icalcomponent *icalcomp;
} RecurrenceExpansionData;

static void
free_rdata (gpointer data)
{
	RecurrenceExpansionData *rdata = data;

	if (!rdata)
		return;

	g_object_unref (rdata->client);
	g_object_unref (rdata->view);
	g_object_unref (rdata->model);
	g_free (rdata);
}

static gboolean
add_instance_cb (ECalComponent *comp,
                 time_t instance_start,
                 time_t instance_end,
                 gpointer user_data)
{
	ECalModelComponent *comp_data;
	ECalModelPrivate *priv;
	RecurrenceExpansionData *rdata = user_data;
	icaltimetype time;
	ECalComponentDateTime datetime, to_set;
	icaltimezone *zone = NULL;
	ECalComponentId *id;

	g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), TRUE);

	priv = rdata->model->priv;

	id = e_cal_component_get_id (comp);
	remove_all_for_id_and_client (rdata->model, rdata->client, id);
	e_cal_component_free_id (id);

	e_table_model_pre_change (E_TABLE_MODEL (rdata->model));

	/* set the right instance start date to component */
	e_cal_component_get_dtstart (comp, &datetime);
	if (datetime.tzid)
		e_cal_client_get_timezone_sync (rdata->client, datetime.tzid, &zone, NULL, NULL);
	time = icaltime_from_timet_with_zone (instance_start, FALSE, zone ? zone : priv->zone);
	to_set.value = &time;
	to_set.tzid = datetime.tzid;
	e_cal_component_set_dtstart (comp, &to_set);
	e_cal_component_free_datetime (&datetime);

	/* set the right instance end date to component*/
	e_cal_component_get_dtend (comp, &datetime);
	zone = NULL;
	if (datetime.tzid)
		e_cal_client_get_timezone_sync (rdata->client, datetime.tzid, &zone, NULL, NULL);
	time = icaltime_from_timet_with_zone (instance_end, FALSE, zone ? zone : priv->zone);
	to_set.value = &time;
	to_set.tzid = datetime.tzid;
	e_cal_component_set_dtend (comp, &to_set);
	e_cal_component_free_datetime (&datetime);

	comp_data = g_object_new (E_TYPE_CAL_MODEL_COMPONENT, NULL);
	comp_data->client = g_object_ref (rdata->client);
	comp_data->icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
	comp_data->instance_start = instance_start;
	comp_data->instance_end = instance_end;

	g_ptr_array_add (priv->objects, comp_data);
	e_table_model_row_inserted (E_TABLE_MODEL (rdata->model), priv->objects->len - 1);

	return TRUE;
}

/* We do this check since the calendar items are downloaded from the server
 * in the open_method, since the default timezone might not be set there. */
static void
ensure_dates_are_in_default_zone (ECalModel *model,
                                  icalcomponent *icalcomp)
{
	icaltimetype dt;
	icaltimezone *zone;

	zone = e_cal_model_get_timezone (model);
	if (!zone)
		return;

	dt = icalcomponent_get_dtstart (icalcomp);
	if (dt.is_utc) {
		dt = icaltime_convert_to_zone (dt, zone);
		icalcomponent_set_dtstart (icalcomp, dt);
	}

	dt = icalcomponent_get_dtend (icalcomp);
	if (dt.is_utc) {
		dt = icaltime_convert_to_zone (dt, zone);
		icalcomponent_set_dtend (icalcomp, dt);
	}
}

static gint
place_master_object_first_cb (gconstpointer p1,
                              gconstpointer p2)
{
	icalcomponent *c1 = (icalcomponent *) p1, *c2 = (icalcomponent *) p2;
	const gchar *uid1, *uid2;
	gint res;

	g_return_val_if_fail (c1 != NULL, 0);
	g_return_val_if_fail (c2 != NULL, 0);

	uid1 = icalcomponent_get_uid (c1);
	uid2 = icalcomponent_get_uid (c2);

	res = g_strcmp0 (uid1, uid2);
	if (res == 0) {
		struct icaltimetype rid1, rid2;

		rid1 = icalcomponent_get_recurrenceid (c1);
		rid2 = icalcomponent_get_recurrenceid (c2);

		if (icaltime_is_null_time (rid1)) {
			if (!icaltime_is_null_time (rid2))
				res = -1;
		} else if (icaltime_is_null_time (rid2)) {
			res = 1;
		} else {
			res = icaltime_compare (rid1, rid2);
		}
	}

	return res;
}

static void client_view_objects_added_cb (ECalClientView *view, const GSList *objects, ECalModel *model);

static void
process_added (ECalClientView *view,
               const GSList *objects,
               ECalModel *model)
{
	ECalModelPrivate *priv;
	const GSList *l;
	GSList *copy;

	priv = model->priv;

	/* order matters, process master object first, then detached instances */
	copy = g_slist_sort (g_slist_copy ((GSList *) objects), place_master_object_first_cb);

	for (l = copy; l; l = l->next) {
		ECalModelComponent *comp_data;
		ECalComponentId *id;
		ECalComponent *comp = e_cal_component_new ();
		ECalClient *client = e_cal_client_view_get_client (view);

		/* This will fail for alarm or VCalendar component */
		if (!e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (l->data))) {
			g_object_unref (comp);
			continue;
		}

		id = e_cal_component_get_id (comp);

		/* remove the components if they are already present and re-add them */
		remove_all_for_id_and_client (model, client, id);

		e_cal_component_free_id (id);
		g_object_unref (comp);
		ensure_dates_are_in_default_zone (model, l->data);

		if (e_cal_util_component_has_recurrences (l->data) && (priv->flags & E_CAL_MODEL_FLAGS_EXPAND_RECURRENCES)) {
			ECalModelClient *client_data = find_client_data (model, client);

			if (client_data) {
				RecurrenceExpansionData *rdata = g_new0 (RecurrenceExpansionData, 1);
				rdata->client = g_object_ref (client);
				rdata->view = g_object_ref (view);
				rdata->model = g_object_ref (model);

				e_cal_client_generate_instances_for_object (rdata->client, l->data, priv->start, priv->end, client_data->cancellable,
									    (ECalRecurInstanceFn) add_instance_cb, rdata, free_rdata);
			}
		} else {
			e_table_model_pre_change (E_TABLE_MODEL (model));

			comp_data = g_object_new (E_TYPE_CAL_MODEL_COMPONENT, NULL);
			comp_data->client = g_object_ref (client);
			comp_data->icalcomp = icalcomponent_new_clone (l->data);
			e_cal_model_set_instance_times (comp_data, priv->zone);

			g_ptr_array_add (priv->objects, comp_data);
			e_table_model_row_inserted (E_TABLE_MODEL (model), priv->objects->len - 1);
		}
	}

	g_slist_free (copy);
}

static void
process_modified (ECalClientView *view,
                  const GSList *objects,
                  ECalModel *model)
{
	ECalModelPrivate *priv;
	const GSList *l;
	GSList *list = NULL;

	priv = model->priv;

	/*  re-add only the recurrence objects */
	for (l = objects; l != NULL; l = g_slist_next (l)) {
		if (!e_cal_util_component_is_instance (l->data) && e_cal_util_component_has_recurrences (l->data) && (priv->flags & E_CAL_MODEL_FLAGS_EXPAND_RECURRENCES))
			list = g_slist_prepend (list, l->data);
		else {
			gint pos;
			ECalModelComponent *comp_data;
			ECalComponentId *id;
			ECalComponent *comp = e_cal_component_new ();
			ECalClient *client = e_cal_client_view_get_client (view);

			if (!e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (l->data))) {
				g_object_unref (comp);
				continue;
			}

			e_table_model_pre_change (E_TABLE_MODEL (model));

			id = e_cal_component_get_id (comp);

			comp_data = search_by_id_and_client (priv, client, id);

			e_cal_component_free_id (id);
			g_object_unref (comp);

			if (!comp_data) {
				/* the modified component is not in the model yet, just skip it */
				continue;
			}

			if (comp_data->icalcomp)
				icalcomponent_free (comp_data->icalcomp);
			if (comp_data->dtstart) {
				g_free (comp_data->dtstart);
				comp_data->dtstart = NULL;
			}
			if (comp_data->dtend) {
				g_free (comp_data->dtend);
				comp_data->dtend = NULL;
			}
			if (comp_data->due) {
				g_free (comp_data->due);
				comp_data->due = NULL;
			}
			if (comp_data->completed) {
				g_free (comp_data->completed);
				comp_data->completed = NULL;
			}
			if (comp_data->created) {
				g_free (comp_data->created);
				comp_data->created = NULL;
			}
			if (comp_data->lastmodified) {
				g_free (comp_data->lastmodified);
				comp_data->lastmodified = NULL;
			}
			if (comp_data->color) {
				g_free (comp_data->color);
				comp_data->color = NULL;
			}

			comp_data->icalcomp = icalcomponent_new_clone (l->data);
			e_cal_model_set_instance_times (comp_data, priv->zone);

			pos = get_position_in_array (priv->objects, comp_data);

			e_table_model_row_changed (E_TABLE_MODEL (model), pos);
		}
	}

	client_view_objects_added_cb (view, list, model);
	g_slist_free (list);
}

static void
process_removed (ECalClientView *view,
                 const GSList *ids,
                 ECalModel *model)
{
	ECalModelPrivate *priv;
	const GSList *l;

	priv = model->priv;

	for (l = ids; l; l = l->next) {
		ECalModelComponent *comp_data = NULL;
		ECalComponentId *id = l->data;
		gint pos;

		/* make sure we remove all objects with this UID */
		while ((comp_data = search_by_id_and_client (priv, e_cal_client_view_get_client (view), id))) {
			GSList *l = NULL;

			pos = get_position_in_array (priv->objects, comp_data);

			if (!g_ptr_array_remove (priv->objects, comp_data))
				continue;

			l = g_slist_append (l, comp_data);
			g_signal_emit (G_OBJECT (model), signals[COMPS_DELETED], 0, l);

			g_slist_free (l);
			g_object_unref (comp_data);

			e_table_model_pre_change (E_TABLE_MODEL (model));
			e_table_model_row_deleted (E_TABLE_MODEL (model), pos);
		}
	}

	/* to notify about changes, because in call of row_deleted there are still all events */
	e_table_model_changed (E_TABLE_MODEL (model));
}

static gpointer
copy_comp_id (gpointer id)
{
	ECalComponentId *comp_id = (ECalComponentId *) id, *copy;

	g_return_val_if_fail (comp_id != NULL, NULL);

	copy = g_new0 (ECalComponentId, 1);
	copy->uid = g_strdup (comp_id->uid);
	copy->rid = g_strdup (comp_id->rid);

	return copy;
}

static void
free_comp_id (gpointer id)
{
	ECalComponentId *comp_id = (ECalComponentId *) id;

	g_return_if_fail (comp_id != NULL);

	g_free (comp_id->uid);
	g_free (comp_id->rid);
	g_free (comp_id);
}

static void
process_event (ECalClientView *view,
               const GSList *objects,
               ECalModel *model,
               void (*process_fn) (ECalClientView *view,
                                   const GSList *objects,
                                   ECalModel *model),
               gboolean *in,
               GSList **save_list,
               gpointer (*copy_fn) (gpointer data),
               void (*free_fn) (gpointer data))
{
	gboolean skip = FALSE;
	const GSList *l;

	g_mutex_lock (model->priv->notify_lock);
	if (*in) {
		skip = TRUE;
		for (l = objects; l; l = l->next) {
			if (l->data)
				*save_list = g_slist_append (*save_list, copy_fn (l->data));
		}
	} else {
		*in = TRUE;
	}

	g_mutex_unlock (model->priv->notify_lock);

	if (skip)
		return;

	/* do it */
	process_fn (view, objects, model);

	g_mutex_lock (model->priv->notify_lock);
	while (*save_list) {
		GSList *copy = *save_list;
		*save_list = NULL;
		g_mutex_unlock (model->priv->notify_lock);

		/* do it */
		process_fn (view, copy, model);

		for (l = copy; l; l = l->next) {
			if (l->data) {
				free_fn (l->data);
			}
		}
		g_slist_free (copy);

		g_mutex_lock (model->priv->notify_lock);
	}

	*in = FALSE;
	g_mutex_unlock (model->priv->notify_lock);
}

static void
client_view_objects_added_cb (ECalClientView *view,
                              const GSList *objects,
                              ECalModel *model)
{
	process_event (
		view, objects, model, process_added,
		&model->priv->in_added,
		&model->priv->notify_added,
		(gpointer (*)(gpointer)) icalcomponent_new_clone,
		(void (*)(gpointer)) icalcomponent_free);
}

static void
client_view_objects_modified_cb (ECalClientView *view,
                                 const GSList *objects,
                                 ECalModel *model)
{
	process_event (
		view, objects, model, process_modified,
		&model->priv->in_modified,
		&model->priv->notify_modified,
		(gpointer (*)(gpointer)) icalcomponent_new_clone,
		(void (*)(gpointer)) icalcomponent_free);
}

static void
client_view_objects_removed_cb (ECalClientView *view,
                                const GSList *ids,
                                ECalModel *model)
{
	process_event (
		view, ids, model, process_removed,
		&model->priv->in_removed,
		&model->priv->notify_removed,
		copy_comp_id, free_comp_id);
}

static void
client_view_progress_cb (ECalClientView *view,
                         gint percent,
                         const gchar *message,
                         gpointer user_data)
{
	ECalModel *model = (ECalModel *) user_data;
	ECalClient *client = e_cal_client_view_get_client (view);

	g_return_if_fail (E_IS_CAL_MODEL (model));

	g_signal_emit (
		model, signals[CAL_VIEW_PROGRESS], 0, message,
		percent, e_cal_client_get_source_type (client));
}

static void
client_view_complete_cb (ECalClientView *view,
                         const GError *error,
                         gpointer user_data)
{
	ECalModel *model = (ECalModel *) user_data;
	ECalClient *client = e_cal_client_view_get_client (view);

	g_return_if_fail (E_IS_CAL_MODEL (model));

	g_signal_emit (G_OBJECT (model), signals[CAL_VIEW_COMPLETE], 0, error,
			e_cal_client_get_source_type (client));
}

struct get_view_data
{
	ECalModel *model; /* do not touch this, if cancelled */
	ECalModelClient *client_data; /* do not touch this, if cancelled */
	GCancellable *cancellable;
	guint tries;
};

static void
free_get_view_data (struct get_view_data *gvd)
{
	if (!gvd)
		return;

	g_object_unref (gvd->cancellable);
	g_free (gvd);
}

static gboolean retry_get_view_timeout_cb (gpointer user_data);

static void
get_view_cb (GObject *source_object,
             GAsyncResult *result,
             gpointer user_data)
{
	struct get_view_data *gvd = user_data;
	GError *error = NULL;
	ECalClientView *view = NULL;

	g_return_if_fail (source_object != NULL);
	g_return_if_fail (result != NULL);
	g_return_if_fail (gvd != NULL);
	g_return_if_fail (gvd->model != NULL);
	g_return_if_fail (gvd->client_data != NULL);

	if (!e_cal_client_get_view_finish (E_CAL_CLIENT (source_object), result, &view, &error)) {
		if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
		    g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
			g_clear_error (&error);

			free_get_view_data (gvd);
			return;
		}

		if (gvd->tries < 10) {
			gvd->tries++;
			g_timeout_add (500, retry_get_view_timeout_cb, gvd);
			return;
		}

		g_debug ("%s: Failed to get view: %s", G_STRFUNC, error ? error->message : "Unknown error");

		g_clear_error (&error);
	} else {
		gvd->client_data->view = view;

		g_signal_connect (gvd->client_data->view, "objects-added", G_CALLBACK (client_view_objects_added_cb), gvd->model);
		g_signal_connect (gvd->client_data->view, "objects-modified", G_CALLBACK (client_view_objects_modified_cb), gvd->model);
		g_signal_connect (gvd->client_data->view, "objects-removed", G_CALLBACK (client_view_objects_removed_cb), gvd->model);
		g_signal_connect (gvd->client_data->view, "progress", G_CALLBACK (client_view_progress_cb), gvd->model);
		g_signal_connect (gvd->client_data->view, "complete", G_CALLBACK (client_view_complete_cb), gvd->model);

		e_cal_client_view_start (gvd->client_data->view, &error);

		if (error) {
			g_debug ("%s: Failed to start view: %s", G_STRFUNC, error->message);
			g_error_free (error);
		}
	}

	free_get_view_data (gvd);
}

static gboolean
retry_get_view_timeout_cb (gpointer user_data)
{
	struct get_view_data *gvd = user_data;

	if (g_cancellable_is_cancelled (gvd->cancellable)) {
		free_get_view_data (gvd);
		return FALSE;
	}

	e_cal_client_get_view (gvd->client_data->client, gvd->model->priv->full_sexp, gvd->cancellable, get_view_cb, gvd);

	return FALSE;
}

static void
update_e_cal_view_for_client (ECalModel *model,
                              ECalModelClient *client_data)
{
	ECalModelPrivate *priv;
	struct get_view_data *gvd;

	priv = model->priv;

	/* Skip if this client has not finished loading yet */
	if (!e_client_is_opened (E_CLIENT (client_data->client)))
		return;

	/* free the previous view, if any */
	if (client_data->view) {
		g_signal_handlers_disconnect_matched (client_data->view, G_SIGNAL_MATCH_DATA,
						      0, 0, NULL, NULL, model);
		g_object_unref (client_data->view);
		client_data->view = NULL;
	}

	/* prepare the view */
	g_return_if_fail (priv->full_sexp != NULL);

	/* Don't create the new query if we won't use it */
	if (!client_data->do_query)
		return;

	if (client_data->cancellable) {
		g_cancellable_cancel (client_data->cancellable);
		g_object_unref (client_data->cancellable);
	}

	client_data->cancellable = g_cancellable_new ();

	gvd = g_new0 (struct get_view_data, 1);
	gvd->client_data = client_data;
	gvd->model = model;
	gvd->tries = 0;
	gvd->cancellable = g_object_ref (client_data->cancellable);

	e_cal_client_get_view (client_data->client, priv->full_sexp, gvd->cancellable, get_view_cb, gvd);
}

void
e_cal_model_update_status_message (ECalModel *model,
                                   const gchar *message,
                                   gdouble percent)
{
	g_return_if_fail (model != NULL);

	g_signal_emit (model, signals[STATUS_MESSAGE], 0, message, percent);
}

static void
backend_died_cb (ECalClient *client,
                 gpointer user_data)
{
	ECalModel *model;

	model = E_CAL_MODEL (user_data);

	e_cal_model_remove_client (model, client);
}

static void
cal_model_retrieve_capabilies_cb (GObject *source_object,
                                  GAsyncResult *result,
                                  gpointer user_data)
{
	ECalClient *client = E_CAL_CLIENT (source_object);
	ECalModel *model = user_data;
	ECalModelClient *client_data;
	gchar *capabilities = NULL;

	g_return_if_fail (client != NULL);
	g_return_if_fail (model != NULL);

	e_client_retrieve_capabilities_finish (
		E_CLIENT (client), result, &capabilities, NULL);
	g_free (capabilities);

	e_cal_model_update_status_message (model, NULL, -1.0);

	client_data = find_client_data (model, client);
	g_return_if_fail (client_data);

	update_e_cal_view_for_client (model, client_data);
}

struct RetryOpenData
{
	EClient *client;
	ECalModel *model;
	GCancellable *cancellable;
};

static void
free_retry_open_data (gpointer data)
{
	struct RetryOpenData *rod = data;

	if (!rod)
		return;

	g_object_unref (rod->client);
	g_object_unref (rod->cancellable);
	g_free (rod);
}

static gboolean cal_model_retry_open_timeout_cb (gpointer user_data);

static void
client_opened_cb (GObject *source_object,
                  GAsyncResult *result,
                  gpointer user_data)
{
	ECalClient *client = E_CAL_CLIENT (source_object);
	ECalModel *model = (ECalModel *) user_data;
	GError *error = NULL;

	e_client_open_finish (E_CLIENT (client), result, &error);

	if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
	    g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
		g_error_free (error);
		return;
	}

	if (error && g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_BUSY)) {
		struct RetryOpenData *rod;

		rod = g_new0 (struct RetryOpenData, 1);
		rod->client = g_object_ref (client);
		rod->model = model;
		rod->cancellable = g_object_ref (model->priv->loading_clients);

		/* postpone for 1/2 of a second, backend is busy now */
		g_timeout_add_full (
			G_PRIORITY_DEFAULT, 500,
			cal_model_retry_open_timeout_cb,
			rod, free_retry_open_data);

		g_error_free (error);

		return;
	}

	if (error != NULL) {
		const gchar *uri;

		uri = e_client_get_uri (E_CLIENT (client));
		e_cal_model_remove_client (model, client);
		g_warning (
			"%s: Failed to open '%s': %s",
			G_STRFUNC, uri, error->message);
		g_error_free (error);
		e_cal_model_update_status_message (model, NULL, -1.0);
		return;
	}

	/* to have them ready for later use */
	e_client_retrieve_capabilities (
		E_CLIENT (client), model->priv->loading_clients,
		cal_model_retrieve_capabilies_cb, model);
}

static gboolean
cal_model_retry_open_timeout_cb (gpointer user_data)
{
	struct RetryOpenData *rod = user_data;

	g_return_val_if_fail (rod != NULL, FALSE);
	g_return_val_if_fail (rod->client != NULL, FALSE);
	g_return_val_if_fail (rod->model != NULL, FALSE);

	e_client_open (
		rod->client, TRUE, rod->cancellable,
		client_opened_cb, rod->model);

	return FALSE;
}

static ECalModelClient *
add_new_client (ECalModel *model,
                ECalClient *client,
                gboolean do_query)
{
	ECalModelPrivate *priv;
	ECalModelClient *client_data;

	priv = model->priv;

	/* DEBUG the load state should always be loaded here
	if (e_cal_get_load_state (client) != E_CAL_LOAD_LOADED) {
		g_assert_not_reached ();
	} */

	/* Look to see if we already have this client */
	client_data = find_client_data (model, client);
	if (client_data) {
		if (client_data->do_query)
			return client_data;
		else
			client_data->do_query = do_query;

		goto load;
	}

	client_data = g_new0 (ECalModelClient, 1);
	client_data->client = g_object_ref (client);
	client_data->view = NULL;
	client_data->do_query = do_query;

	priv->clients = g_list_append (priv->clients, client_data);

	g_signal_connect (
		client_data->client, "backend_died",
		G_CALLBACK (backend_died_cb), model);

 load:
	if (e_client_is_opened (E_CLIENT (client))) {
		update_e_cal_view_for_client (model, client_data);
	} else {
		gchar *msg;

		msg = g_strdup_printf (_("Opening %s"), e_client_get_uri (E_CLIENT (client)));
		e_cal_model_update_status_message (model, msg, -1.0);
		g_free (msg);

		e_cal_client_set_default_timezone (client, e_cal_model_get_timezone (model));

		e_client_open (E_CLIENT (client), TRUE, model->priv->loading_clients, client_opened_cb, model);
	}

	return client_data;
}

/**
 * e_cal_model_add_client
 */
void
e_cal_model_add_client (ECalModel *model,
                        ECalClient *client)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));
	g_return_if_fail (E_IS_CAL_CLIENT (client));

	add_new_client (model, client, TRUE);
}

static void
remove_client_objects (ECalModel *model,
                       ECalModelClient *client_data)
{
	gint i;

	/* remove all objects belonging to this client */
	for (i = model->priv->objects->len; i > 0; i--) {
		ECalModelComponent *comp_data = (ECalModelComponent *) g_ptr_array_index (model->priv->objects, i - 1);

		g_return_if_fail (comp_data != NULL);

		if (comp_data->client == client_data->client) {
			GSList *l = NULL;

			g_ptr_array_remove (model->priv->objects, comp_data);

			l = g_slist_append (l, comp_data);
			g_signal_emit (G_OBJECT (model), signals[COMPS_DELETED], 0, l);

			g_slist_free (l);
			g_object_unref (comp_data);

			e_table_model_pre_change (E_TABLE_MODEL (model));
			e_table_model_row_deleted (E_TABLE_MODEL (model), i - 1);
		}
	}

	/* to notify about changes, because in call of row_deleted there are still all events */
	e_table_model_changed (E_TABLE_MODEL (model));
}

static void
remove_client (ECalModel *model,
               ECalModelClient *client_data)
{
	/* FIXME We might not want to disconnect the open signal for the default client */
	g_signal_handlers_disconnect_matched (client_data->client, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, model);
	if (client_data->view)
		g_signal_handlers_disconnect_matched (client_data->view, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, model);

	remove_client_objects (model, client_data);

	/* If this is the default client and we were querying (so it
	 * was also a source), keep it around but don't query it */
	if (model->priv->default_client == client_data->client && client_data->do_query) {
		client_data->do_query = FALSE;

		return;
	}

	if (model->priv->default_client == client_data->client)
		model->priv->default_client = NULL;

	/* Remove the client from the list */
	model->priv->clients = g_list_remove (model->priv->clients, client_data);

	/* free all remaining memory */
	g_object_unref (client_data->client);
	if (client_data->view)
		g_object_unref (client_data->view);
	g_free (client_data);
}

/**
 * e_cal_model_remove_client
 */
void
e_cal_model_remove_client (ECalModel *model,
                           ECalClient *client)
{
	ECalModelClient *client_data;

	g_return_if_fail (E_IS_CAL_MODEL (model));
	g_return_if_fail (E_IS_CAL_CLIENT (client));

	client_data = find_client_data (model, client);
	if (client_data)
		remove_client (model, client_data);
}

/**
 * e_cal_model_remove_all_clients
 */
void
e_cal_model_remove_all_clients (ECalModel *model)
{
	ECalModelPrivate *priv;

	g_return_if_fail (E_IS_CAL_MODEL (model));

	priv = model->priv;
	while (priv->clients != NULL) {
		ECalModelClient *client_data = (ECalModelClient *) priv->clients->data;
		remove_client (model, client_data);
	}
}

static GSList *
get_objects_as_list (ECalModel *model)
{
	gint i;
	GSList *l = NULL;
	ECalModelPrivate *priv = model->priv;

	for (i = 0; i < priv->objects->len; i++) {
		ECalModelComponent *comp_data;

		comp_data = g_ptr_array_index (priv->objects, i);
		if (comp_data == NULL) {
			g_warning ("comp_data is null\n");
			continue;
		}

		l = g_slist_prepend (l, comp_data);
	}

	return l;
}

struct cc_data
{
	ECalModel *model;
	EFlag *eflag;
};

static gboolean
cleanup_content_cb (gpointer user_data)
{
	ECalModel *model;
	ECalModelPrivate *priv;
	GSList *slist;
	gint len;
	struct cc_data *data = user_data;

	g_return_val_if_fail (data != NULL, FALSE);
	g_return_val_if_fail (data->model != NULL, FALSE);
	g_return_val_if_fail (data->eflag != NULL, FALSE);

	model = data->model;
	priv = model->priv;

	g_return_val_if_fail (priv != NULL, FALSE);

	e_table_model_pre_change (E_TABLE_MODEL (model));
	len = priv->objects->len;

	slist = get_objects_as_list (model);
	g_ptr_array_set_size (priv->objects, 0);
	g_signal_emit (G_OBJECT (model), signals[COMPS_DELETED], 0, slist);

	e_table_model_rows_deleted (E_TABLE_MODEL (model), 0, len);

	g_slist_foreach (slist, (GFunc) g_object_unref, NULL);
	g_slist_free (slist);

	e_flag_set (data->eflag);

	return FALSE;
}

static void
redo_queries (ECalModel *model)
{
	ECalModelPrivate *priv;
	GList *l;
	struct cc_data data;

	priv = model->priv;

	if (priv->full_sexp)
		g_free (priv->full_sexp);

	if (priv->start != -1 && priv->end != -1) {
		gchar *iso_start, *iso_end;

		iso_start = isodate_from_time_t (priv->start);
		iso_end = isodate_from_time_t (priv->end);

		if (priv->search_sexp) {
			priv->full_sexp = g_strdup_printf ("(and (occur-in-time-range? (make-time \"%s\")"
					"                           (make-time \"%s\"))"
					"     %s)",
					iso_start, iso_end,
					priv->search_sexp ? priv->search_sexp : "");
		} else {
			priv->full_sexp = g_strdup_printf ("(occur-in-time-range? (make-time \"%s\")"
					"                           (make-time \"%s\"))",
					iso_start, iso_end);
		}

		g_free (iso_start);
		g_free (iso_end);
	} else if (priv->search_sexp) {
		priv->full_sexp = g_strdup (priv->search_sexp);
	} else {
		priv->full_sexp = g_strdup ("#f");
	}

	/* clean up the current contents, which should be done
	 * always from the main thread, because of gtk calls during removal */
	data.model = model;
	data.eflag = e_flag_new ();

	if (!g_main_context_is_owner (g_main_context_default ())) {
		/* function called from other than main thread */
		g_timeout_add (10, cleanup_content_cb, &data);
		e_flag_wait (data.eflag);
	} else {
		cleanup_content_cb (&data);
	}

	e_flag_free (data.eflag);

	/* update the view for all clients */
	for (l = priv->clients; l != NULL; l = l->next) {
		ECalModelClient *client_data;

		client_data = (ECalModelClient *) l->data;
		update_e_cal_view_for_client (model, client_data);
	}
}

void
e_cal_model_get_time_range (ECalModel *model,
                            time_t *start,
                            time_t *end)
{
	ECalModelPrivate *priv;

	g_return_if_fail (model != NULL);
	g_return_if_fail (E_IS_CAL_MODEL (model));

	priv = model->priv;

	if (start)
		*start = priv->start;

	if (end)
		*end = priv->end;
}

void
e_cal_model_set_time_range (ECalModel *model,
                            time_t start,
                            time_t end)
{
	ECalModelPrivate *priv;

	g_return_if_fail (model != NULL);
	g_return_if_fail (E_IS_CAL_MODEL (model));
	g_return_if_fail (start >= 0 && end >= 0);
	g_return_if_fail (start <= end);

	priv = model->priv;

	if (priv->start == start && priv->end == end)
		return;

	priv->start = start;
	priv->end = end;

	g_signal_emit (G_OBJECT (model), signals[TIME_RANGE_CHANGED], 0, start, end);
	redo_queries (model);
}

const gchar *
e_cal_model_get_search_query (ECalModel *model)
{
	ECalModelPrivate *priv;

	g_return_val_if_fail (model != NULL, NULL);
	g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);

	priv = model->priv;

	return priv->search_sexp;
}

/**
 * e_cal_model_set_query
 */
void
e_cal_model_set_search_query (ECalModel *model,
                              const gchar *sexp)
{
	ECalModelPrivate *priv;

	g_return_if_fail (E_IS_CAL_MODEL (model));

	priv = model->priv;

	if (!strcmp (sexp ? sexp : "", priv->search_sexp ? priv->search_sexp : ""))
		return;

	if (priv->search_sexp)
		g_free (priv->search_sexp);

	priv->search_sexp = g_strdup (sexp);

	redo_queries (model);
}

/**
 * e_cal_model_set_query
 */
void
e_cal_model_set_search_query_with_time_range (ECalModel *model,
                                              const gchar *sexp,
                                              time_t start,
                                              time_t end)
{
	ECalModelPrivate *priv;
	gboolean do_query = FALSE;

	g_return_if_fail (E_IS_CAL_MODEL (model));

	priv = model->priv;

	if (strcmp (sexp ? sexp : "", priv->search_sexp ? priv->search_sexp : "")) {
		if (priv->search_sexp)
			g_free (priv->search_sexp);

		priv->search_sexp = g_strdup (sexp);
		do_query = TRUE;
	}

	if (!(priv->start == start && priv->end == end)) {
		priv->start = start;
		priv->end = end;
		do_query = TRUE;

		g_signal_emit (G_OBJECT (model), signals[TIME_RANGE_CHANGED], 0, start, end);
	}

	if (do_query)
		redo_queries (model);
}

/**
 * e_cal_model_create_component_with_defaults
 */
icalcomponent *
e_cal_model_create_component_with_defaults (ECalModel *model,
                                            gboolean all_day)
{
	ECalModelPrivate *priv;
	ECalComponent *comp;
	icalcomponent *icalcomp;
	ECalClient *client;

	g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);

	priv = model->priv;

	g_return_val_if_fail (priv->clients != NULL, NULL);

	client = e_cal_model_get_default_client (model);
	if (!client)
		return icalcomponent_new (priv->kind);

	switch (priv->kind) {
	case ICAL_VEVENT_COMPONENT :
		comp = cal_comp_event_new_with_defaults (
			client, all_day,
			e_cal_model_get_use_default_reminder (model),
			e_cal_model_get_default_reminder_interval (model),
			e_cal_model_get_default_reminder_units (model));
		break;
	case ICAL_VTODO_COMPONENT :
		comp = cal_comp_task_new_with_defaults (client);
		break;
	case ICAL_VJOURNAL_COMPONENT :
		comp = cal_comp_memo_new_with_defaults (client);
		break;
	default:
		return NULL;
	}

	if (!comp)
		return icalcomponent_new (priv->kind);

	icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
	g_object_unref (comp);

	/* make sure the component has an UID */
	if (!icalcomponent_get_uid (icalcomp)) {
		gchar *uid;

		uid = e_cal_component_gen_uid ();
		icalcomponent_set_uid (icalcomp, uid);

		g_free (uid);
	}

	return icalcomp;
}

/**
 * e_cal_model_get_color_for_component
 */
const gchar *
e_cal_model_get_color_for_component (ECalModel *model,
                                     ECalModelComponent *comp_data)
{
	ECalModelClass *model_class;
	const gchar *color = NULL;

	g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);
	g_return_val_if_fail (comp_data != NULL, NULL);

	model_class = (ECalModelClass *) G_OBJECT_GET_CLASS (model);
	if (model_class->get_color_for_component != NULL)
		color = model_class->get_color_for_component (model, comp_data);

	if (!color)
		color = ecm_get_color_for_component (model, comp_data);

	return color;
}

/**
 * e_cal_model_get_rgb_color_for_component
 */
gboolean
e_cal_model_get_rgb_color_for_component (ECalModel *model,
                                         ECalModelComponent *comp_data,
                                         gdouble *red,
                                         gdouble *green,
                                         gdouble *blue)
{
	GdkColor gdk_color;
	const gchar *color;

	color = e_cal_model_get_color_for_component (model, comp_data);
	if (color && gdk_color_parse (color, &gdk_color)) {

		if (red)
			*red = ((double) gdk_color.red)/0xffff;
		if (green)
			*green = ((double) gdk_color.green)/0xffff;
		if (blue)
			*blue = ((double) gdk_color.blue)/0xffff;

		return TRUE;
	}

	return FALSE;
}

/**
 * e_cal_model_get_component_at
 */
ECalModelComponent *
e_cal_model_get_component_at (ECalModel *model,
                              gint row)
{
	ECalModelPrivate *priv;

	g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);

	priv = model->priv;

	g_return_val_if_fail (row >= 0 && row < priv->objects->len, NULL);

	return g_ptr_array_index (priv->objects, row);
}

ECalModelComponent *
e_cal_model_get_component_for_uid (ECalModel *model,
                                   const ECalComponentId *id)
{
	ECalModelPrivate *priv;

	g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);

	priv = model->priv;

	return search_by_id_and_client (priv, NULL, id);
}

/**
 * e_cal_model_date_value_to_string
 */
gchar *
e_cal_model_date_value_to_string (ECalModel *model,
                                  gconstpointer value)
{
	ECalModelPrivate *priv;
	ECellDateEditValue *dv = (ECellDateEditValue *) value;
	struct icaltimetype tt;
	struct tm tmp_tm;
	gchar buffer[64];

	g_return_val_if_fail (E_IS_CAL_MODEL (model), g_strdup (""));

	priv = model->priv;

	if (!dv)
		return g_strdup ("");

	/* We currently convert all the dates to the current timezone. */
	tt = dv->tt;
	icaltimezone_convert_time (&tt, dv->zone, priv->zone);

	tmp_tm.tm_year = tt.year - 1900;
	tmp_tm.tm_mon = tt.month - 1;
	tmp_tm.tm_mday = tt.day;
	tmp_tm.tm_hour = tt.hour;
	tmp_tm.tm_min = tt.minute;
	tmp_tm.tm_sec = tt.second;
	tmp_tm.tm_isdst = -1;

	tmp_tm.tm_wday = time_day_of_week (tt.day, tt.month - 1, tt.year);

	memset (buffer, 0, sizeof (buffer));
	e_time_format_date_and_time (&tmp_tm, priv->use_24_hour_format,
				     TRUE, FALSE,
				     buffer, sizeof (buffer));
	return g_strdup (buffer);
}

/* FIXME is it still needed ?
static ECellDateEditValue *
copy_ecdv (ECellDateEditValue *ecdv)
{
	ECellDateEditValue *new_ecdv;
 *
	new_ecdv = g_new0 (ECellDateEditValue, 1);
	new_ecdv->tt = ecdv ? ecdv->tt : icaltime_null_time ();
	new_ecdv->zone = ecdv ? ecdv->zone : NULL;
 *
	return new_ecdv;
} */

struct _ECalModelComponentPrivate {
	gchar nouse;
};

static void e_cal_model_component_finalize (GObject *object);

static GObjectClass *component_parent_class;

/* Class initialization function for the calendar component object */
static void
e_cal_model_component_class_init (ECalModelComponentClass *class)
{
	GObjectClass *object_class;

	object_class = (GObjectClass *) class;

	component_parent_class = g_type_class_peek_parent (class);

	object_class->finalize = e_cal_model_component_finalize;
}

static void
e_cal_model_component_finalize (GObject *object)
{
	ECalModelComponent *comp_data = E_CAL_MODEL_COMPONENT (object);

	if (comp_data->client) {
		g_object_unref (comp_data->client);
		comp_data->client = NULL;
	}
	if (comp_data->icalcomp) {
		icalcomponent_free (comp_data->icalcomp);
		comp_data->icalcomp = NULL;
	}
	if (comp_data->dtstart) {
		g_free (comp_data->dtstart);
		comp_data->dtstart = NULL;
	}
	if (comp_data->dtend) {
		g_free (comp_data->dtend);
		comp_data->dtend = NULL;
	}
	if (comp_data->due) {
		g_free (comp_data->due);
		comp_data->due = NULL;
	}
	if (comp_data->completed) {
		g_free (comp_data->completed);
		comp_data->completed = NULL;
	}
	if (comp_data->created) {
		g_free (comp_data->created);
		comp_data->created = NULL;
	}
	if (comp_data->lastmodified) {
		g_free (comp_data->lastmodified);
		comp_data->lastmodified = NULL;
	}
	if (comp_data->color) {
		g_free (comp_data->color);
		comp_data->color = NULL;
	}

	/* Chain up to parent's finalize() method. */
	G_OBJECT_CLASS (component_parent_class)->finalize (object);
}

/* Object initialization function for the calendar component object */
static void
e_cal_model_component_init (ECalModelComponent *comp)
{
	comp->dtstart = NULL;
	comp->dtend = NULL;
	comp->due = NULL;
	comp->completed = NULL;
	comp->created = NULL;
	comp->lastmodified = NULL;
	comp->color = NULL;
}

GType
e_cal_model_component_get_type (void)
{
	static GType e_cal_model_component_type = 0;

	if (!e_cal_model_component_type) {
		static GTypeInfo info = {
			sizeof (ECalModelComponentClass),
			(GBaseInitFunc) NULL,
			(GBaseFinalizeFunc) NULL,
			(GClassInitFunc) e_cal_model_component_class_init,
			NULL, NULL,
			sizeof (ECalModelComponent),
			0,
			(GInstanceInitFunc) e_cal_model_component_init
		};
		e_cal_model_component_type = g_type_register_static (G_TYPE_OBJECT, "ECalModelComponent", &info, 0);
	}

	return e_cal_model_component_type;
}

/**
 * e_cal_model_generate_instances_sync
 *
 * cb function is not called with cb_data, but with ECalModelGenerateInstancesData which contains cb_data
 */
void
e_cal_model_generate_instances_sync (ECalModel *model,
                                     time_t start,
                                     time_t end,
                                     ECalRecurInstanceFn cb,
                                     gpointer cb_data)
{
	ECalModelGenerateInstancesData mdata;
	gint i, n;

	n = e_table_model_row_count (E_TABLE_MODEL (model));
	for (i = 0; i < n; i++) {
		ECalModelComponent *comp_data = e_cal_model_get_component_at (model, i);

		mdata.comp_data = comp_data;
		mdata.cb_data = cb_data;

		if (comp_data->instance_start < end && comp_data->instance_end > start)
			e_cal_client_generate_instances_for_object_sync (comp_data->client, comp_data->icalcomp, start, end, cb, &mdata);
	}
}

/**
 * e_cal_model_get_object_array
 */
GPtrArray *
e_cal_model_get_object_array (ECalModel *model)
{
	g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);

	return model->priv->objects;
}

void
e_cal_model_set_instance_times (ECalModelComponent *comp_data,
                                const icaltimezone *zone)
{
	struct icaltimetype start_time, end_time;
	icalcomponent_kind kind;

	kind = icalcomponent_isa (comp_data->icalcomp);
	start_time = icalcomponent_get_dtstart (comp_data->icalcomp);
	end_time = icalcomponent_get_dtend (comp_data->icalcomp);

	if (kind == ICAL_VEVENT_COMPONENT) {
		if (start_time.is_date && icaltime_is_null_time (end_time)) {
			/* If end_time is null and it's an all day event,
			 * just make start_time = end_time so that end_time
			 * will be a valid date
			 */
			end_time = start_time;
			icaltime_adjust (&end_time, 1, 0, 0, 0);
			icalcomponent_set_dtend (comp_data->icalcomp, end_time);
		} else if (start_time.is_date && end_time.is_date &&
			   (icaltime_compare_date_only (start_time, end_time) == 0)) {
			/* If both DTSTART and DTEND are DATE values, and they are the
			 * same day, we add 1 day to DTEND. This means that most
			 * events created with the old Evolution behavior will still
			 * work OK. */
			icaltime_adjust (&end_time, 1, 0, 0, 0);
			icalcomponent_set_dtend (comp_data->icalcomp, end_time);
		}
	}

	if (start_time.zone)
		zone = start_time.zone;
	else {
		icalparameter *param = NULL;
		icalproperty *prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_DTSTART_PROPERTY);

	       if (prop)	{
			param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);

			if (param) {
				const gchar *tzid = NULL;
				icaltimezone *st_zone = NULL;

				tzid = icalparameter_get_tzid (param);
				if (tzid)
					e_cal_client_get_timezone_sync (comp_data->client, tzid, &st_zone, NULL, NULL);

				if (st_zone)
					zone = st_zone;
			}
	       }
	}

	comp_data->instance_start = icaltime_as_timet_with_zone (start_time, zone);

	if (end_time.zone)
		zone = end_time.zone;
	else {
		icalparameter *param = NULL;
		icalproperty *prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_DTSTART_PROPERTY);

	       if (prop)	{
			param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);

			if (param) {
				const gchar *tzid = NULL;
				icaltimezone *end_zone = NULL;

				tzid = icalparameter_get_tzid (param);
				if (tzid)
					e_cal_client_get_timezone_sync (comp_data->client, tzid, &end_zone, NULL, NULL);

				if (end_zone)
					zone = end_zone;
			}
	       }

	}
	comp_data->instance_end = icaltime_as_timet_with_zone (end_time, zone);
}

/**
 * e_cal_model_set_default_time_func:
 * This function will be used when creating new item from the "click-to-add",
 * when user didn't fill a start date there.
 **/
void
e_cal_model_set_default_time_func (ECalModel *model,
                                   ECalModelDefaultTimeFunc func,
                                   gpointer user_data)
{
	g_return_if_fail (E_IS_CAL_MODEL (model));

	model->priv->get_default_time = func;
	model->priv->get_default_time_user_data = user_data;
}