/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/***************************************************************************
 *            kolab-util-calendar.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 <libecal/e-cal.h>
#include <libedata-cal/e-data-cal-types.h>

#include <libekolabutil/camel-system-headers.h>
#include <libekolab/kolab-settings-handler.h>
#include <libekolab/kolab-mail-access.h>

#include "kolab-util-calendar.h"

/*----------------------------------------------------------------------------*/
/* internal statics */

static KolabUtilHttpJob*
kolab_util_calendar_create_http_request (KolabSettingsHandler *ksettings,
                                         const gchar *path,
                                         GError **error)
{
	CamelURL *uri = NULL;
	const gchar *url = NULL;
	const gchar *user = NULL;
	const gchar *passwd = NULL;
	guint tls_variant = KOLAB_TLS_VARIANT_DEFAULT;
	KolabUtilHttpJob *job = NULL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (ksettings));
	g_assert (path != NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	url = kolab_settings_handler_get_char_field (ksettings,
	                                             KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_URI,
	                                             &tmp_err);
	if (url == NULL) {
		g_propagate_error (error, tmp_err);
		return NULL;
	}
	tls_variant = kolab_settings_handler_get_uint_field (ksettings,
	                                                     KOLAB_SETTINGS_HANDLER_UINT_FIELD_TLS_VARIANT,
	                                                     &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (error, tmp_err);
		return NULL;
	}
	uri = camel_url_new (url, &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (error, tmp_err);
		return NULL;
	}
	if (uri == NULL) {
		/* FIXME is this an error condition? */
		return NULL;
	}
	switch (tls_variant) {
	case KOLAB_TLS_VARIANT_NONE:
		camel_url_set_protocol (uri, "http");
		break;
	default:
		camel_url_set_protocol (uri, "https");
	}
	camel_url_set_path (uri, path);
	user = kolab_settings_handler_get_char_field (ksettings,
	                                              KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_USER_NAME,
	                                              &tmp_err);
	if (user == NULL) {
		g_propagate_error (error, tmp_err);
		camel_url_free (uri);
		return NULL;
	}
	camel_url_set_user (uri, user);
	passwd = kolab_settings_handler_get_char_field (ksettings,
	                                                KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_USER_PASSWORD,
	                                                &tmp_err);
	if (passwd == NULL) {
		g_warning ("%s()[%u]: no password supplied! (%s)",
		           __func__, __LINE__, tmp_err->message);
		g_error_free (tmp_err);
	}

	job = kolab_util_http_job_new ();
	job->url = uri;
	job->passwd = g_strdup (passwd); /* no longer carried by CamelURL */

	return job;
} /* kolab_util_calendar_create_http_request () */

static KolabUtilHttpJob*
kolab_util_calendar_create_pfb_trigger (KolabSettingsHandler *ksettings,
                                        const gchar *sourcename,
                                        GError **error)
{
	KolabUtilHttpJob *job = NULL;
	gchar *path = NULL;
	const gchar *username = NULL;
	gint source_offset = 0;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (ksettings));
	g_assert (sourcename != NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	username = kolab_settings_handler_get_char_field (ksettings,
	                                                  KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_USER_NAME,
	                                                  &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (error, tmp_err);
		return NULL;
	}

	if (strncmp (username, "INBOX/", 6))
		source_offset = 6;
	path = g_strdup_printf ("/freebusy/trigger/%s/%s.pfb",
	                        username, sourcename+source_offset);

	job = kolab_util_calendar_create_http_request (ksettings,
	                                               path,
	                                               &tmp_err);
	g_free (path);
	if (job == NULL) {
		g_propagate_error (error, tmp_err);
		return NULL;
	}
	return job;
} /* kolab_util_calendar_create_pfb_trigger () */

static KolabUtilHttpJob*
kolab_util_calendar_create_xfb_request (KolabSettingsHandler *ksettings,
                                        gchar *query,
                                        GError **error)
{
	KolabUtilHttpJob *job = NULL;
	gchar *path = NULL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (ksettings));
	g_assert (query != NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	path = g_strdup_printf ("/freebusy/%s.ifb", query);

	job = kolab_util_calendar_create_http_request (ksettings,
	                                               path,
	                                               &tmp_err);
	g_free (path);
	if (job == NULL) {
		g_propagate_error (error, tmp_err);
		g_warning ("%s()[%u] call to kolab_util_calendar_create_http_request() returned NULL!",
		           __func__, __LINE__);
		return NULL;
	}
	return job;
} /* kolab_util_calendar_create_xfb_request () */

