/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/***************************************************************************
 *            kolab-settings-handler.c
 *
 *  Wed Dec 22 13:46:54 2010
 *  Copyright  2010  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 <errno.h>

#include <glib/gi18n-lib.h>

#include <libedataserver/e-data-server-util.h>
#include <libekolabutil/kolab-util-glib.h>

#include "kolab-util-backend.h"
#include "kolab-settings-handler.h"

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

typedef struct _KolabSettingsHandlerPrivate KolabSettingsHandlerPrivate;
struct _KolabSettingsHandlerPrivate
{
	gboolean is_configured;
	gboolean is_up;

	gchar   *sdata_char[KOLAB_SETTINGS_HANDLER_CHAR_LAST_FIELD];
	guint    sdata_uint[KOLAB_SETTINGS_HANDLER_UINT_LAST_FIELD];
	gint     sdata_int[KOLAB_SETTINGS_HANDLER_INT_LAST_FIELD];
	gboolean sdata_bool[KOLAB_SETTINGS_HANDLER_BOOL_LAST_FIELD];

	GHashTable *sdata_tbl;
};

#define KOLAB_SETTINGS_HANDLER_PRIVATE(obj)  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), KOLAB_TYPE_SETTINGS_HANDLER, KolabSettingsHandlerPrivate))

G_DEFINE_TYPE (KolabSettingsHandler, kolab_settings_handler, G_TYPE_OBJECT)

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

typedef gboolean (*KolabSettingsHandlerCharSetFunc) (KolabSettingsHandler*, gchar*, GError**);
typedef gboolean (*KolabSettingsHandlerUintSetFunc) (KolabSettingsHandler*, guint, GError**);
typedef gboolean (*KolabSettingsHandlerIntSetFunc) (KolabSettingsHandler*, gint, GError**);
typedef gboolean (*KolabSettingsHandlerBoolSetFunc) (KolabSettingsHandler*, gboolean, GError**);

typedef gboolean (*KolabSettingsHandlerGetFunc) (KolabSettingsHandler*, GError**);

static gboolean kolab_settings_handler_char_get_func_camel_data_dir (KolabSettingsHandler*, GError**);
static gboolean kolab_settings_handler_char_set_func_camel_data_dir (KolabSettingsHandler*, gchar*, GError**);
static gboolean kolab_settings_handler_char_get_func_camel_cache_dir (KolabSettingsHandler*, GError**);
static gboolean kolab_settings_handler_char_set_func_camel_cache_dir (KolabSettingsHandler*, gchar*, GError**);
static gboolean kolab_settings_handler_char_get_func_camel_config_dir (KolabSettingsHandler*, GError**);
static gboolean kolab_settings_handler_char_set_func_camel_config_dir (KolabSettingsHandler*, gchar*, GError**);
static gboolean kolab_settings_handler_uint_set_func_folder_context (KolabSettingsHandler*, guint value, GError**);

static KolabSettingsHandlerGetFunc _kolab_settings_handler_char_get_funcs[] = {
	kolab_settings_handler_char_get_func_camel_data_dir, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_DATA_DIR */
	kolab_settings_handler_char_get_func_camel_cache_dir, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_CACHE_DIR */
	kolab_settings_handler_char_get_func_camel_config_dir, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_CONFIG_DIR */
	NULL, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_ACCOUNT_DIR */
	NULL, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_ESOURCE_UID */
	NULL, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_SERVER_NAME */
	NULL, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_USER_NAME */
	NULL, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_USER_PASSWORD */
	NULL, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_URI */
	NULL  /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_PKCS11_USER_PIN */
};

static KolabSettingsHandlerCharSetFunc _kolab_settings_handler_char_set_funcs[] = {
	kolab_settings_handler_char_set_func_camel_data_dir, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_DATA_DIR */
	kolab_settings_handler_char_set_func_camel_cache_dir, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_CACHE_DIR */
	kolab_settings_handler_char_set_func_camel_config_dir, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_CONFIG_DIR */
	NULL, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_ACCOUNT_DIR */
	NULL, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_ESOURCE_UID */
	NULL, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_SERVER_NAME */
	NULL, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_USER_NAME */
	NULL, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_USER_PASSWORD */
	NULL, /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_URI */
	NULL  /* KOLAB_SETTINGS_HANDLER_CHAR_FIELD_PKCS11_USER_PIN */
};

