/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/***************************************************************************
 *            kolab-util-calendar-cache.h
 *
 *  2011
 *  Copyright  2011  Silvan Marco Fin
 *  <silvan@kernelconcepts.de>
 ****************************************************************************/

/*
 * 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.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

/*----------------------------------------------------------------------------*/

#include "kolab-util-calendar.h"
#include "kolab-util-calendar-cache.h"

#include <libekolabutil/kolab-util-glib.h>
#include <libekolab/kolab-util-backend.h>
#include <libekolabconv/main/src/evolution/evolution-util.h>

/*----------------------------------------------------------------------------*/

typedef struct _KolabUtilCalendarCachePrivate KolabUtilCalendarCachePrivate;
struct _KolabUtilCalendarCachePrivate
{
	gchar * foo;
};

#define KOLAB_UTIL_CALENDAR_CACHE_PRIVATE(o)  (G_TYPE_INSTANCE_GET_PRIVATE ((o), KOLAB_TYPE_UTIL_CALENDAR_CACHE, KolabUtilCalendarCachePrivate))


G_DEFINE_TYPE (KolabUtilCalendarCache, kolab_util_calendar_cache, G_TYPE_OBJECT)

/*----------------------------------------------------------------------------*/

ECalComponent*
kolab_util_calendar_cache_get_tz_by_id (ECalBackendCache *cache,
                                        const gchar *tzid)
{
	ECalComponent *ecaltz = NULL;
	icalcomponent *icalcomp = NULL;
	const icaltimezone *icaltz = NULL;
	gboolean ok = FALSE;

	g_assert (E_IS_CAL_BACKEND_CACHE (cache));
	g_return_val_if_fail (tzid != NULL, NULL);

	icaltz = e_cal_backend_cache_get_timezone (cache,
	                                           tzid);
	if (icaltz == NULL) {
		g_debug ("%s()[%u]: timezone for \"%s\" not found.",
		         __func__, __LINE__, tzid);
		return NULL;
	}

	ecaltz = e_cal_component_new();
	e_cal_component_set_new_vtype (ecaltz, E_CAL_COMPONENT_TIMEZONE);
	/* Copy the icalcomponent, so that the new created ECalComponent contains
	 * a real copy und doesn't invalidate data on the backend cache on unref.
	 */
	icalcomp = icalcomponent_new_clone (icaltimezone_get_component ((icaltimezone *) icaltz));

	ok = e_cal_component_set_icalcomponent (ecaltz, icalcomp);
	if  (! ok) {
		g_object_unref (ecaltz);
		icalcomponent_free (icalcomp);
		ecaltz = NULL;
		g_warning ("%s[%u]: could not set timezone.",
		           __func__, __LINE__);
	}

	return ecaltz;
}

/**
 * kolab_util_calendar_cache_get_tz:
 * @cache: A Cache.
 * @comp: An ECalComponent (Some calendar entry).
 *.
 * The tzid of the ECalComponent DTSTART property is used to extract timezone
 * information from the supplied backend cache. The ECalComponent returned
 * should be freed, using g_object_unref (), once no longer needed. Note:
 * Events may not provide a timezone/TZID (like UTC)!
 *
 * Returns: An ECalComponent containing a timezone or NULL if none is found..
 */
