/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/***************************************************************************
 *            kolab-mail-imap-client.c
 *
 *  Fri Feb 04 11:19:23 2011
 *  Copyright  2011  Christian Hilberg
 *  <hilberg@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 <config.h>

#include <glib/gi18n-lib.h>

#include <camel/camel-kolab-imapx-provider.h>
#include <camel/camel-kolab-imapx-store.h>
#include <camel/camel-kolab-imapx-folder.h>
#include <camel/camel-kolab-session.h>

#include <libekolabutil/camel-system-headers.h>
#include <libekolabutil/kolab-util-glib.h>
#include <libekolabutil/kolab-util-error.h>
#include <libekolabutil/kolab-util-camel.h>

#include "kolab-util-backend.h"
#include "kolab-mail-summary.h"
#include "kolab-types.h"

#include "kolab-mail-imap-client.h"
#include "kolab-mail-handle-friend.h"

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

/* TODO
 *
 * setup
 * - CamelSession
 * - CamelStore
 * - (CamelProvider not needed)
 *
 * notes
 * - "camel_session_get_store connects us, which we don't want to do on startup"
 *   --> call camel_session_get_service (session, uri, CAMEL_PROVIDER_STORE, ex)
 *       and cast to CAMEL_STORE
 * - then again, we know the store we have - do need to register it with
 *   the CamelSession in the first place?
 *
 */

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

typedef struct _KolabMailImapClientPrivate KolabMailImapClientPrivate;
struct _KolabMailImapClientPrivate
{
	KolabSettingsHandler *ksettings;
	KolabMailMimeBuilder *mimebuilder;
	gboolean is_up;
	gboolean is_online;
	KolabFolderContextID context;

	CamelKolabSession *session;
	CamelKolabIMAPXStore *store;
	CamelFolder *folder;
	gchar *foldername;
};

#define KOLAB_MAIL_IMAP_CLIENT_PRIVATE(obj)  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), KOLAB_TYPE_MAIL_IMAP_CLIENT, KolabMailImapClientPrivate))

G_DEFINE_TYPE (KolabMailImapClient, kolab_mail_imap_client, G_TYPE_OBJECT)

/*----------------------------------------------------------------------------*/
/* object/class init */

static void
kolab_mail_imap_client_init (KolabMailImapClient *object)
{
	KolabMailImapClient *self = NULL;
	KolabMailImapClientPrivate *priv = NULL;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (object));

	self = object;
	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	priv->ksettings = NULL;
	priv->mimebuilder = NULL;
	priv->is_up = FALSE;
	priv->is_online = FALSE;
	priv->context = KOLAB_FOLDER_CONTEXT_INVAL;

	priv->session = NULL;
	priv->store = NULL;
	priv->folder = NULL;
	priv->foldername = NULL;
}

static void
kolab_mail_imap_client_dispose (GObject *object)
{
	KolabMailImapClient *self = NULL;
	KolabMailImapClientPrivate *priv = NULL;

	self = KOLAB_MAIL_IMAP_CLIENT (object);
	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	if (priv->folder != NULL)
		g_object_unref (priv->folder);

	if (priv->store != NULL)
		g_object_unref (priv->store);

	if (priv->session != NULL)
		g_object_unref (priv->session);

	if (priv->ksettings != NULL) /* ref'd in configure() */
		g_object_unref (priv->ksettings);

	if (priv->mimebuilder != NULL)
		g_object_unref (priv->mimebuilder);

	priv->folder = NULL;
	priv->session = NULL;
	priv->store = NULL;
	priv->ksettings = NULL;
	priv->mimebuilder = NULL;

	G_OBJECT_CLASS (kolab_mail_imap_client_parent_class)->dispose (object);
}