static KolabSettingsHandlerGetFunc _kolab_settings_handler_uint_get_funcs[] = {
	NULL, /* KOLAB_SETTINGS_HANDLER_UINT_FIELD_FOLDER_CONTEXT */
	NULL  /* KOLAB_SETTINGS_HANDLER_UINT_FIELD_TLS_VARIANT */
};

static KolabSettingsHandlerUintSetFunc _kolab_settings_handler_uint_set_funcs[] = {
	kolab_settings_handler_uint_set_func_folder_context, /* KOLAB_SETTINGS_HANDLER_UINT_FIELD_FOLDER_CONTEXT */
	NULL  /* KOLAB_SETTINGS_HANDLER_UINT_FIELD_TLS_VARIANT */
};

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

static gboolean
kolab_settings_handler_char_get_func_camel_data_dir (KolabSettingsHandler *self, GError **err)
{
	KolabSettingsHandlerPrivate *priv = NULL;
	KolabFolderContextID context = KOLAB_FOLDER_CONTEXT_INVAL;
	gchar *data_dir = NULL;
	gchar *priv_data_dir = NULL;
	gchar *backend_dir = NULL;

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

	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);
	context = priv->sdata_uint[KOLAB_SETTINGS_HANDLER_UINT_FIELD_FOLDER_CONTEXT];

	priv_data_dir = priv->sdata_char[KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_DATA_DIR];
	if (priv_data_dir != NULL)
		return TRUE;

	/* TODO elaborate on directory creation */

	switch (context) {
	case KOLAB_FOLDER_CONTEXT_EMAIL:
		backend_dir = g_strdup ("mail");
		break;
	case KOLAB_FOLDER_CONTEXT_CONTACT:
		backend_dir = g_strdup ("addressbook");
		break;
	case KOLAB_FOLDER_CONTEXT_CALENDAR:
		backend_dir = g_strdup ("calendar");
		break;
	default:
		g_assert_not_reached ();
	}

	data_dir = g_build_filename (e_get_user_data_dir (),
	                             backend_dir,
	                             NULL);

	g_free (backend_dir);

	if (g_mkdir_with_parents (data_dir, 0700) != 0) {
		g_set_error (err,
		             G_FILE_ERROR,
		             g_file_error_from_errno (errno),
		             _("Could not create directory '%s' ('%s')"),
		             data_dir, g_strerror (errno));
		g_free (data_dir);
		return FALSE;
	}

	priv->sdata_char[KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_DATA_DIR] = data_dir;
	return TRUE;
}

static gboolean
kolab_settings_handler_char_get_func_camel_cache_dir (KolabSettingsHandler *self, GError **err)
{
	KolabSettingsHandlerPrivate *priv = NULL;
	KolabFolderContextID context = KOLAB_FOLDER_CONTEXT_INVAL;
	gchar *cache_dir = NULL;
	gchar *priv_cache_dir = NULL;
	gchar *backend_dir = NULL;

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

	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);
	context = priv->sdata_uint[KOLAB_SETTINGS_HANDLER_UINT_FIELD_FOLDER_CONTEXT];

	priv_cache_dir = priv->sdata_char[KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_CACHE_DIR];
	if (priv_cache_dir != NULL)
		return TRUE;

	/* TODO elaborate on directory creation */

	switch (context) {
	case KOLAB_FOLDER_CONTEXT_EMAIL:
		backend_dir = g_strdup ("mail");
		break;
	case KOLAB_FOLDER_CONTEXT_CONTACT:
		backend_dir = g_strdup ("addressbook");
		break;
	case KOLAB_FOLDER_CONTEXT_CALENDAR:
		backend_dir = g_strdup ("calendar");
		break;
	default:
		g_assert_not_reached ();
	}

	cache_dir = g_build_filename (e_get_user_cache_dir (),
	                              backend_dir,
	                              NULL);

	g_free (backend_dir);

	if (g_mkdir_with_parents (cache_dir, 0700) != 0) {
		g_set_error (err,
		             G_FILE_ERROR,
		             g_file_error_from_errno (errno),
		             _("Could not create directory '%s' ('%s')"),
		             cache_dir, g_strerror (errno));
		g_free (cache_dir);
		return FALSE;
	}

	priv->sdata_char[KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_CACHE_DIR] = cache_dir;
	return TRUE;
}