ECalComponent*
kolab_util_calendar_cache_get_tz (ECalBackendCache *cache,
                                  ECalComponent *comp)
{
	ECalComponent *ecaltz = NULL;
	gchar *tzid = NULL;
	gint field_nr;
	ECalComponentField tzid_search[3] = {
		E_CAL_COMPONENT_FIELD_DTEND,
		E_CAL_COMPONENT_FIELD_DTSTART,
		E_CAL_COMPONENT_FIELD_DUE
	};

	g_assert (E_IS_CAL_BACKEND_CACHE (cache));
	g_assert (E_IS_CAL_COMPONENT (comp));

	/* First of all: This method relies on Evolution already having sent the timezone.
	 * Then retrieve timezone by its tzid and stuff it into another ECalComponent.
	 */
	g_assert (E_IS_CAL_COMPONENT (comp));
	for (field_nr = 0; field_nr < 3; field_nr++) {
		tzid = kolab_util_calendar_get_tzid (comp,
		                                     tzid_search[field_nr]);
		g_debug ("%s()[%u]: %s",
		         __func__, __LINE__, tzid);
		if (tzid != NULL)
			break;
	}
	if (tzid == NULL) {
		/* This may happen, e.g.:
		 *  + All-day-events don't provide TZIDs. */
		return NULL;
	}

	ecaltz = kolab_util_calendar_cache_get_tz_by_id (cache, tzid);
	g_free (tzid);
	return ecaltz;
} /* kolab_util_calendar_cache_get_tz () */

/**
 * kolab_util_calendar_cache_get_object:
 * @cal_cache: A Cache.
 * @koma: A KolabMailAccess object.
 * @uri: An ECalComponent uri string.
 * @uid: The uid to search for.
 * @bulk: Whether or not this is a mass operation.
 * @cancellable: A cancellation stack.
 * @error: GError placeholder.
 *
 * Retrieves the the object referenced by @uid from the given @cal_cache. If
 * none is found, NULL is returned.
 *
 * Returns: A new ECalComponent containing the object.
 *          Unref if no longer needed.
 **/