/*----------------------------------------------------------------------------*/
/* public API */

/**
 * kolab_util_calendar_get_tzid:
 * @comp: An ECalComponent to derive the tzid from.
 * @from: field to get the tzid from.
 *
 * The ECalComponentDateTime Struct from an ECalComponents dtstart field or
 * dtend field is queried for its tzid.
 *
 * Returns: A newly allocated tzid string (g_free() after use)
 *          or NULL if no ID was found.
 */
gchar*
kolab_util_calendar_get_tzid (ECalComponent *comp,
                              ECalComponentField from)
{
	ECalComponentDateTime *dt = NULL;
	gchar *tzid = NULL;

	g_assert (E_IS_CAL_COMPONENT (comp));

	dt = g_new0 (ECalComponentDateTime, 1);
	switch (from) {
	case E_CAL_COMPONENT_FIELD_DTSTART:
		e_cal_component_get_dtstart (comp, dt);
		break;
	case E_CAL_COMPONENT_FIELD_DTEND:
		e_cal_component_get_dtend (comp, dt);
		break;
	case E_CAL_COMPONENT_FIELD_DUE:
		e_cal_component_get_due (comp, dt);
		break;
	default:
		g_warning ("%s()[%u]: TZID from %u not supported.",
		           __func__, __LINE__, from);
		return NULL;
	}

	if (dt == NULL) {
		g_warning ("%s()[%u]: Found ECalComponent without ECalComponentField %u.",
		           __func__, __LINE__, from);
		g_assert_not_reached();
	}

	tzid = g_strdup (dt->tzid);
	e_cal_component_free_datetime (dt);
	g_free (dt);
	return tzid;
} /* kolab_util_calendar_get_tzid () */

gboolean
kolab_util_calendar_toggle_pfb_trigger (KolabSettingsHandler *ksettings,
                                        const gchar *sourcename,
                                        GError **error)
{
	/* Keep running on errors.
	 * If an error occured, internal_state is set to false as indicator.
	 */
	KolabUtilHttpJob *job = NULL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (ksettings));
	g_assert (sourcename != NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	job = kolab_util_calendar_create_pfb_trigger (ksettings,
	                                              sourcename,
	                                              &tmp_err);
	if (job == NULL) {
		g_propagate_error (error, tmp_err);
		g_warning ("%s()[%u] error: could not create F/B toggle, giving up on %s.",
		           __func__, __LINE__, sourcename);
		return FALSE;
	}
	job->buffer = g_byte_array_new ();
	(void)kolab_util_http_get (job, &tmp_err);
	kolab_util_http_job_free (job);

	if (tmp_err != NULL) {
		g_propagate_error (error, tmp_err);
		return FALSE;
	}
	return TRUE;
} /* kolab_util_calendar_toggle_pfb_trigger () */

KolabUtilHttpJob*
kolab_util_calendar_retrieve_xfb (KolabSettingsHandler *ksettings,
                                  gchar *query,
                                  GError **error)
{
	KolabUtilHttpJob *job = NULL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (ksettings));
	g_assert (query != NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	job = kolab_util_calendar_create_xfb_request (ksettings,
	                                              (gchar *) query,
	                                              &tmp_err);
	if (job == NULL) {
		g_propagate_error (error, tmp_err);
		g_warning ("%s()[%u] error creating job, giving up on %s.",
		           __func__, __LINE__, (gchar *) query);
		return NULL;
	}

	job->buffer = g_byte_array_new ();
	(void)kolab_util_http_get (job, &tmp_err);
	if (tmp_err != NULL) {
		/* might happen, perhaps the server did not now about the user. */
		kolab_util_http_job_free (job);
		g_propagate_error (error, tmp_err);
		return NULL;
	}

	return job;
} /* kolab_util_calendar_retrieve_xfb () */