static gboolean
kolab_settings_handler_char_set_func_camel_data_dir (KolabSettingsHandler *self, gchar *value, GError **err)
{
	(void)self;
	(void)value;
	g_set_error (err,
	             KOLAB_BACKEND_ERROR,
	             KOLAB_BACKEND_ERROR_GENERIC,
	             _("Cannot set Camel data directory, it is a read-only resource"));
	return FALSE;
}

static gboolean
kolab_settings_handler_char_set_func_camel_cache_dir (KolabSettingsHandler *self, gchar *value, GError **err)
{
	(void)self;
	(void)value;
	/* FIXME mark as a translatable string */
	g_set_error (err,
	             KOLAB_BACKEND_ERROR,
	             KOLAB_BACKEND_ERROR_GENERIC,
	             "Cannot set Camel cache directory, it is a read-only resource");
	return FALSE;
}

static gboolean
kolab_settings_handler_char_get_func_camel_config_dir (KolabSettingsHandler *self, GError **err)
{
	KolabSettingsHandlerPrivate *priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);
	gboolean ok = FALSE;
	const gchar *data_dir = NULL;
	gchar *config_dir = NULL;
	gchar *priv_config_dir = NULL;
	GError *tmp_err = NULL;

	priv_config_dir = priv->sdata_char[KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_CONFIG_DIR];
	if (priv_config_dir != NULL)
		return TRUE;

	ok = kolab_settings_handler_char_get_func_camel_data_dir (self, &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	data_dir = priv->sdata_char[KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_DATA_DIR];

	config_dir = g_build_filename (data_dir, "config", NULL);

	if (g_mkdir_with_parents (config_dir, 0700) != 0) {
		g_set_error (err,
		             G_FILE_ERROR,
		             g_file_error_from_errno (errno),
		             _("Could not create directory '%s' ('%s')"),
		             config_dir, g_strerror (errno));
		g_free (config_dir);
		return FALSE;
	}

	priv->sdata_char[KOLAB_SETTINGS_HANDLER_CHAR_FIELD_CAMEL_CONFIG_DIR] = config_dir;
	return TRUE;
}

static gboolean
kolab_settings_handler_char_set_func_camel_config_dir (KolabSettingsHandler *self, gchar *value, GError **err)
{
	(void)self;
	(void)value;
	g_set_error (err,
	             KOLAB_BACKEND_ERROR,
	             KOLAB_BACKEND_ERROR_GENERIC,
	             _("Cannot set Camel configuration directory, it is a read-only resource"));
	return FALSE;
}

static gboolean
kolab_settings_handler_uint_set_func_folder_context (KolabSettingsHandler *self, guint value, GError **err)
{
	(void)self;
	(void)value;
	g_set_error (err,
	             KOLAB_BACKEND_ERROR,
	             KOLAB_BACKEND_ERROR_GENERIC,
	             _("Cannot change folder context after it has initially been set"));
	return FALSE;
}

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