ECalComponent*
kolab_util_calendar_cache_get_object (ECalBackendCache *cal_cache,
                                      KolabMailAccess *koma,
                                      const gchar *uri,
                                      const gchar *uid,
                                      gboolean bulk,
                                      GCancellable *cancellable,
                                      GError **error)
{
	const KolabMailHandle *kmh = NULL;
	ECalComponent *comp = NULL;
	ECalComponent *tz = NULL;
	GError *tmp_error = NULL;
	gchar *sourcename = NULL;
	gchar *tzid = NULL;
	gboolean ok = FALSE;

	g_assert (E_IS_CAL_BACKEND_CACHE (cal_cache));
	g_assert (KOLAB_IS_MAIL_ACCESS (koma));
	g_assert (uri != NULL);
	g_assert (uid != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	sourcename = kolab_util_backend_get_relative_path_from_uri (uri);

	kmh = kolab_mail_access_get_handle (koma,
	                                    uid,
	                                    sourcename,
	                                    cancellable,
	                                    &tmp_error);
	g_free (sourcename);
	if (kmh == NULL) {
		/* empty object, could be "nothing found" */
		if (tmp_error != NULL) {
			/* this means, something went wrong */
			g_propagate_error (error, tmp_error);
		}
		return NULL;
	}

	ok = kolab_mail_access_retrieve_handle (koma,
	                                        kmh,
	                                        bulk,
	                                        cancellable,
	                                        &tmp_error);
	if (! ok) {
		g_propagate_error (error, tmp_error);
		return NULL;
	}
	comp = kolab_mail_handle_get_ecalcomponent (kmh);

	/* If there is timezone information, we extract it, too and put it in
	 * the cache, so that at any time after a call to cache_get_object()
	 * the timezone, if any, may be retrieved from the cache. */
	tzid = kolab_util_calendar_get_tzid (comp,
	                                     E_CAL_COMPONENT_FIELD_DTSTART);
	if (tzid != NULL) {
		icaltimezone *icaltz = NULL;
		/* remember, this only works, after kolab_mail_access_retrieve_handle () */
		tz = kolab_mail_handle_get_timezone (kmh);
		if (tz != NULL) {
			icaltz = ecalcomponent_tz_get_icaltimezone (tz);
			e_cal_backend_cache_put_timezone (cal_cache,
			                                  icaltz);
			g_free (tzid);
			g_object_unref (tz);
		}
	}

	return comp;
} /* kolab_util_calendar_cache_get_object () */

gboolean
kolab_util_calendar_cache_update_object (ECalBackendCache *cache,
                                         KolabMailAccess *koma,
                                         const gchar *uri,
                                         const gchar *uid,
                                         gboolean bulk,
                                         GCancellable *cancellable,
                                         GError **error)
{
	ECalComponent *ecalcomp = NULL;

	g_assert (E_IS_CAL_BACKEND_CACHE (cache));
	g_assert (KOLAB_IS_MAIL_ACCESS (koma));
	g_assert (uri != NULL);
	g_assert (uid != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	ecalcomp = kolab_util_calendar_cache_get_object (cache,
	                                                 koma,
	                                                 uri,
	                                                 uid,
	                                                 bulk,
	                                                 cancellable,
	                                                 error);
	if (ecalcomp == NULL)
		return FALSE;
	g_object_unref (ecalcomp);
	return TRUE;
} /* kolab_util_calendar_cache_update_on_object () */

/**
 * kolab_util_calendar_cache_remove_instance:
 * @cal_cache: An ECalBackendCache.
 * @mod: CalObjModType to apply to the remove request.
 * @oldcomp: The object before the removal took place.
 * @uid: The UID of the recurring event.
 * @rid: The recurrence ID of the recurrence.
 *
 * Removes an instance of an event with recurrence.
 *
 * Returns: The modified component.
 **/
ECalComponent*
kolab_util_calendar_cache_remove_instance (ECalBackendCache *cal_cache,
                                           CalObjModType mod,
                                           ECalComponent *oldcomp,
                                           const gchar *uid,
                                           const gchar *rid)
{
	ECalComponent *newcomp = NULL;
	icalcomponent *icalcomp = NULL;
	gboolean ok = FALSE;

	g_debug ("%s()[%u] called.", __func__, __LINE__);

	g_assert (E_IS_CAL_BACKEND_CACHE (cal_cache));
	g_assert (E_IS_CAL_COMPONENT (oldcomp));
	g_assert (uid != NULL);
	g_assert (rid != NULL);

	icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (oldcomp));
	e_cal_util_remove_instances (icalcomp,
	                             icaltime_from_string (rid),
	                             mod);

	newcomp = e_cal_component_new ();
	e_cal_component_set_icalcomponent (newcomp, icalcomp);
	ok = e_cal_backend_cache_remove_component (cal_cache,
	                                           uid,
	                                           NULL);
	if (! ok) {
		g_debug (" + object with uid %s not found in cache", uid);
	}
	ok = e_cal_backend_cache_put_component (cal_cache,
	                                        newcomp);
	if (! ok) {
		/* FIXME this is an error. If cache is still
		 * in use, a GError needs to be set here
		 */
		g_debug (" + new component could not be placed into cache");
	}
	return newcomp;
} /* kolab_util_calendar_cache_remove_instance () */

gboolean
kolab_util_calendar_cache_update_on_query (ECalBackendCache *cache,
                                           KolabMailAccess *koma,
                                           const gchar *query,
                                           const gchar *uri,
                                           GCancellable *cancellable,
                                           GError **error)
{
	GList *changed_uids = NULL;
	gchar *sourcename = NULL;
	GError *tmp_error = NULL;

	g_debug ("%s()[%u] called.", __func__, __LINE__);

	g_assert (E_IS_CAL_BACKEND_CACHE (cache));
	g_assert (KOLAB_IS_MAIL_ACCESS (koma));
	/* query may be NULL */
	g_assert (uri != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	sourcename = kolab_util_backend_get_relative_path_from_uri (uri);

	/* Backend cache no longer in use for PIM objects -
	 * we can just free() the result (but must fetch
	 * it nonetheless)
	 */
	changed_uids = kolab_mail_access_query_changed_uids (koma,
	                                                     sourcename,
	                                                     query,
	                                                     cancellable,
	                                                     &tmp_error);
	if (tmp_error != NULL) {
		g_propagate_error (error, tmp_error);
		g_free (sourcename);
		return FALSE;
	}
	if (changed_uids != NULL)
		g_debug (" + changed_uids count: %u",
		         g_list_length (changed_uids));
	else
		g_debug (" + changed_uids empty!");

	kolab_util_glib_glist_free (changed_uids);
	return TRUE;
}

/**
 * kolab_util_calendar_cache_assure_uid_on_ecalcomponent:
 * @cache: An ECalBackendCache.
 * @koma: A KolabMailAccess instance.
 * @uri: An ECalComponent uri string.
 * @ecalcomp: An ECalComponent.
 * @bulk: Whether or not this is a mass operation.
 * @cancellable: A cancellation stack.
 * @error: A GError placeholder.
 *
 * Sets a new uid to ecalcomp and assures, that it is not used in @koma, so far.
 *
 * Returns: On Success TRUE is returned.
 */
gboolean
kolab_util_calendar_cache_assure_uid_on_ecalcomponent (ECalBackendCache *cache,
                                                       KolabMailAccess *koma,
                                                       ECalComponent *ecalcomp,
                                                       const gchar *uri,
                                                       gboolean bulk,
                                                       GCancellable *cancellable,
                                                       GError **error)
{
	ECalComponent *tmp_comp = NULL;
	GError *tmp_error = NULL;
	const gchar *tmp_uid = NULL;
	gchar *uid = NULL;
	KolabSettingsHandler *ksettings = NULL;

	g_assert (E_IS_CAL_BACKEND_CACHE (cache));
	g_assert (KOLAB_IS_MAIL_ACCESS (koma));
	g_assert (E_IS_CAL_COMPONENT (ecalcomp));
	g_assert (uri != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	ksettings = kolab_mail_access_get_settings_handler (koma);
	if (ksettings == NULL) {
		return FALSE;
	}

	/* Extract uid from the ECalComponent. If it has none, generate one */
	e_cal_component_get_uid (ecalcomp, &tmp_uid);
	uid = g_strdup (tmp_uid);
	if (uid == NULL)
		uid = e_cal_component_gen_uid();
	for (;;) {
		/* remember, this has to be called with a non-empty uid */
		tmp_comp = kolab_util_calendar_cache_get_object (cache,
		                                                 koma,
		                                                 uri,
		                                                 uid,
		                                                 bulk,
		                                                 cancellable,
		                                                 &tmp_error);
		if (tmp_error != NULL) {
			g_propagate_error (error, tmp_error);
			g_free (uid);
			g_object_unref (ksettings);
			return FALSE;
		}
		if (tmp_comp == NULL) {
			e_cal_component_set_uid (ecalcomp, uid);
			g_free (uid);
			break;
		}
		else {
			g_free (uid);
			uid = e_cal_component_gen_uid();
		}
		g_object_unref (tmp_comp);
	}
	g_object_unref (ksettings);
	return TRUE;
} /* kolab_util_calendar_cache_assure_uid_on_ecalcomponent () */

static void
kolab_util_calendar_cache_init (KolabUtilCalendarCache *object)
{
	/* TODO: Add initialization code here */
	(void)object;
}

static void
kolab_util_calendar_cache_dispose (GObject *object)
{
	/* TODO_ Add dispose code here */
	G_OBJECT_CLASS (kolab_util_calendar_cache_parent_class)->dispose (object);
}

static void
kolab_util_calendar_cache_finalize (GObject *object)
{
	/* TODO: Add finalization code here */

	G_OBJECT_CLASS (kolab_util_calendar_cache_parent_class)->finalize (object);
}

static void
kolab_util_calendar_cache_class_init (KolabUtilCalendarCacheClass *klass)
{
	GObjectClass* object_class = G_OBJECT_CLASS (klass);
	/* GObjectClass* parent_class = G_OBJECT_CLASS (klass); */

	g_type_class_add_private (klass, sizeof (KolabUtilCalendarCachePrivate));

	object_class->dispose = kolab_util_calendar_cache_dispose;
	object_class->finalize = kolab_util_calendar_cache_finalize;
}

/*----------------------------------------------------------------------------*/