gboolean
kolab_util_calendar_store (ECalComponent *ecalcomp,
                           ECalComponent *ecaltz,
                           ECalComponent *default_tz,
                           KolabMailAccess *koma,
                           const gchar *uri,
                           GCancellable *cancellable,
                           GError **error)
{
	gchar *sourcename = NULL;
	KolabMailHandle *kmh = NULL;
	KolabSettingsHandler *ksettings = NULL;
	KolabMailAccessOpmodeID mode = KOLAB_MAIL_ACCESS_OPMODE_INVAL;
	gboolean do_trigger = FALSE;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (E_IS_CAL_COMPONENT (ecalcomp));
	/* ecaltz may be NULL */
	(void)default_tz; /* FIXME needed? */ /* default_tz may be NULL */
	g_assert (KOLAB_IS_MAIL_ACCESS (koma));
	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) {
		/* FIXME: set GError here */
		return FALSE;
	}

	sourcename = kolab_util_backend_get_relative_path_from_uri (uri);

	kolab_util_backend_modtime_set_on_ecalcomp (ecalcomp);
	kmh = kolab_mail_handle_new_from_ecalcomponent (ecalcomp,
	                                                ecaltz);
	ok =  kolab_mail_access_store_handle (koma,
	                                      kmh,
	                                      sourcename,
	                                      cancellable,
	                                      &tmp_err);
	if (! ok)
		goto cleanup;

	mode = kolab_mail_access_get_opmode (koma,
	                                     &tmp_err);
	if (tmp_err != NULL)
		goto cleanup;
	if (mode < KOLAB_MAIL_ACCESS_OPMODE_ONLINE) {
		/* In OFFLINE state, the triggers are generated during
		 * changing to ONLINE state in e_cal_backend_set_mode ().
		 */
		goto cleanup;
	}

	do_trigger = kolab_mail_access_source_fbtrigger_needed (koma,
	                                                        sourcename,
	                                                        &tmp_err);
	if (tmp_err != NULL)
		goto cleanup;

	if (do_trigger == TRUE) {
		(void) kolab_util_calendar_toggle_pfb_trigger (ksettings,
		                                               sourcename,
		                                               &tmp_err);
		if (tmp_err != NULL) {
			/* if triggering the XFB generation fails,
			 * this is not a critical failure regarding
			 * storing the PIM object
			 */
			g_warning ("%s()[%u] %s",
			           __func__, __LINE__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
	}

 cleanup:
	g_object_unref (ksettings);
	g_free (sourcename);
	if (tmp_err != NULL) {
		g_propagate_error (error, tmp_err);
		return FALSE;
	}
	return TRUE;
} /* kolab_util_calendar_store () */

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

#if 0 /* FIXME old */
ECalBackendSyncStatus
kolab_util_calendar_map_error (GError *error)
{
	ECalBackendSyncStatus status = GNOME_Evolution_Calendar_Success;

	if (error == NULL)
		return GNOME_Evolution_Calendar_Success;

	switch (error->code) {
	case KOLAB_BACKEND_ERROR_SYNC_NOTSTORED:
		status = GNOME_Evolution_Calendar_Success;
		break;
	case KOLAB_BACKEND_ERROR_NOTFOUND:
		status = GNOME_Evolution_Calendar_ObjectNotFound;
		break;
	case KOLAB_BACKEND_ERROR_CONTEXT_MISUSE:
	case KOLAB_BACKEND_ERROR_INFODB_NOFOLDER:
		status = GNOME_Evolution_Calendar_NoSuchCal;
		break;
	case KOLAB_BACKEND_ERROR_DATATYPE_EVOLUTION:
	case KOLAB_BACKEND_ERROR_DATATYPE_KOLAB:
		status = GNOME_Evolution_Calendar_InvalidObject;
	default:
		status = GNOME_Evolution_Calendar_OtherError;
	}

	g_debug ("%s()[%u] ECalBackendSyncStatus: %i", __func__, __LINE__, status);
	return status;
}
#endif

/**
 * kolab_util_calendar_extract:
 * @icalcomp: An icalcomponent.
 * @source_type: The data type to process.
 * @ecalcomp: An ECalComponent to return the extracted values to.
 * @tzcomp: An ECaComponent to return contained timezone data.
 *
 * Extract supported information from icalcomponent and return them through
 * ecalcomp and tzcomp.
 *
 * Returns: if the given component type could be extracted successfully into ecalcomp + tzcomp;
 */
gboolean
kolab_util_calendar_extract (icalcomponent *icalcomp,
                             ECalSourceType source_type,
                             ECalComponent **ecalcomp,
                             ECalComponent **tzcomp,
                             GError **error)
{
	ECalComponent *comp = NULL;
	ECalComponent *tz = NULL;
	icalcomponent *icalsub = NULL;
	icalcomponent *icaltz = NULL;
	icalcomponent_kind icalkind;
	GError *tmp_err = NULL;

	/* Caution: Let this function return errors
	 *          only which are well-known EDataCal
	 *          errors, since the error set here
	 *          is propagated directly (no mapping)
	 *          in the ECalBackendKolab
	 */

	/* TODO Function argument assertions */

	switch (source_type) {
	case E_CAL_SOURCE_TYPE_EVENT:
		icalkind = ICAL_VEVENT_COMPONENT;
		break;
	case E_CAL_SOURCE_TYPE_TODO:
		icalkind = ICAL_VTODO_COMPONENT;
		break;
	case E_CAL_SOURCE_TYPE_JOURNAL:
		icalkind = ICAL_VJOURNAL_COMPONENT;
		break;
	default:
		/* can't happen */
		g_assert_not_reached ();
	}

	icalsub = icalcomponent_get_first_component (icalcomp,
	                                             icalkind);
	if (icalsub == NULL) {
		tmp_err = e_data_cal_create_error (InvalidObject, NULL);
		g_propagate_error (error, tmp_err);
		return FALSE;
	}

	comp = e_cal_component_new ();
	e_cal_component_set_icalcomponent (comp, icalsub);
	*ecalcomp = e_cal_component_clone (comp);
	g_object_unref (comp);

	icaltz = icalcomponent_get_first_component (icalcomp,
	                                            ICAL_VTIMEZONE_COMPONENT);
	if (icaltz == NULL) {
		/* no TZ - no problem */
		tzcomp = NULL;
	}
	else {
		/* Create a new ECalComponent from the
		 * ical timezone component.
		 */
		tz = e_cal_component_new ();
		e_cal_component_set_icalcomponent (tz,
		                                   icaltz);
		*tzcomp = e_cal_component_clone (tz);
		g_object_unref (tz);
	}

	return TRUE;
} /* kolab_util_calendar_extract () */

void
kolab_util_calendar_err_to_edb_err (GError **e_err,
                                    const GError *k_err,
                                    const gchar *func,
                                    guint line)
{
	EDataCalCallStatus status = OtherError;
	GError *tmp_err = NULL;

	g_return_if_fail (e_err == NULL || *e_err == NULL);
	g_return_if_fail (k_err != NULL);

	g_warning ("%s()[%u]: '%s', Code %i, Domain '%s'",
	           func, line, k_err->message, k_err->code,
	           g_quark_to_string (k_err->domain));

	if (!e_err)
		return;

	if (g_error_matches (k_err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
		g_propagate_error (e_err, g_error_copy (k_err));
		return;
	}

	/* TODO
	 *
	 * Need to implement a more elaborated error mapping here.
	 * This function maps Kolab engine errors only. Errors can
	 * originate from the camel/ as well as from the libekolab/
	 * libs. These yield errors in various domains. The following
	 * EDataCalCallStatus (other than OTHER_ERROR) are candidates
	 * for being populated from certain k_err's:
	 *
	 * RepositoryOffline
	 * PermissionDenied
	 * InvalidRange
	 * ObjectNotFound
	 * InvalidObject
	 * ObjectIdAlreadyExists
	 * AuthenticationFailed
	 * AuthenticationRequired
	 * UnsupportedField
	 * UnsupportedMethod
	 * TLSNotAvailable
	 * NoSuchCal
	 * UnknownUser
	 * SearchSizeLimitExceeded
	 * InvalidQuery
	 * QueryRefused
	 * CouldNotCancel
	 * InvalidServerVersion
	 * NotSupported
	 * NotOpened
	 *
	 * Depending on k_err->domain and k_err->code, we can map
	 * to a fitting EDataCalCallStatus. More than one combination
	 * of k_err->domain and k_err->code may map to one specific
	 * EDataCalCallStatus.
	 */

	tmp_err = e_data_cal_create_error (status,
	                                   k_err->message);
	g_propagate_error (e_err, tmp_err);
}