static void
kolab_settings_handler_init (KolabSettingsHandler *object)
{
	KolabSettingsHandler *self = NULL;
	KolabSettingsHandlerPrivate *priv = NULL;
	gint ii = 0;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (object));

	self = KOLAB_SETTINGS_HANDLER (object);
	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);

	for (ii = 0; ii < KOLAB_SETTINGS_HANDLER_CHAR_LAST_FIELD; ii++)
		priv->sdata_char[ii] = NULL;
	for (ii = 0; ii < KOLAB_SETTINGS_HANDLER_UINT_LAST_FIELD; ii++)
		priv->sdata_uint[ii] = 0;
	for (ii = 0; ii < KOLAB_SETTINGS_HANDLER_INT_LAST_FIELD; ii++)
		priv->sdata_int[ii] = 0;
	for (ii = 0; ii < KOLAB_SETTINGS_HANDLER_BOOL_LAST_FIELD; ii++)
		priv->sdata_bool[ii] = FALSE;

	/* documenting initial settings (dumb fallback values) */
	priv->sdata_uint[KOLAB_SETTINGS_HANDLER_UINT_FIELD_FOLDER_CONTEXT] = KOLAB_FOLDER_CONTEXT_INVAL;
	priv->sdata_int[KOLAB_SETTINGS_HANDLER_INT_FIELD_KOLAB_SERVER_IMAP_PORT]  = 143;
	priv->sdata_int[KOLAB_SETTINGS_HANDLER_INT_FIELD_KOLAB_SERVER_IMAPS_PORT] = 993;
	priv->sdata_int[KOLAB_SETTINGS_HANDLER_INT_FIELD_KOLAB_SERVER_HTTP_PORT]  = 80;
	priv->sdata_int[KOLAB_SETTINGS_HANDLER_INT_FIELD_KOLAB_SERVER_HTTPS_PORT] = 443;
	priv->sdata_int[KOLAB_SETTINGS_HANDLER_INT_FIELD_KOLAB_SERVER_LDAP_PORT]  = 389;
	priv->sdata_int[KOLAB_SETTINGS_HANDLER_INT_FIELD_KOLAB_SERVER_LDAPS_PORT] = 636;

	priv->sdata_tbl = NULL;

	priv->is_configured = FALSE;
	priv->is_up = FALSE;
}

static void
kolab_settings_handler_dispose (GObject *object)
{
	/* KolabSettingsHandler *self = NULL; */
	/* KolabSettingsHandlerPrivate *priv = NULL; */

	/* self = KOLAB_SETTINGS_HANDLER (object); */
	/* priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self); */

	/* TODO: Add dispose code here */

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

static void
kolab_settings_handler_finalize (GObject *object)
{
	KolabSettingsHandler *self = NULL;
	KolabSettingsHandlerPrivate *priv = NULL;
	gint ii = 0;

	self = KOLAB_SETTINGS_HANDLER (object);
	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);

	for (ii = 0; ii < KOLAB_SETTINGS_HANDLER_CHAR_LAST_FIELD; ii++) {
		if (priv->sdata_char[ii] != NULL)
			g_free (priv->sdata_char[ii]);
	}

	if (priv->sdata_tbl != NULL)
		g_hash_table_destroy (priv->sdata_tbl);

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

static void
kolab_settings_handler_class_init (KolabSettingsHandlerClass *klass)
{
	GObjectClass* object_class = G_OBJECT_CLASS (klass);
	/* GObjectClass* parent_class = G_OBJECT_CLASS (klass); */

	g_type_class_add_private (klass, sizeof (KolabSettingsHandlerPrivate));

	object_class->dispose = kolab_settings_handler_dispose;
	object_class->finalize = kolab_settings_handler_finalize;
}

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

/**
 * kolab_settings_handler_configure:
 * @self: a #KolabSettingsHandler instance
 * @context: the folder context (i.e. backend type) the settings handler will
 *	     be used in (for now, either KOLAB_FOLDER_CONTEXT_CALENDAR or
 *	     KOLAB_FOLDER_CONTEXT_CONTACT)
 * @err: a #GError object (or NULL)
 *
 * Configures the object for operation. Must be called once after object
 * instantiation and before any other operation.
 *
 * Returns: TRUE on success,
 *          FALSE otherwise (with @err set)
 */
gboolean
kolab_settings_handler_configure  (KolabSettingsHandler *self,
                                   KolabFolderContextID context,
                                   GError **err)
{
	KolabSettingsHandlerPrivate *priv = NULL;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (self));
	g_assert ((context > KOLAB_FOLDER_CONTEXT_INVAL) &&
	          (context < KOLAB_FOLDER_LAST_CONTEXT));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);

	/* configure once... */
	if (priv->is_configured == TRUE)
		return TRUE;

	priv->sdata_uint[KOLAB_SETTINGS_HANDLER_UINT_FIELD_FOLDER_CONTEXT] = context;

	priv->is_configured = TRUE;
	return TRUE;
}