static void
kolab_mail_imap_client_finalize (GObject *object)
{
	KolabMailImapClient *self = NULL;
	KolabMailImapClientPrivate *priv = NULL;

	self = KOLAB_MAIL_IMAP_CLIENT (object);
	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	if (priv->foldername != NULL)
		g_free (priv->foldername);

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

static void
kolab_mail_imap_client_class_init (KolabMailImapClientClass *klass)
{
	GObjectClass* object_class = G_OBJECT_CLASS (klass);
	/* GObjectClass* parent_class = G_OBJECT_CLASS (klass); */

	g_type_class_add_private (klass, sizeof (KolabMailImapClientPrivate));

	object_class->dispose = kolab_mail_imap_client_dispose;
	object_class->finalize = kolab_mail_imap_client_finalize;
}

/*----------------------------------------------------------------------------*/
/* camel helpers */

static CamelFolder*
kolab_mail_imap_client_camel_get_folder (KolabMailImapClient *self,
                                         const gchar *foldername,
                                         GCancellable *cancellable,
                                         GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	CamelFolder *folder = NULL;
	GError *tmp_err = NULL;
	CamelStoreGetFolderFlags flags = 0; /* TODO check which flags to set */

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert (foldername != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	/* return cached CamelFolder, if it exists and is the required one */
	if (priv->foldername != NULL) {
		if (g_strcmp0 (priv->foldername, foldername) == 0) {
			g_object_ref (priv->folder);
			return priv->folder;
		}
	}

	/* get new CamelFolder */
	folder = camel_store_get_folder_sync (CAMEL_STORE (priv->store),
	                                      foldername,
	                                      flags,
	                                      cancellable,
	                                      &tmp_err);
	if (tmp_err != NULL) {
		if (folder != NULL)
			g_object_unref (folder);
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	if (folder == NULL) {
		g_set_error (err,
		             KOLAB_CAMEL_ERROR,
		             KOLAB_CAMEL_ERROR_GENERIC,
		             _("Cannot get Camel folder for path '%s'"),
		             foldername);
		return NULL;
	}

	/* bind new cached folder object */
	if (priv->foldername != NULL) {
		g_free (priv->foldername);
		g_object_unref (priv->folder);
	}
	priv->foldername = g_strdup (foldername);
	priv->folder = folder;
	g_object_ref (priv->folder);

	return priv->folder;
}

static GHashTable*
kolab_mail_imap_client_camel_gen_summaries (KolabMailImapClient *self,
                                            const gchar *foldername,
                                            KolabFolderTypeID foldertype,
                                            KolabFolderContextID foldercontext,
                                            GCancellable *cancellable,
                                            GError **err)
{
	/* KolabMailImapClientPrivate *priv = NULL; */
	CamelFolder *folder = NULL;
	GHashTable *summaries = NULL;
	GPtrArray *imap_uids = NULL;
	gboolean have_entries = FALSE;
	guint ii = 0;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert (foldername != NULL);
	g_assert ((foldertype >= KOLAB_FOLDER_TYPE_EVENT) &&
	          (foldertype < KOLAB_FOLDER_LAST_TYPE));
	g_assert ((foldercontext >= KOLAB_FOLDER_CONTEXT_CALENDAR) &&
	          (foldercontext < KOLAB_FOLDER_LAST_CONTEXT));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	/* priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self); */
	folder =  kolab_mail_imap_client_camel_get_folder (self,
	                                                   foldername,
	                                                   cancellable,
	                                                   &tmp_err);
	if (folder == NULL) {
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	/* get folder message info */
	imap_uids = camel_folder_get_uids (folder);
	if (imap_uids == NULL) {
		g_object_unref (folder);
		g_debug ("%s: empty folder (%s)", __func__, foldername);
		return NULL;
	}

	summaries = g_hash_table_new_full (g_str_hash,
	                                   g_str_equal,
	                                   g_free,
	                                   kolab_mail_summary_gdestroy);

	/* get the subject lines from the message info array
	 * (these hold the Kolab PIM object UID)
	 */
	for (ii = 0; ii < imap_uids->len; ii++) {
		const gchar *imapuid = NULL;
		const gchar *subject = NULL;
		CamelMessageInfo *mi = NULL;
		KolabMailSummary *summary = NULL;

		imapuid = (const gchar *)g_ptr_array_index (imap_uids, ii);
		if (imapuid == NULL)
			continue;
		mi = camel_folder_get_message_info (folder, imapuid);
		if (mi == NULL)
			continue;
		subject = camel_message_info_subject (mi);
		camel_folder_free_message_info (folder, mi);
		/* cannot go without Kolab UID */
		if (subject == NULL)
			continue;
		if (g_strcmp0 (subject, "") == 0)
			continue;
		/* Check for UID duplicates in folder
		 *
		 * This is an error condition actually and should never
		 * happen. We'll just take the first occurrence and skip
		 * subsequent UID duplicates, if any.
		 *
		 * CAUTION this only validates that there are no Kolab UID
		 * duplicates within one folder. A global check must be
		 * done elsewhere (e.g. in KolabMailInfoDb context)
		 */
		summary = g_hash_table_lookup (summaries, subject);
		if (summary != NULL) {
			g_warning ("%s: got duplicate UID (%s) in folder (%s), skipping",
			           __func__, subject, foldername);
			continue;
		}

		summary = kolab_mail_summary_new ();
		kolab_mail_summary_set_char_field (summary,
		                                   KOLAB_MAIL_SUMMARY_CHAR_FIELD_KOLAB_UID,
		                                   g_strdup (subject));
		kolab_mail_summary_set_char_field (summary,
		                                   KOLAB_MAIL_SUMMARY_CHAR_FIELD_IMAP_UID,
		                                   g_strdup (imapuid));
		kolab_mail_summary_set_uint_field (summary,
		                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_FOLDER_TYPE,
		                                   foldertype);
		kolab_mail_summary_set_uint_field (summary,
		                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_FOLDER_CONTEXT,
		                                   foldercontext);
		kolab_mail_summary_set_uint_field (summary,
		                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_LOCATION,
		                                   KOLAB_OBJECT_CACHE_LOCATION_IMAP);
		/* TODO check whether more details need to be set */

		g_hash_table_insert (summaries, g_strdup (subject), summary);
		have_entries = TRUE;
	}

	camel_folder_free_uids (folder, imap_uids);
	g_object_unref (folder);

	if (! have_entries) {
		g_hash_table_destroy (summaries);
		return NULL;
	}

	return summaries;
}

static CamelMimeMessage*
kolab_mail_imap_client_camel_get_msg_imap_uid (KolabMailImapClient *self,
                                               const gchar *foldername,
                                               const gchar *imap_uid,
                                               const gchar *kolab_uid,
                                               GCancellable *cancellable,
                                               GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	CamelFolder *folder = NULL;
	CamelMimeMessage *message = NULL;
	CamelMessageInfo *mi = NULL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert (foldername != NULL);
	g_assert (imap_uid != NULL);
	/* kolab_uid may be NULL */
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	folder = kolab_mail_imap_client_camel_get_folder (self,
	                                                  foldername,
	                                                  cancellable,
	                                                  &tmp_err);
	if (folder == NULL) {
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	/* we need to check for a valid CamelMessageInfo in any case,
	 * this cannot be done solely if kolab_uid != NULL (because it
	 * safeguards us against no-longer existing imap_uids)
	 */
	mi = camel_folder_get_message_info (folder, imap_uid);
	if (mi == NULL) {
		g_object_unref (folder);
		/* the message with the stored imap_uid may well no longer exist */
		g_debug ("%s: Kolab UID (%s) IMAP UID (%s) could not get CamelMessageInfo",
		         __func__, kolab_uid, imap_uid);
		return NULL;
	}

	if (kolab_uid != NULL) {
		const gchar *subject = camel_message_info_subject (mi);
		if (subject == NULL) {
			camel_folder_free_message_info (folder, mi);
			g_object_unref (folder);
			g_warning ("%s: Kolab UID (%s) IMAP UID (%s) have message with NULL subject",
			           __func__, kolab_uid, imap_uid);
			return NULL;
		}
		if (g_strcmp0 (subject, kolab_uid) != 0) {
			camel_folder_free_message_info (folder, mi);
			g_object_unref (folder);
			g_debug ("%s: IMAP UID (%s) does not carry expected Kolab UID (%s)",
			         __func__, imap_uid, kolab_uid);
			return NULL;
		}
	}

	camel_folder_free_message_info (folder, mi);

	message = camel_folder_get_message_sync (folder,
	                                         imap_uid,
	                                         cancellable,
	                                         &tmp_err);
	if (tmp_err != NULL) {
		if (message != NULL)
			g_object_unref (message);
		g_object_unref (folder);
		g_propagate_error (err, tmp_err);
		return NULL;
	}
	g_object_unref (folder);

	if (message == NULL) {
		g_warning ("%s: IMAP UID (%s) NULL message in camel folder",
		           __func__, imap_uid);
		return NULL;
	}

	g_assert (CAMEL_IS_MIME_MESSAGE (message));

	/* set a custom header on the message carrying the messages' IMAP UID */
	camel_medium_set_header (CAMEL_MEDIUM (message),
	                         KOLAB_IMAP_CLIENT_X_EVO_UID_HEADER,
	                         imap_uid);
	return message;
}

static CamelMimeMessage*
kolab_mail_imap_client_camel_get_msg_kolab_uid (KolabMailImapClient *self,
                                                const gchar *foldername,
                                                const gchar *kolab_uid,
                                                GCancellable *cancellable,
                                                GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	CamelFolder *folder = NULL;
	CamelMimeMessage *message = NULL;
	GPtrArray *imap_uids = NULL;
	GError *tmp_err = NULL;
	guint ii = 0;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert (foldername != NULL);
	g_assert (kolab_uid != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	folder = kolab_mail_imap_client_camel_get_folder (self,
	                                                  foldername,
	                                                  cancellable,
	                                                  &tmp_err);
	if (folder ==  NULL) {
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	camel_folder_refresh_info_sync (folder,
	                                cancellable,
	                                &tmp_err);
	if (tmp_err != NULL) {
		g_object_unref (folder);
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	/* Folder-grovel-search. Last resort, if Kolab message has
	 * changed IMAP UID from what's recorded in InfoDb.
	 * TODO: update InfoDb with what's found here so we can avoid
	 *       grovelling the folder _again_
	 */
	imap_uids = camel_folder_get_uids (folder);
	for (ii = 0; ii < imap_uids->len; ii++) {
		const gchar *msg_subject = NULL;
		gchar *imap_uid = g_ptr_array_index (imap_uids, ii);
		if (imap_uid == NULL) {
			g_warning ("%s: NULL imap uid in camel folder uid list",
			           __func__);
			continue;
		}

		message = kolab_mail_imap_client_camel_get_msg_imap_uid (self,
		                                                         foldername,
		                                                         imap_uid,
		                                                         NULL,
		                                                         cancellable,
		                                                         &tmp_err);
		if (tmp_err != NULL) {
			if (message != NULL) {
				g_object_unref (message);
				message = NULL;
			}
			g_warning ("%s: %s", __func__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
			continue;
		}

		if (message == NULL)
			continue;

		msg_subject = camel_mime_message_get_subject (message);
		if (msg_subject == NULL) {
			g_object_unref (message);
			message = NULL;
			g_warning ("%s: NULL message subject in camel folder",
			           __func__);
			continue;
		}
		if (g_strcmp0 (msg_subject, kolab_uid) == 0)
			break; /* message found */

		g_object_unref (message);
		message = NULL;
	}

	camel_folder_free_uids (folder, imap_uids);
	g_object_unref (folder);

	return message;
}

/*----------------------------------------------------------------------------*/
/* folder helpers */

static gboolean
kolab_mail_imap_client_update_folder (KolabMailImapClient *self,
                                      const gchar *foldername,
                                      GCancellable *cancellable,
                                      GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	CamelFolder *folder = NULL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert (foldername != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	folder = kolab_mail_imap_client_camel_get_folder (self,
	                                                  foldername,
	                                                  cancellable,
	                                                  &tmp_err);
	if (folder == NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* need to get folder even in offline mode to ensure
	 * that the locally cached instance is updated to the
	 * requested one -- only, no need to actually synchronize
	 * that folder in offline mode
	 */
	if (priv->is_online == FALSE) {
		g_object_unref (folder);
		return TRUE;
	}

	camel_offline_folder_downsync_sync (CAMEL_OFFLINE_FOLDER (folder),
	                                    NULL,
	                                    cancellable,
	                                    &tmp_err);
	if (tmp_err != NULL) {
		g_object_unref (folder);
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	camel_folder_synchronize_sync (folder,
	                               TRUE,
	                               cancellable,
	                               &tmp_err);
	if (tmp_err != NULL) {
		g_object_unref (folder);
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	camel_folder_expunge_sync (folder,
	                           cancellable,
	                           &tmp_err);
	if (tmp_err != NULL) {
		g_object_unref (folder);
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	camel_folder_refresh_info_sync (folder,
	                                cancellable,
	                                &tmp_err);
	if (tmp_err != NULL) {
		g_object_unref (folder);
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	g_object_unref (folder);

	return TRUE;
}

/*----------------------------------------------------------------------------*/
/* object config/status */

gboolean
kolab_mail_imap_client_configure (KolabMailImapClient *self,
                                  KolabSettingsHandler *ksettings,
                                  KolabMailMimeBuilder *mimebuilder,
                                  GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	/* const gchar *tmp_str = NULL; */
	const gchar *data_dir = NULL;
	const gchar *cache_dir = NULL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert (KOLAB_IS_SETTINGS_HANDLER (ksettings));
	g_assert (KOLAB_IS_MAIL_MIME_BUILDER (mimebuilder));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	g_assert (priv->is_up == FALSE);
	g_assert (priv->is_online == FALSE);

	if (priv->ksettings != NULL)
		return TRUE;

	g_object_ref (ksettings); /* unref'd in dispose() */
	g_object_ref (mimebuilder);
	priv->ksettings = ksettings;
	priv->mimebuilder = mimebuilder;

	/* we store the context locally, all else we can/should get on-the-fly
	 * from the settings handler
	 */
	priv->context = kolab_settings_handler_get_uint_field (priv->ksettings,
	                                                       KOLAB_SETTINGS_HANDLER_UINT_FIELD_FOLDER_CONTEXT,
	                                                       &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	if (priv->context == KOLAB_FOLDER_CONTEXT_INVAL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_GENERIC,
		             _("Internal inconsistency detected: Folder PIM context not set"));
		return FALSE;
	}

	/* set up session object directories (create if not existing) */
	data_dir = \
		kolab_settings_handler_get_char_field (priv->ksettings,
		                                       KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_DATA_DIR,
		                                       &tmp_err);
	if (data_dir == NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	cache_dir = \
		kolab_settings_handler_get_char_field (priv->ksettings,
		                                       KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_CACHE_DIR,
		                                       &tmp_err);
	if (cache_dir == NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* create session object */
	priv->session = g_object_new (CAMEL_TYPE_KOLAB_SESSION,
	                              CAMEL_KOLAB_SESSION_PROP_DATA_DIR, data_dir,
	                              CAMEL_KOLAB_SESSION_PROP_CACHE_DIR, cache_dir,
	                              NULL);

	/* set to offline state first */
	camel_session_set_online (CAMEL_SESSION (priv->session), FALSE);

	return TRUE;
}

static gboolean
kolab_mail_imap_client_configure_store_settings (KolabMailImapClient *self,
                                                 GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	CamelService *service = NULL;
	CamelSettings *camel_settings = NULL;
	CamelURL *camel_url = NULL;
	GError *tmp_err = NULL;

	/* TODO rework this function for new CamelSettings API
	 *
	 * Set the details for the settings object directly
	 * instead of using the camel_settings_load_from_url()
	 * function, which is a transient one only and
	 * scheduled for removal
	 */

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	/* FIXME this is a transient thing, we should set
	 *       the CamelSettings values directly from
	 *       KolabSettingsHandler
	 */
	camel_url = \
		kolab_util_backend_camel_url_new_from_settings (priv->ksettings,
		                                                &tmp_err);
	if (camel_url == NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* create new settings object for service */
	service = CAMEL_SERVICE (priv->store);
	camel_service_set_settings (service, NULL);
	camel_settings = camel_service_get_settings (service);

	camel_settings_load_from_url (camel_settings, camel_url);

	/* re-set the settings for our service (store) */
	camel_service_set_settings (service, camel_settings);

	camel_url_free (camel_url);

	return TRUE;
}

gboolean
kolab_mail_imap_client_bringup (KolabMailImapClient *self,
                                GCancellable *cancellable,
                                GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	CamelService *service = NULL;
	gchar *dbpath = NULL;
	gchar *account_uid = NULL;
	gchar *extd_account_uid = NULL;
	GError *tmp_err = NULL;
	gboolean ok = FALSE;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	if (priv->is_up == TRUE)
		return TRUE;

	g_assert (priv->is_online == FALSE);

	/* bring up session object */
	ok = camel_kolab_session_bringup (priv->session,
	                                  cancellable,
	                                  &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* create the service uid for the storage path */
	account_uid = \
		kolab_util_backend_account_uid_new_from_settings (priv->ksettings,
		                                                  &tmp_err);
	if (account_uid == NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* register Kolab service (store) with CamelSession */
	service = camel_session_add_service (CAMEL_SESSION (priv->session),
	                                     account_uid,
	                                     KOLAB_CAMEL_PROVIDER_PROTOCOL,
	                                     CAMEL_PROVIDER_STORE,
	                                     &tmp_err);
	g_free (account_uid);
	if (tmp_err != NULL) {
		if (service != NULL) {
			/* should not happen */
			g_warning ("%s: while adding service, error was set, but non-NULL service returned!", __func__);
			g_object_unref (service);
		}
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	priv->store = CAMEL_KOLAB_IMAPX_STORE (service);
	ok = camel_kolab_imapx_store_set_folder_context (priv->store,
	                                                 priv->context);
	if (! ok) {
		/* FIXME mark this as a translatable string */
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_CAMEL,
		             "Could not set PIM folder context %i for this backend",
		             priv->context);
		return FALSE;
	}

	/* configure the service with settings from KolabSettingsHandler */
	ok = kolab_mail_imap_client_configure_store_settings (self,
	                                                      &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* set offline state */
	ok = camel_offline_store_set_online_sync (CAMEL_OFFLINE_STORE (priv->store),
	                                          FALSE, /* offline */
	                                          cancellable,
	                                          &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	camel_session_set_online (CAMEL_SESSION (priv->session), FALSE);

	/* store the data path for the configured account into the
	 * settings handler so we can access it from the InfoDb
	 */
	dbpath = kolab_util_camel_get_storage_path (service,
	                                            CAMEL_SESSION (priv->session),
	                                            &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	/* dbpath might be NULL here (in case of error), InfoDb will find out */
	ok = kolab_settings_handler_set_char_field (priv->ksettings,
	                                            KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_ACCOUNT_DIR,
	                                            dbpath,
	                                            &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	priv->is_up = TRUE;

	return TRUE;
}

gboolean
kolab_mail_imap_client_shutdown (KolabMailImapClient *self,
                                 GCancellable *cancellable,
                                 GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	GError *tmp_err = NULL;
	gboolean ok = FALSE;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	g_assert (priv->is_online == FALSE);

	if (priv->is_up == FALSE)
		return TRUE;

	/* TODO shut down imapx object */

	/* shut down session object */
	ok = camel_kolab_session_shutdown (priv->session,
	                                   cancellable,
	                                   &tmp_err);
	if (! ok) {
		g_warning ("%s: %s",
		           __func__, tmp_err->message);
		g_error_free (tmp_err);
	}

	priv->is_up = FALSE;
	return TRUE;
}

/*----------------------------------------------------------------------------*/
/* object state transition */

gboolean
kolab_mail_imap_client_go_online (KolabMailImapClient *self,
                                  GCancellable *cancellable,
                                  GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	const gchar *tmp_str = NULL;
	GError *tmp_err = NULL;
	gboolean ok = FALSE;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	if (priv->is_online == TRUE)
		return TRUE;

	/* set the user password (we get it late in the book backend) */
	tmp_str = kolab_settings_handler_get_char_field (priv->ksettings,
	                                                 KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_USER_PASSWORD,
	                                                 &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	if (tmp_str == NULL)
		g_warning ("%s: no password supplied", __func__);
	camel_kolab_session_set_password (priv->session, tmp_str);

	/* set the TPM token pin (we may get it late in the book backend) */
	tmp_str = kolab_settings_handler_get_char_field (priv->ksettings,
	                                                 KOLAB_SETTINGS_HANDLER_CHAR_FIELD_PKCS11_USER_PIN,
	                                                 &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	camel_kolab_session_set_token_pin (priv->session, tmp_str);

	/* remove local CamelFolder object */
	if (priv->folder != NULL) {
		g_object_unref (priv->folder);
		priv->folder = NULL;
	}
	g_free (priv->foldername);
	priv->foldername = NULL;

	/* connect the camel service */
	ok = camel_service_connect_sync (CAMEL_SERVICE (priv->store), &tmp_err);
	camel_kolab_session_set_token_pin (priv->session, NULL); /* forget pin */
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	camel_offline_store_set_online_sync (CAMEL_OFFLINE_STORE (priv->store),
	                                     TRUE, /* online */
	                                     cancellable,
	                                     &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	camel_session_set_online (CAMEL_SESSION (priv->session), TRUE);

	g_debug ("KolabMailImapClient: changed state: online operation");

	priv->is_online = TRUE;
	return TRUE;
}

gboolean
kolab_mail_imap_client_go_offline (KolabMailImapClient *self,
                                   GCancellable *cancellable,
                                   GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	GError *tmp_err = NULL;
	gboolean ok = FALSE;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	if (priv->is_online == FALSE)
		return TRUE;

	/* remove local CamelFolder object */
	if (priv->folder != NULL) {
		g_object_unref (priv->folder);
		priv->folder = NULL;
	}
	g_free (priv->foldername);
	priv->foldername = NULL;

	/* disconnect the camel service */
	camel_offline_store_prepare_for_offline_sync (CAMEL_OFFLINE_STORE (priv->store),
	                                              cancellable,
	                                              &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	ok = camel_service_disconnect_sync (CAMEL_SERVICE (priv->store),
	                                    TRUE, /* try to disconnect cleanly */
	                                    &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	camel_offline_store_set_online_sync (CAMEL_OFFLINE_STORE (priv->store),
	                                     FALSE, /* offline */
	                                     cancellable,
	                                     &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	camel_session_set_online (CAMEL_SESSION (priv->session), FALSE);

	g_debug ("KolabMailImapClient: changed state: offline operation");

	priv->is_online = FALSE;
	return TRUE;
}

/*----------------------------------------------------------------------------*/
/* folders */

GList*
kolab_mail_imap_client_query_foldernames (KolabMailImapClient *self,
                                          GCancellable *cancellable,
                                          GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	CamelFolderInfo *fi = NULL;
	GList *folder_list = NULL;
	CamelStoreGetFolderInfoFlags flags = 0;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	flags = CAMEL_STORE_FOLDER_INFO_RECURSIVE | CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL;
	fi = camel_store_get_folder_info_sync (CAMEL_STORE (priv->store),
	                                       NULL,
	                                       flags,
	                                       cancellable,
	                                       &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	if (fi == NULL)
		return NULL;

	camel_store_free_folder_info (CAMEL_STORE (priv->store), fi);

	folder_list = camel_kolab_imapx_store_resect_folder_list (priv->store);
	return folder_list;
}

KolabFolderTypeID
kolab_mail_imap_client_get_folder_type (KolabMailImapClient *self,
                                        const gchar *foldername,
                                        GCancellable *cancellable,
                                        GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	KolabFolderTypeID foldertype = KOLAB_FOLDER_TYPE_INVAL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert (foldername != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, KOLAB_FOLDER_TYPE_INVAL);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	foldertype = camel_kolab_imapx_store_get_folder_type (priv->store,
	                                                      foldername,
	                                                      cancellable,
	                                                      &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return KOLAB_FOLDER_TYPE_INVAL;
	}
	if (foldertype == KOLAB_FOLDER_TYPE_INVAL) {
		g_set_error (err,
		             KOLAB_CAMEL_ERROR,
		             KOLAB_CAMEL_ERROR_GENERIC,
		             _("Folder '%s' has an invalid PIM type"),
		             foldername);
		return KOLAB_FOLDER_TYPE_INVAL;
	}

	return foldertype;
}

static guint64
kolab_mail_imap_client_get_folder_uidvalidity (KolabMailImapClient *self,
                                               const gchar *foldername,
                                               GCancellable *cancellable,
                                               GError **err)
{
	/* KolabMailImapClientPrivate *priv = NULL; */
	CamelFolder *folder = NULL;
	guint64 uidvalidity = 0;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert (foldername != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, KOLAB_FOLDER_TYPE_INVAL);

	/* priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self); */

	folder = kolab_mail_imap_client_camel_get_folder (self,
	                                                  foldername,
	                                                  cancellable,
	                                                  &tmp_err);
	if (folder == NULL) {
		g_propagate_error (err, tmp_err);
		return 0;
	}

	/* FIXME check for folder uidvalidity changes
	 *
	 *      Problem: IMAPX does not currently (2.30.3+)
	 *      report IMAP folder UIDVALIDITY. We always
	 *      read 0 (zero), so we cannot detect a change
	 *      there presently
	 */
	uidvalidity = camel_kolab_imapx_folder_get_uidvalidity (CAMEL_KOLAB_IMAPX_FOLDER (folder),
	                                                        &tmp_err);
	g_object_unref (folder);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return 0;
	}

	return uidvalidity;
}

KolabFolderSummary*
kolab_mail_imap_client_query_folder_summary (KolabMailImapClient *self,
                                             const gchar *foldername,
                                             GCancellable *cancellable,
                                             GError **err)
{
	/* KolabMailImapClientPrivate *priv = NULL; */
	KolabFolderTypeID foldertype = KOLAB_FOLDER_TYPE_INVAL;
	KolabFolderSummary *summary = NULL;
	guint64 uidvalidity = 0;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert (foldername != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	/* priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self); */

	foldertype = kolab_mail_imap_client_get_folder_type (self,
	                                                     foldername,
	                                                     cancellable,
	                                                     &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	uidvalidity = kolab_mail_imap_client_get_folder_uidvalidity (self,
	                                                             foldername,
	                                                             cancellable,
	                                                             &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	summary = kolab_folder_summary_new ();
	kolab_folder_summary_set_char_field (summary,
	                                     KOLAB_FOLDER_SUMMARY_CHAR_FIELD_FOLDERNAME,
	                                     g_strdup (foldername));
	kolab_folder_summary_set_uint_field (summary,
	                                     KOLAB_FOLDER_SUMMARY_UINT_FIELD_FOLDER_TYPE,
	                                     foldertype);
	kolab_folder_summary_set_uint_field (summary,
	                                     KOLAB_FOLDER_SUMMARY_UINT_FIELD_CACHE_LOCATION,
	                                     KOLAB_OBJECT_CACHE_LOCATION_IMAP);
	kolab_folder_summary_set_uint64_field (summary,
	                                       KOLAB_FOLDER_SUMMARY_UINT64_FIELD_UIDVALIDITY,
	                                       uidvalidity);

	return summary;
}

GList*
kolab_mail_imap_client_query_foldernames_anon (gpointer self,
                                               GCancellable *cancellable,
                                               GError **err)
{
	GList *list = NULL;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	list = kolab_mail_imap_client_query_foldernames(self,
	                                                cancellable,
	                                                err);
	return list;
}

gboolean
kolab_mail_imap_client_create_folder (KolabMailImapClient *self,
                                      const gchar *foldername,
                                      GCancellable *cancellable,
                                      GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert (foldername != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	ok = kolab_mail_imap_client_store (self,
	                                   NULL,
	                                   foldername,
	                                   TRUE,
	                                   cancellable,
	                                   &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	return TRUE;
}

gboolean
kolab_mail_imap_client_delete_folder (KolabMailImapClient *self,
                                      const gchar *foldername,
                                      GCancellable *cancellable,
                                      GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert (foldername != NULL);
	(void)cancellable; /* FIXME */ /* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	/* TODO implement me */

	return TRUE;
}

gboolean
kolab_mail_imap_client_exists_folder (KolabMailImapClient *self,
                                      const gchar *foldername,
                                      GCancellable *cancellable,
                                      GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert (foldername != NULL);
	(void)cancellable; /* FIXME */ /* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	/* TODO implement me */

	return TRUE;
}

/*----------------------------------------------------------------------------*/
/* mailobject search/retrieve/store/delete */

GHashTable*
kolab_mail_imap_client_query_summaries (KolabMailImapClient *self,
                                        const gchar *foldername,
                                        const gchar *sexp,
                                        gboolean update,
                                        GCancellable *cancellable,
                                        GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	GHashTable *summaries = NULL;
	KolabFolderTypeID foldertype = KOLAB_FOLDER_TYPE_INVAL;
	KolabFolderContextID foldercontext = KOLAB_FOLDER_CONTEXT_INVAL;
	GError *tmp_err = NULL;
	gboolean ok = FALSE;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert (foldername != NULL);
	(void)sexp; /* TODO implement expression search */ /* sexp may be NULL */
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	/* get folder type and context */
	foldertype = kolab_mail_imap_client_get_folder_type (self,
	                                                     foldername,
	                                                     cancellable,
	                                                     &tmp_err);
	if (foldertype == KOLAB_FOLDER_TYPE_INVAL) {
		g_propagate_error (err, tmp_err);
		return NULL;
	}
	foldercontext = kolab_util_folder_type_map_to_context_id (foldertype);
	if (foldercontext == KOLAB_FOLDER_CONTEXT_INVAL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_CONTEXT_MISUSE,
		             _("Internal inconsistency detected: Folder '%s' has a PIM type %i set which does not map to a known folder context"),
		             foldername, foldertype);
		return NULL;
	}

	/* update the CamelFolder */
	if (update) {
		ok = kolab_mail_imap_client_update_folder (self,
		                                           foldername,
		                                           cancellable,
		                                           &tmp_err);
		if (! ok) {
			g_propagate_error (err, tmp_err);
			return NULL;
		}
	}

	/* generate summaries table. may be NULL */
	summaries = kolab_mail_imap_client_camel_gen_summaries (self,
	                                                        foldername,
	                                                        foldertype,
	                                                        foldercontext,
	                                                        cancellable,
	                                                        &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	return summaries;
}

gboolean
kolab_mail_imap_client_store (KolabMailImapClient *self,
                              KolabMailHandle *kmailhandle,
                              const gchar *foldername,
                              gboolean update,
                              GCancellable *cancellable,
                              GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	KolabMailMimeBuilderHeaderInfo *headerinfo = NULL;
	KolabMailSummary *summary = NULL;
	const Kolab_conv_mail *kconvmail = NULL;
	const gchar *kolab_uid = NULL;
	const gchar *imap_uid = NULL;
	const gchar *handle_foldername = NULL;
	gchar *stored_imap_uid = NULL;
	CamelMimeMessage *new_message = NULL;
	CamelMimeMessage *orig_message = NULL;
	CamelMimeMessage *stored_message = NULL;
	CamelFolder *folder = NULL;
	GError *tmp_err = NULL;
	gboolean ok = TRUE;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert ((kmailhandle != NULL) || (foldername != NULL));
	if (kmailhandle != NULL)
		g_assert (KOLAB_IS_MAIL_HANDLE (kmailhandle));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	/* TODO
	 * - check whether we cover all cases
	 * - check for code dupe with retrieve()
	 */

	/* folder creation */
	if ((kmailhandle == NULL) && (foldername != NULL)) {
		/* FIXME implement me */
		g_set_error (&tmp_err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_INTERNAL,
		             _("Folder creation not yet implemented"));
		goto cleanup;
	}

	handle_foldername = kolab_mail_handle_get_foldername (kmailhandle);

	/* moving to a different folder */
	if ((handle_foldername != NULL) && (foldername != NULL)) {
		if (g_strcmp0 (handle_foldername, foldername) != 0) {
			/* FIXME implement me */
			g_set_error (&tmp_err,
			             KOLAB_BACKEND_ERROR,
			             KOLAB_BACKEND_ERROR_INTERNAL,
			             _("Moving object to different folder not yet implemented"));
			goto cleanup;
		}
	}

	/* update the CamelFolder */
	if (update) {
		ok = kolab_mail_imap_client_update_folder (self,
		                                           foldername,
		                                           cancellable,
		                                           &tmp_err);
		if (! ok)
			goto cleanup;
	}

	/* normal store operation (handle_foldername may be unset) */
	folder = kolab_mail_imap_client_camel_get_folder (self,
	                                                  foldername,
	                                                  cancellable,
	                                                  &tmp_err);
	if (tmp_err != NULL)
		goto cleanup;

	if (folder == NULL) {
		g_set_error (&tmp_err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_NOTFOUND,
		             _("Could not get Camel folder for '%s'"),
		             foldername);
		goto cleanup;
	}

	/* try with imap uid first */
	summary = kolab_mail_handle_get_summary_nonconst (kmailhandle);
	g_assert (summary != NULL);
	imap_uid = kolab_mail_summary_get_char_field (summary,
	                                              KOLAB_MAIL_SUMMARY_CHAR_FIELD_IMAP_UID);
	kolab_uid = kolab_mail_handle_get_uid (kmailhandle);

	if (imap_uid != NULL) {
		orig_message = kolab_mail_imap_client_camel_get_msg_imap_uid (self,
		                                                              foldername,
		                                                              imap_uid,
		                                                              kolab_uid,
		                                                              cancellable,
		                                                              &tmp_err);
		if (tmp_err != NULL) {
			g_warning ("%s: %s", __func__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
	}

	/* if no luck with imap uid, we'll need to search messages for kolab uid */
	if (orig_message == NULL) {
		orig_message = kolab_mail_imap_client_camel_get_msg_kolab_uid (self,
		                                                               foldername,
		                                                               kolab_uid,
		                                                               cancellable,
		                                                               &tmp_err);
		if (tmp_err != NULL) {
			g_warning ("%s: %s", __func__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
	}

	/* orig_message may still be NULL (non-existent on server) */
	kconvmail = kolab_mail_handle_get_kconvmail (kmailhandle);
	g_assert (kconvmail != NULL); /* must be ok since the handle is complete */
	new_message = kolab_mail_mime_builder_camel_new_from_conv (priv->mimebuilder,
	                                                           kconvmail,
	                                                           cancellable,
	                                                           &tmp_err);
	if (new_message == NULL)
		goto cleanup;

	headerinfo = g_new0 (KolabMailMimeBuilderHeaderInfo, 1);
	headerinfo->kolab_uid = g_strdup (kolab_uid);
	headerinfo->from_name = g_strdup (KOLAB_IMAP_CLIENT_DUMMY_FROM_NAME);
	headerinfo->from_addr = g_strdup (KOLAB_IMAP_CLIENT_DUMMY_FROM_ADDR);
	ok = kolab_mail_mime_builder_camel_set_header (priv->mimebuilder,
	                                               new_message,
	                                               headerinfo,
	                                               orig_message,
	                                               &tmp_err);
	g_free (headerinfo->from_addr);
	g_free (headerinfo->from_name);
	g_free (headerinfo->kolab_uid);
	g_free (headerinfo);
	if (! ok)
		goto cleanup;

	/* append the new message to the imap folder */
	camel_folder_append_message_sync (folder,
	                                  new_message,
	                                  NULL,
	                                  /* new IMAP UID never reported by IMAPX */ NULL,
	                                  cancellable,
	                                  &tmp_err);

	/* FIXME CamelIMAPX error reporting is not working in all cases
	 *       - tmp_ex is not set here if the server rejects the message
	 *         because of NUL bytes, so the original would get lost if we
	 *         just sync'ed the CamelFolder without further checking
	 *       - IMAPX currently never reports the IMAP UID of a freshly
	 *         stored message
	 */
	if (tmp_err != NULL)
		goto cleanup;

	/* FIXME need to read-back email from the server right away for several reasons:
	 *       - presently, it's the only way to be sure that the append operation
	 *         was really successful (see above FIXME). Might have been fixed in 2.32
	 *	   and above
	 *       - if we do not read-back, the mail will not be in the IMAPX store (so
	 *         that we can use it in offline mode)
	 *       - IMAPX apparently never returnes a stored_imap_uid, so we need to
	 *         grovel-search for the Kolab UID (stored in the mail subject) in it's
	 *         folder either way
	 *       - camel_folder_sync_message() needs the IMAP UID of a message to be stored
	 *         in the IMAPX offline store, but the IMAP UID of the freshly stored message
	 *         is exactly what IMAPX does not provide us with here (see above)
	 */
	stored_message = kolab_mail_imap_client_camel_get_msg_kolab_uid (self,
	                                                                 foldername,
	                                                                 kolab_uid,
	                                                                 cancellable,
	                                                                 &tmp_err);
	if (tmp_err != NULL)
		goto cleanup;

	if (stored_message == NULL) {
		g_set_error (&tmp_err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_CAMEL,
		             _("Could not read-back mail message from server, UID '%s', Folder '%s'"),
		             kolab_uid, foldername);
		goto cleanup;
	}

	stored_imap_uid = g_strdup (camel_medium_get_header (CAMEL_MEDIUM (stored_message),
	                                                     KOLAB_IMAP_CLIENT_X_EVO_UID_HEADER));
	if (stored_imap_uid == NULL) {
		g_set_error (&tmp_err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_INTERNAL,
		             _("Could not get IMAP UID from message, UID '%s', Folder '%s'"),
		             kolab_uid, foldername);
		goto cleanup;
	}

	camel_medium_set_header (CAMEL_MEDIUM (stored_message),
	                         KOLAB_IMAP_CLIENT_X_EVO_UID_HEADER,
	                         NULL);

	/* mark old, existing message as deleted */
	if (orig_message != NULL) {
		const gchar *local_imap_uid = NULL;
		local_imap_uid = camel_medium_get_header (CAMEL_MEDIUM (orig_message),
		                                          KOLAB_IMAP_CLIENT_X_EVO_UID_HEADER);
		g_debug ("%s: Kolab UID (%s) Folder (%s) IMAP UID (%s) marking for deletion",
		         __func__, kolab_uid, foldername, local_imap_uid);
		if (local_imap_uid != NULL) {
			camel_folder_delete_message (folder, local_imap_uid);
			camel_medium_set_header (CAMEL_MEDIUM (orig_message),
			                         KOLAB_IMAP_CLIENT_X_EVO_UID_HEADER,
			                         NULL);
		} else {
			g_warning ("%s: Kolab UID (%s) Folder (%s) IMAP UID not set on camel message",
			           __func__, kolab_uid, foldername);
		}
	}

	/* update folder */
	camel_folder_synchronize_sync (folder,
	                               TRUE,
	                               cancellable,
	                               &tmp_err);
	if (tmp_err != NULL)
		goto cleanup;

	/* refresh folder info */
	camel_folder_refresh_info_sync (folder,
	                                cancellable,
	                                &tmp_err);
	if (tmp_err != NULL)
		goto cleanup;

	kolab_mail_summary_set_char_field (summary,
	                                   KOLAB_MAIL_SUMMARY_CHAR_FIELD_IMAP_UID,
	                                   stored_imap_uid);
	g_debug ("%s: Kolab UID (%s) Folder (%s) IMAP UID (%s) successfully stored",
	         __func__, kolab_uid, foldername, stored_imap_uid);
	stored_imap_uid = NULL; /* data now owned by mail summary, do not free() */

	/* drop kconvmail from handle */
	kolab_mail_handle_drop_kconvmail (kmailhandle);

 cleanup:
	if (tmp_err != NULL) {
		ok = FALSE;
		g_propagate_error (err, tmp_err);
	}
	if (orig_message != NULL)
		g_object_unref (orig_message);
	if (new_message != NULL)
		g_object_unref (new_message);
	if (stored_message != NULL)
		g_object_unref (stored_message);
	if (folder != NULL)
		g_object_unref (folder);
	if (stored_imap_uid != NULL)
		g_free (stored_imap_uid);

	return ok; /* TRUE or FALSE */
}

gboolean
kolab_mail_imap_client_retrieve (KolabMailImapClient *self,
                                 KolabMailHandle *kmailhandle,
                                 gboolean update,
                                 GCancellable *cancellable,
                                 GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	KolabFolderTypeID folder_type = KOLAB_FOLDER_TYPE_INVAL;
	KolabFolderContextID context = KOLAB_FOLDER_CONTEXT_INVAL;
	KolabFolderTypeID s_folder_type = KOLAB_FOLDER_TYPE_INVAL;
	KolabFolderContextID s_context = KOLAB_FOLDER_CONTEXT_INVAL;
	KolabMailSummary *summary = NULL;
	const gchar *kolab_uid = NULL;
	const gchar *imap_uid = NULL;
	const gchar *foldername = NULL;
	CamelMimeMessage *camel_message = NULL;
	Kolab_conv_mail *kconvmail = NULL;
	GError *tmp_err = NULL;
	gboolean ok = TRUE;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert (KOLAB_IS_MAIL_HANDLE (kmailhandle));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	kolab_uid = kolab_mail_handle_get_uid (kmailhandle);
	if (kolab_uid == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_INTERNAL,
		             _("Internal inconsistency detected: Kolab UID not set on PIM Object handle"));
		return FALSE;
	}

	foldername = kolab_mail_handle_get_foldername (kmailhandle);
	if (foldername == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_INTERNAL,
		             _("Internal inconsistency detected: Folder name not set on PIM Object handle, UID '%s'"),
		             kolab_uid);
		return FALSE;
	}

	summary = kolab_mail_handle_get_summary_nonconst (kmailhandle);
	g_assert (summary != NULL);

	/* folder type/context checking */
	folder_type = kolab_mail_imap_client_get_folder_type (self,
	                                                      foldername,
	                                                      cancellable,
	                                                      &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	context = kolab_util_folder_type_map_to_context_id (folder_type);
	s_folder_type = kolab_mail_summary_get_uint_field (summary,
	                                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_FOLDER_TYPE);
	s_context = kolab_mail_summary_get_uint_field (summary,
	                                               KOLAB_MAIL_SUMMARY_UINT_FIELD_FOLDER_CONTEXT);

	if ((s_context != KOLAB_FOLDER_CONTEXT_INVAL) && (s_context != context)) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_DATATYPE_INTERNAL,
		             _("Internal inconsistency detected: Folder context mismatch, real is %i, stored is %i, UID '%s', Folder '%s'"),
		             context, s_context, kolab_uid, foldername);
		return FALSE;
	}

	g_debug ("%s: UID (%s) with folder type/context (%i)/(%i)",
	         __func__, kolab_uid, folder_type, context);

	/* update the CamelFolder */
	if (update) {
		ok = kolab_mail_imap_client_update_folder (self,
		                                           foldername,
		                                           cancellable,
		                                           &tmp_err);
		if (! ok) {
			g_propagate_error (err, tmp_err);
			return FALSE;
		}
	}

	/* try with imap uid first */
	imap_uid = kolab_mail_summary_get_char_field (summary,
	                                              KOLAB_MAIL_SUMMARY_CHAR_FIELD_IMAP_UID);

	if (imap_uid != NULL) {
		camel_message = kolab_mail_imap_client_camel_get_msg_imap_uid (self,
		                                                               foldername,
		                                                               imap_uid,
		                                                               kolab_uid,
		                                                               cancellable,
		                                                               &tmp_err);
		if (tmp_err != NULL) {
			g_warning ("%s: %s", __func__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
	}

	/* if no luck with imap uid, we'll need to search messages for kolab uid */
	if (camel_message == NULL) {
		camel_message = kolab_mail_imap_client_camel_get_msg_kolab_uid (self,
		                                                                foldername,
		                                                                kolab_uid,
		                                                                cancellable,
		                                                                &tmp_err);
		if (tmp_err != NULL) {
			g_propagate_error (err, tmp_err);
			return FALSE;
		}
	}

	if (camel_message == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_NOTFOUND,
		             _("Could not find Kolab mail message, UID '%s', Folder '%s'"),
		             kolab_uid, foldername);
		return FALSE;
	}

	kconvmail = kolab_mail_mime_builder_conv_new_from_camel (priv->mimebuilder,
	                                                         camel_message,
	                                                         cancellable,
	                                                         &tmp_err);
	g_object_unref (camel_message);

	if (kconvmail == NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* attach kconvmail to handle */
	kolab_mail_handle_set_kconvmail (kmailhandle, kconvmail);

	/* set folder type/context, if not already set on handle */
	/* FIXME check whether this can be better done in Synchronizer */
	if (s_folder_type == KOLAB_FOLDER_TYPE_INVAL) {
		g_debug ("%s: UID (%s) setting folder type/context (%i)/(%i)",
		         __func__, kolab_uid, folder_type, context);
		kolab_mail_summary_set_uint_field (summary,
		                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_FOLDER_TYPE,
		                                   folder_type);
		kolab_mail_summary_set_uint_field (summary,
		                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_FOLDER_CONTEXT,
		                                   context);
	}

	return TRUE;
}

gboolean
kolab_mail_imap_client_delete (KolabMailImapClient *self,
                               KolabMailHandle *kmailhandle,
                               gboolean imapuid_only,
                               gboolean update,
                               GCancellable *cancellable,
                               GError **err)
{
	KolabMailImapClientPrivate *priv = NULL;
	const KolabMailSummary *summary = NULL;
	const gchar *kolab_uid = NULL;
	const gchar *foldername = NULL;
	gchar *imap_uid = NULL;
	CamelFolder *folder = NULL;
	CamelMimeMessage *camel_message = NULL;
	GError *tmp_err = NULL;
	gboolean ok = TRUE;

	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (self));
	g_assert (KOLAB_IS_MAIL_HANDLE (kmailhandle));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_IMAP_CLIENT_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	kolab_uid = kolab_mail_handle_get_uid (kmailhandle);
	if (kolab_uid == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_INTERNAL,
		             _("Internal inconsistency detected: Kolab UID not set on PIM Object handle"));
		return FALSE;
	}

	foldername = kolab_mail_handle_get_foldername (kmailhandle);
	if (foldername == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_INTERNAL,
		             _("Internal inconsistency detected: Folder name not set on PIM Object handle, UID '%s'"),
		             kolab_uid);
		return FALSE;
	}

	/* update the CamelFolder */
	if (update) {
		ok = kolab_mail_imap_client_update_folder (self,
		                                           foldername,
		                                           cancellable,
		                                           &tmp_err);
		if (! ok) {
			g_propagate_error (err, tmp_err);
			return FALSE;
		}
	}

	/* get the CamelFolder */
	folder = kolab_mail_imap_client_camel_get_folder (self,
	                                                  foldername,
	                                                  cancellable,
	                                                  &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	if (folder == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_NOTFOUND,
		             _("Could not get Camel folder for '%s'"),
		             foldername);
		return FALSE;
	}

	/* try with imap uid first */
	summary = kolab_mail_handle_get_summary (kmailhandle);
	g_assert (summary != NULL);
	imap_uid = g_strdup (kolab_mail_summary_get_char_field (summary,
	                                                        KOLAB_MAIL_SUMMARY_CHAR_FIELD_IMAP_UID));

	if (imap_uid != NULL) {
		camel_message = kolab_mail_imap_client_camel_get_msg_imap_uid (self,
		                                                               foldername,
		                                                               imap_uid,
		                                                               kolab_uid,
		                                                               cancellable,
		                                                               &tmp_err);
		if (tmp_err != NULL) {
			g_warning ("%s: %s", __func__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
	}

	/* we may want to be sure only to delete if the IMAP UID is valid on server */
	if (imapuid_only)
		goto kolab_uid_skip;

	/* if no luck with imap uid, we'll need to search messages for kolab uid */
	if (camel_message == NULL) {
		camel_message = kolab_mail_imap_client_camel_get_msg_kolab_uid (self,
		                                                                foldername,
		                                                                kolab_uid,
		                                                                cancellable,
		                                                                &tmp_err);
		if (tmp_err != NULL) {
			g_warning ("%s: %s", __func__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
	}

 kolab_uid_skip:

	if (camel_message == NULL) {
		g_warning ("%s: UID (%s) not found in folder (%s)",
		           __func__, kolab_uid, foldername);
		camel_folder_synchronize_sync (folder,
		                               TRUE,
		                               cancellable,
		                               &tmp_err);
		if (tmp_err != NULL) {
			g_warning ("%s: %s",
			           __func__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
		camel_folder_refresh_info_sync (folder,
		                                cancellable,
		                                &tmp_err);
		if (tmp_err != NULL) {
			g_warning ("%s: %s",
			           __func__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
		g_object_unref (folder);
		if (imap_uid != NULL)
			g_free (imap_uid);
		return TRUE;
	}

	/* mark existing message as deleted */
	g_free (imap_uid);
	imap_uid = g_strdup (camel_medium_get_header (CAMEL_MEDIUM (camel_message),
	                                              KOLAB_IMAP_CLIENT_X_EVO_UID_HEADER));
	camel_medium_set_header (CAMEL_MEDIUM (camel_message),
	                         KOLAB_IMAP_CLIENT_X_EVO_UID_HEADER,
	                         NULL);
	g_object_unref (camel_message);

	if (imap_uid != NULL) {
		g_debug ("%s: Kolab UID (%s) Folder (%s) IMAP UID (%s) marking for deletion",
		         __func__, kolab_uid, foldername, imap_uid);
		camel_folder_delete_message (folder, imap_uid);
	}
	else
		g_warning ("%s: Kolab UID (%s) IMAP UID not set on camel message",
		           __func__, kolab_uid);

	camel_folder_synchronize_sync (folder,
	                               TRUE,
	                               cancellable,
	                               &tmp_err);
	if (tmp_err != NULL)
		goto cleanup;
	camel_folder_refresh_info_sync (folder,
	                                cancellable,
	                                &tmp_err);

 cleanup:
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		ok = FALSE;
	} else {
		g_debug ("%s: Kolab UID (%s) Folder (%s) IMAP UID (%s) deleted",
		         __func__, kolab_uid, foldername, imap_uid);
		ok = TRUE;
	}
	if (imap_uid != NULL)
		g_free (imap_uid);
	g_object_unref (folder);

	return ok; /* TRUE or FALSE */
}

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