/**
 * kolab_settings_handler_bringup:
 * @self: a #KolabSettingsHandler instance
 * @err: a #GError object (or NULL)
 *
 * Gets the #KolabSettingsHandler object into operational mode. This may involve
 * file I/O.
 * Must be called once after kolab_settings_handler_configure() and before
 * any other operation.
 *
 * Returns: TRUE on success,
 *          FALSE otherwise (with @err set)
 */
gboolean
kolab_settings_handler_bringup (KolabSettingsHandler *self,
                                GError **err)
{
	KolabSettingsHandlerPrivate *priv = NULL;

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

	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);

	g_assert (priv->is_configured == TRUE);
	g_assert (priv->is_up == FALSE);

	/* TODO implement me */

	priv->is_up = TRUE;
	return TRUE;
}

/**
 * kolab_settings_handler_shutdown:
 * @self: a #KolabSettingsHandler instance
 * @err: a #GError object (or NULL)
 *
 * Shuts down the #KolabSettingsHandler object.
 * Must be called before object destruction. No further operation on the
 * object is valid unless kolab_settings_handler_bringup() is called again.
 *
 * Returns: TRUE on success,
 *          FALSE otherwise (with @err set)
 */
gboolean
kolab_settings_handler_shutdown (KolabSettingsHandler *self,
                                 GError **err)
{
	KolabSettingsHandlerPrivate *priv = NULL;

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

	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);

	g_assert (priv->is_configured == TRUE);
	g_assert (priv->is_up == TRUE);

	/* TODO implement me */

	priv->is_up = FALSE;
	return TRUE;
}

/*----------------------------------------------------------------------------*/
/* config item getters/setters */

/**
 * kolab_settings_handler_set_char_field:
 * @self: a #KolabSettingsHandler instance
 * @field_id: the char field id to set a value for
 * @value: the string to set as a value for the given field
 * @err: a #GError object (or NULL)
 *
 * Sets @value as the value for the char field selected by @field_id.
 * If an error occurs and the internal value of the char field selected by
 * @field_id is not changed, no ownership is taken of @value. If the function
 * returns successfully, the #KolabSettingsHandler instance will have taken
 * ownership of @value, so make sure it is not free'd later on.
 *
 * Returns: TRUE on success,
 *          FALSE otherwise (with @err set)
 */
gboolean
kolab_settings_handler_set_char_field (KolabSettingsHandler *self,
                                       KolabSettingsHandlerCharFieldID field_id,
                                       gchar *value,
                                       GError **err)
{
	KolabSettingsHandlerPrivate *priv = NULL;
	KolabSettingsHandlerCharSetFunc setfunc = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (self));
	g_assert (field_id < KOLAB_SETTINGS_HANDLER_CHAR_LAST_FIELD);
	/* value may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);

	g_assert (priv->is_configured == TRUE);
	g_assert (priv->is_up == TRUE);

	setfunc = _kolab_settings_handler_char_set_funcs[field_id];
	if (setfunc != NULL) {
		ok = setfunc (self, value, &tmp_err);
		if (! ok) {
			g_propagate_error (err, tmp_err);
			return FALSE;
		}
	}

	if (priv->sdata_char[field_id] != NULL)
		g_free (priv->sdata_char[field_id]);

	priv->sdata_char[field_id] = value;
	return TRUE;
}

/**
 * kolab_settings_handler_get_char_field:
 * @self: a #KolabSettingsHandler instance
 * @field_id: the char field id to get a value from
 * @err: a #GError object (or NULL)
 *
 * Gets the value of the char field selected by @field_id.
 *
 * Returns: the referenced string on success,
 *          NULL otherwise (with @err set)
 */
const gchar*
kolab_settings_handler_get_char_field (KolabSettingsHandler *self,
                                       KolabSettingsHandlerCharFieldID field_id,
                                       GError **err)
{
	KolabSettingsHandlerPrivate *priv = NULL;
	KolabSettingsHandlerGetFunc getfunc = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (self));
	g_assert (field_id < KOLAB_SETTINGS_HANDLER_CHAR_LAST_FIELD);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);

	g_assert (priv->is_configured == TRUE);
	g_assert (priv->is_up == TRUE);

	getfunc = _kolab_settings_handler_char_get_funcs[field_id];
	if (getfunc != NULL) {
		ok = getfunc (self, &tmp_err);
		if (! ok) {
			g_propagate_error (err, tmp_err);
			return NULL;
		}
	}

	return priv->sdata_char[field_id];
}

/**
 * kolab_settings_handler_set_uint_field:
 * @self: a #KolabSettingsHandler instance
 * @field_id: the uint field id to set a value for
 * @value: value to set for the given field
 * @err: a #GError object (or NULL)
 *
 * Sets @value as the value for the uint field selected by @field_id.
 * If an error occurs, the internal value of the uint field selected by
 * @field_id is not changed.
 *
 * Returns: TRUE on success,
 *          FALSE otherwise (with @err set)
 */
gboolean
kolab_settings_handler_set_uint_field (KolabSettingsHandler *self,
                                       KolabSettingsHandlerUintFieldID field_id,
                                       guint value,
                                       GError **err)
{
	KolabSettingsHandlerPrivate *priv = NULL;
	KolabSettingsHandlerUintSetFunc setfunc = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (self));
	g_assert (field_id < KOLAB_SETTINGS_HANDLER_UINT_LAST_FIELD);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);

	g_assert (priv->is_configured == TRUE);
	g_assert (priv->is_up == TRUE);

	setfunc = _kolab_settings_handler_uint_set_funcs[field_id];
	if (setfunc != NULL) {
		ok = setfunc (self, value, &tmp_err);
		if (! ok) {
			g_propagate_error (err, tmp_err);
			return FALSE;
		}
	}

	priv->sdata_uint[field_id] = value;
	return TRUE;
}

/**
 * kolab_settings_handler_get_uint_field:
 * @self: a #KolabSettingsHandler instance
 * @field_id: the uint field id to get a value from
 * @err: a #GError object (or NULL)
 *
 * Gets the value of the uint field selected by @field_id. Check @err for errors
 * since 0 may be a valid return value for the uint field.
 *
 * Returns: the referenced unsigned integer on success,
 *          0 otherwise (with @err set)
 */
guint
kolab_settings_handler_get_uint_field (KolabSettingsHandler *self,
                                       KolabSettingsHandlerUintFieldID field_id,
                                       GError **err)
{
	KolabSettingsHandlerPrivate *priv = NULL;
	KolabSettingsHandlerGetFunc getfunc = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (self));
	g_assert (field_id < KOLAB_SETTINGS_HANDLER_UINT_LAST_FIELD);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);

	g_assert (priv->is_configured == TRUE);
	g_assert (priv->is_up == TRUE);

	getfunc = _kolab_settings_handler_uint_get_funcs[field_id];
	if (getfunc != NULL) {
		ok = getfunc (self, &tmp_err);
		if (! ok) {
			g_propagate_error (err, tmp_err);
			return 0; /* no error indicator, check **err */
		}
	}

	return priv->sdata_uint[field_id];
}

/**
 * kolab_settings_handler_set_int_field:
 * @self: a #KolabSettingsHandler instance
 * @field_id: the int field id to set a value for
 * @value: value to set for the given field
 * @err: a #GError object (or NULL)
 *
 * Sets @value as the value for the int field selected by @field_id.
 * If an error occurs, the internal value of the int field selected by
 * @field_id is not changed.
 *
 * Returns: TRUE on success,
 *          FALSE otherwise (with @err set)
 */
gboolean
kolab_settings_handler_set_int_field (KolabSettingsHandler *self,
                                      KolabSettingsHandlerIntFieldID field_id,
                                      gint value,
                                      GError **err)
{
	KolabSettingsHandlerPrivate *priv = NULL;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (self));
	/* g_assert (field_id < KOLAB_SETTINGS_HANDLER_INT_LAST_FIELD); */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);

	g_assert (priv->is_configured == TRUE);
	g_assert (priv->is_up == TRUE);

	priv->sdata_int[field_id] = value;
	return TRUE;
}

/**
 * kolab_settings_handler_get_int_field:
 * @self: a #KolabSettingsHandler instance
 * @field_id: the int field id to get a value from
 * @err: a #GError object (or NULL)
 *
 * Gets the value of the int field selected by @field_id. Check @err for errors
 * since 0 may be a valid return value for the int field.
 *
 * Returns: the referenced integer on success,
 *          0 otherwise (with @err set)
 */
gint
kolab_settings_handler_get_int_field (KolabSettingsHandler *self,
                                      KolabSettingsHandlerIntFieldID field_id,
                                      GError **err)
{
	KolabSettingsHandlerPrivate *priv = NULL;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (self));
	/* g_assert (field_id < KOLAB_SETTINGS_HANDLER_INT_LAST_FIELD); */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);

	g_assert (priv->is_configured == TRUE);
	g_assert (priv->is_up == TRUE);

	return priv->sdata_int[field_id];
}

/**
 * kolab_settings_handler_set_bool_field:
 * @self: a #KolabSettingsHandler instance
 * @field_id: the bool field id to set a value for
 * @value: value to set for the given field
 * @err: a #GError object (or NULL)
 *
 * Sets @value as the value for the bool field selected by @field_id.
 * If an error occurs, the internal value of the bool field selected by
 * @field_id is not changed.
 *
 * Returns: TRUE on success,
 *          FALSE otherwise (with @err set)
 */
gboolean
kolab_settings_handler_set_bool_field (KolabSettingsHandler *self,
                                       KolabSettingsHandlerBoolFieldID field_id,
                                       gboolean value,
                                       GError **err)
{
	KolabSettingsHandlerPrivate *priv = NULL;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (self));
	/* g_assert (field_id < KOLAB_SETTINGS_HANDLER_BOOL_LAST_FIELD); */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);

	g_assert (priv->is_configured == TRUE);
	g_assert (priv->is_up == TRUE);

	priv->sdata_bool[field_id] = value;
	return TRUE;
}

/**
 * kolab_settings_handler_get_bool_field:
 * @self: a #KolabSettingsHandler instance
 * @field_id: the bool field id to get a value from
 * @err: a #GError object (or NULL)
 *
 * Gets the value of the bool field selected by @field_id. Check @err for errors
 * since FALSE may be a valid return value for the bool field.
 *
 * Returns: the referenced boolean value on success,
 *          FALSE otherwise (with @err set)
 */
gboolean
kolab_settings_handler_get_bool_field (KolabSettingsHandler *self,
                                       KolabSettingsHandlerBoolFieldID field_id,
                                       GError **err)
{
	KolabSettingsHandlerPrivate *priv = NULL;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (self));
	/* g_assert (field_id < KOLAB_SETTINGS_HANDLER_BOOL_LAST_FIELD); */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);

	g_assert (priv->is_configured == TRUE);
	g_assert (priv->is_up == TRUE);

	return priv->sdata_bool[field_id];
}

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

static GHashTable*
kolab_settings_handler_create_table (KolabSettingsHandlerTblID tbl_id)
{
	/* g_assert (tbl_id < KOLAB_SETTINGS_HANDLER_TBL_LAST_ID); */

	switch (tbl_id) {
	case KOLAB_SETTINGS_HANDLER_TBL_SYNCSTRATEGY:
		/* GINT_TO_POINTER() values (don't free()) */
		return g_hash_table_new_full (g_str_hash,
		                              g_str_equal,
		                              g_free,
		                              NULL);
	case KOLAB_SETTINGS_HANDLER_TBL_FOLDERCREATE:
		/* values are pointers to keys, don't free() */
		return g_hash_table_new_full (g_str_hash,
		                              g_str_equal,
		                              g_free,
		                              NULL);
	default:
		g_assert_not_reached ();
	}
}

/**
 * kolab_settings_handler_set_value:
 * @self: a #KolabSettingsHandler instance
 * @tbl_id: ID for the internal table to store the @value in
 * @key: the key string to reference the value with
 * @value: a pointer to the data object to store
 * @err: a #GError object (or NULL)
 *
 * Store a @value under the given @key in an internal table identified
 * by @tbl_id. Be sure that the data pointed to does not get erased as long
 * as it is accessible via @tbl_id:@key.
 *
 * The data type @value points to is determined by the @tbl_id. Each table
 * has a certain value data type associated with it (see #KolabSettingsHandlerTblID),
 * so be sure to supply only pointers to the data type this table is meant
 * for (orelse, table destruction or replacing of existing values will fail
 * miserably).
 *
 * Returns: TRUE on success,
 *          FALSE otherwise (with @err set)
 */
gboolean
kolab_settings_handler_set_value (KolabSettingsHandler *self,
                                  KolabSettingsHandlerTblID tbl_id,
                                  const gchar *key,
                                  gpointer value,
                                  GError **err)
{
	KolabSettingsHandlerPrivate *priv = NULL;
	GHashTable *tbl = NULL;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (self));
	/* g_assert (tbl_id < KOLAB_SETTINGS_HANDLER_TBL_LAST_ID); */
	g_assert (key != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);

	g_assert (priv->is_configured == TRUE);
	g_assert (priv->is_up == TRUE);

	if (priv->sdata_tbl == NULL)
		priv->sdata_tbl = g_hash_table_new_full (g_direct_hash,
		                                         g_direct_equal,
		                                         NULL,
		                                         kolab_util_glib_ghashtable_gdestroy);
	tbl = g_hash_table_lookup (priv->sdata_tbl,
	                           GINT_TO_POINTER (tbl_id));
	if (tbl == NULL) {
		tbl = kolab_settings_handler_create_table (tbl_id);
		g_hash_table_insert (priv->sdata_tbl,
		                     GINT_TO_POINTER (tbl_id),
		                     tbl);
	}

	g_hash_table_replace (tbl, g_strdup (key), value);
	return TRUE;
}

/**
 * kolab_settings_handler_get_value:
 * @self: a #KolabSettingsHandler instance
 * @tbl_id: ID for the internal table to store the @value in
 * @key: the key string to reference the value with
 * @err: a #GError object (or NULL)
 *
 * Retrieve a @value under the given @key from an internal table identified
 * by @tbl_id.
 *
 * The data type @value points to is determined by the @tbl_id. Each table
 * has a certain value data type associated with it (see #KolabSettingsHandlerTblID).
 *
 * It is an error to try to retrieve a value if nothing has ever been stored
 * in any table or if a @tbl_id is given for a table into which nothing has
 * been stored before, or if there is no value stored for @key in @tbl_id
 * (@err has an error value set to #KOLAB_BACKEND_ERROR_NOTFOUND in these cases).
 *
 * Returns: a pointer to the data referenced by @tbl_id:@key on success,
 *	    NULL with @err set on failure or if not found
 */
gconstpointer
kolab_settings_handler_get_value (KolabSettingsHandler *self,
                                  KolabSettingsHandlerTblID tbl_id,
                                  const gchar *key,
                                  GError **err)
{
	KolabSettingsHandlerPrivate *priv = NULL;
	GHashTable *tbl = NULL;
	gpointer value = NULL;
	gpointer orig_key = NULL;
	gboolean found = FALSE;

	g_assert (KOLAB_IS_SETTINGS_HANDLER (self));
	/* g_assert (tbl_id < KOLAB_SETTINGS_HANDLER_TBL_LAST_ID); */
	g_assert (key != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_SETTINGS_HANDLER_PRIVATE (self);

	g_assert (priv->is_configured == TRUE);
	g_assert (priv->is_up == TRUE);

	if (priv->sdata_tbl == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_NOTFOUND,
		             _("Internal inconsistency detected: No settings table exists, no value has previously been set"));
		return NULL;
	}

	tbl = g_hash_table_lookup (priv->sdata_tbl,
	                           GINT_TO_POINTER (tbl_id));
	if (tbl == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_NOTFOUND,
		             _("Internal inconsistency detected: No settings table with ID '%i' exists"),
		             tbl_id);
		return NULL;
	}

	found = g_hash_table_lookup_extended (tbl, key, &orig_key, &value);
	if (! found) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_NOTFOUND,
		             _("Internal inconsistency detected: No settings value for table ID '%i', key ID '%s', exists"),
		             tbl_id, key);
		return NULL;
	}

	return value; /* may be NULL */
}

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