/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/***************************************************************************
 *            camel-imapx-metadata.c
 *
 *  Mon Oct 11 12:43:03 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 main.c; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

/*----------------------------------------------------------------------------*/
/* ANNOTATEMORE / METADATA (RFC 5464) */

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

#include <string.h>
#include <glib/gi18n-lib.h>

/* Kolab error reporting */
#include <libekolabutil/kolab-util-error.h>

#include "camel-imapx-utils.h"
#include "camel-imapx-metadata.h"

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

CamelImapxMetadataAttrib*
camel_imapx_metadata_attrib_new (void)
{
	gint acc = 0;
	CamelImapxMetadataAttrib *ma = NULL;

	ma = g_new0 (CamelImapxMetadataAttrib, 1);
	for (acc = 0; acc < CAMEL_IMAPX_METADATA_LAST_ACCESS; acc++) {
		ma->data[acc] = NULL;
		ma->type[acc] = CAMEL_IMAPX_METADATA_ATTRIB_TYPE_UNSET;
	}
	return ma;
}

void
camel_imapx_metadata_attrib_free (CamelImapxMetadataAttrib *ma)
{
	gint acc = 0;

	if (ma == NULL)
		return;

	for (acc = 0; acc < CAMEL_IMAPX_METADATA_LAST_ACCESS; acc++) {
		if (ma->data[acc])
			g_byte_array_free (ma->data[acc], TRUE);
	}

	g_free (ma);
}

static void
camel_imapx_metadata_attrib_gdestroy (gpointer data)
{
	CamelImapxMetadataAttrib *ma = (CamelImapxMetadataAttrib *)data;
	camel_imapx_metadata_attrib_free (ma);
}

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

CamelImapxMetadataEntry*
camel_imapx_metadata_entry_new (void)
{
	CamelImapxMetadataEntry *me = g_new0 (CamelImapxMetadataEntry, 1);
	me->attributes = g_hash_table_new_full (g_str_hash,
	                                        g_str_equal,
	                                        g_free,
	                                        camel_imapx_metadata_attrib_gdestroy);
	return me;
}

void
camel_imapx_metadata_entry_free (CamelImapxMetadataEntry *me)
{
	if (me == NULL)
		return;

	if (me->attributes)
		/* needs key:val destroy functions set */
		g_hash_table_destroy (me->attributes);
	g_free (me);
}

static void
camel_imapx_metadata_entry_gdestroy (gpointer data)
{
	CamelImapxMetadataEntry *me = (CamelImapxMetadataEntry *)data;
	camel_imapx_metadata_entry_free (me);
}

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

CamelImapxMetadataAnnotation*
camel_imapx_metadata_annotation_new (void)
{
	CamelImapxMetadataAnnotation *man = g_new0 (CamelImapxMetadataAnnotation, 1);
	man->entries = g_hash_table_new_full (g_str_hash,
	                                      g_str_equal,
	                                      g_free,
	                                      camel_imapx_metadata_entry_gdestroy);
	return man;
}

void
camel_imapx_metadata_annotation_free (CamelImapxMetadataAnnotation *man)
{
	if (man == NULL)
		return;

	if (man->entries)
		/* needs key:val destroy functions set */
		g_hash_table_destroy (man->entries);
	g_free (man);
}

static void
camel_imapx_metadata_annotation_gdestroy (gpointer data)
{
	CamelImapxMetadataAnnotation *man = (CamelImapxMetadataAnnotation *)data;
	camel_imapx_metadata_annotation_free (man);
}

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

CamelImapxMetadata*
camel_imapx_metadata_new (camel_imapx_metadata_proto_t proto,
                          gboolean locked)
{
	CamelImapxMetadata *md = NULL;
	g_assert (proto < CAMEL_IMAPX_METADATA_LAST_PROTO);

	md = g_new0 (CamelImapxMetadata, 1);
	md->md_lock = g_mutex_new ();

	if (locked)
		g_mutex_lock (md->md_lock);

	md->proto = proto;
	md->mboxes = g_hash_table_new_full (g_str_hash,
	                                    g_str_equal,
	                                    g_free,
	                                    camel_imapx_metadata_annotation_gdestroy);
	return md;
}

void
camel_imapx_metadata_free (CamelImapxMetadata *md)
{
	if (md == NULL)
		return;

	if (md->mboxes)
		g_hash_table_destroy (md->mboxes);

	while (!g_mutex_trylock (md->md_lock));
	g_mutex_unlock (md->md_lock);
	g_mutex_free (md->md_lock);

	g_free (md);
}

CamelImapxMetadata*
camel_imapx_metadata_resect (CamelImapxMetadata **md)
{
	CamelImapxMetadata *tmp_md = NULL;
	GMutex *tmp_lock = NULL;

	if (md == NULL)
		return NULL;

	/* (acquire md lock) */
	g_mutex_lock ((*md)->md_lock);

	tmp_md = *md;
	*md = camel_imapx_metadata_new (tmp_md->proto, FALSE);

	/* (swap lock pointers of md and tmp_md) */
	tmp_lock = tmp_md->md_lock;		/* locked   */
	tmp_md->md_lock = (*md)->md_lock;	/* unlocked */
	(*md)->md_lock = tmp_lock;		/* locked   */

	/* (release md lock) */
	g_mutex_unlock ((*md)->md_lock);

	return tmp_md;
}

camel_imapx_metadata_proto_t
camel_imapx_metadata_get_proto (CamelImapxMetadata *md)
{
	camel_imapx_metadata_proto_t mp = CAMEL_IMAPX_METADATA_PROTO_INVAL;

	if (md == NULL)
		return CAMEL_IMAPX_METADATA_PROTO_INVAL;

	g_assert (md->md_lock != NULL);

	/* (acquire md lock) */
	g_mutex_lock (md->md_lock);

	mp = md->proto;

	/* (acquire md lock) */
	g_mutex_unlock (md->md_lock);

	return mp;
}

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

/* IMAP ANNOTATEMORE Extension
 * draft-daboo-imap-annotatemore-05
 *
 * annotate-data = "ANNOTATION" SP mailbox SP entry-list
 *   ; empty string for mailbox implies
 *   ; server annotation.
 *
 * mailbox = "INBOX" / astring
 *  ; INBOX is case-insensitive.  All case variants of
 *  ; INBOX (e.g., "iNbOx") MUST be interpreted as INBOX
 *  ; not as an astring.  An astring which consists of
 *  ; the case-insensitive sequence "I" "N" "B" "O" "X"
 *  ; is considered to be INBOX and not an astring.
 *
 * entry-list = entry-att *(SP entry-att) /
 *              "(" entry *(SP entry) ")"
 *  ; entry attribute-value pairs list for
 *  ; GETANNOTATION response, or
 *  ; parenthesised entry list for unsolicited
 *  ; notification of annotation changes
 *
 * entry-att = entry SP "(" att-value *(SP att-value) ")"
 *
 * entry = string
 *  ; slash-separated path to entry
 *  ; MUST NOT contain "*" or "%"
 *
 * att-value = attrib SP value
 *
 * attrib = string
 *  ; dot-separated attribute name
 *  ; MUST NOT contain "*" or "%"
 *
 * value = nstring
 *
 * nstring = string / "NIL"
 */
static gboolean
camel_imapx_metadata_parse_annotation_response (CamelImapxMetadata *md,
                                                CamelIMAPXStream *is,
                                                GCancellable *cancellable,
                                                GError **err)
{
	gint tok = 0;
	guint len = 0;
	guchar *token = NULL;
	GError *tmp_err = NULL;

	gchar *mbox_name = NULL;
	gchar *annot_name = NULL;

	CamelImapxMetadataAnnotation *man = NULL;
	CamelImapxMetadataEntry *me = NULL;

	g_assert (md != NULL);
	g_assert (md->mboxes != NULL);
	g_assert (CAMEL_IS_IMAPX_STREAM (is));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	/* mailbox name */
	tok = camel_imapx_stream_astring (is, &token, cancellable, &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	mbox_name = g_strdup ((gchar *) token);

	/* annotation name */
	tok = camel_imapx_stream_astring (is, &token, cancellable, &tmp_err);
	if (tmp_err != NULL) {
		g_free (mbox_name);
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	annot_name = g_strdup ((gchar *) token);

	/* get annotation structure */
	man = g_hash_table_lookup (md->mboxes, mbox_name);
	if (man == NULL) {
		man = camel_imapx_metadata_annotation_new ();
		g_hash_table_insert (md->mboxes,
		                     g_strdup (mbox_name),
		                     man);
	}

	/* get entry structure */
	me = g_hash_table_lookup (man->entries, annot_name);
	if (me == NULL) {
		me = camel_imapx_metadata_entry_new ();
		g_hash_table_insert (man->entries,
		                     g_strdup (annot_name),
		                     me);
	}

	g_free (annot_name);
	g_free (mbox_name);

	/* parse and add entry attributes
	 * If parse fails, *man and *me will stay around, keeping the
	 * contents we were able to parse before failure
	 */
	tok = camel_imapx_stream_token (is, &token, &len, cancellable, &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	if (tok != '(') {
		tmp_err = g_error_new (KOLAB_CAMEL_ERROR,
		                       KOLAB_CAMEL_ERROR_GENERIC,
		                       "%s: malformed annotation response (expected '(' after annotation name)",
		                       __func__);
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	while (TRUE) {
		CamelImapxMetadataAttrib *ma = NULL;
		gchar *attrib_name = NULL;
		gchar *attrib_suffix[2] = {NULL, NULL};
		guchar *attrib_value = NULL;
		camel_imapx_metadata_access_t acc = CAMEL_IMAPX_METADATA_ACCESS_PRIVATE;
		gboolean valid_utf8 = FALSE;
		guint len = 0;

		tok = camel_imapx_stream_token (is, &token, &len, cancellable, &tmp_err);
		if (tmp_err != NULL) {
			g_propagate_error (err, tmp_err);
			return FALSE;
		}
		if (tok == ')')
			break;
		camel_imapx_stream_ungettoken (is, tok, token, len);

		/* we do not handle unsolicited responses here, so
		 * do not ENABLE them on the server
		 */
		/* attribute name */
		tok = camel_imapx_stream_astring (is, &token, cancellable, &tmp_err);
		if (tmp_err != NULL) {
			g_propagate_error (err, tmp_err);
			return FALSE;
		}
		attrib_name = g_strdup ((gchar* ) token);

		/* attribute value */
		tok = camel_imapx_stream_nstring (is, &token, cancellable, &tmp_err);
		if (tmp_err != NULL) {
			g_free (attrib_name);
			g_propagate_error (err, tmp_err);
			return FALSE;
		}
		attrib_value = token; /* token owned by stream, do not g_free(attrib_value) */

		/* check .priv/.shared suffix */
		attrib_suffix[0] = g_strrstr (attrib_name, ".priv");
		attrib_suffix[1] = g_strrstr (attrib_name, ".shared"); /* if NULL then we have no suffix at all */

		if (attrib_suffix[0] != NULL) {
			/* private */
			acc = CAMEL_IMAPX_METADATA_ACCESS_PRIVATE;
			attrib_suffix[0][0] = '\0';
		} else {
			/* shared (i.e. not private) */
			acc = CAMEL_IMAPX_METADATA_ACCESS_SHARED;
			if (attrib_suffix[1] != NULL)
				attrib_suffix[1][0] = '\0';
		}

		/* get/create and fill attribute structure */
		ma = g_hash_table_lookup (me->attributes, attrib_name);
		if (ma == NULL) {
			ma = camel_imapx_metadata_attrib_new ();
			g_hash_table_insert (me->attributes,
			                     g_strdup (attrib_name),
			                     ma);
		}
		if ((ma->type[acc] != CAMEL_IMAPX_METADATA_ATTRIB_TYPE_UNSET) &&
		    (ma->type[acc] != CAMEL_IMAPX_METADATA_ATTRIB_TYPE_NIL)) {
			g_byte_array_free (ma->data[acc], TRUE);
			g_byte_array_unref (ma->data[acc]);
			/* refcount should now be 0 */
		}

		/* add value data to attribute structure */
		len = strlen ((gchar*) attrib_value);
		ma->data[acc] = g_byte_array_new ();
		g_byte_array_append (ma->data[acc],
		                     attrib_value,
		                     len + 1);
		ma->data[acc]->data[len] = '\0';

		if (attrib_value == NULL) {
			ma->type[acc] = CAMEL_IMAPX_METADATA_ATTRIB_TYPE_NIL;
			goto val_add_done;
		}

		valid_utf8 = g_utf8_validate ((gchar*) attrib_value, (gssize) len, NULL);
		if (valid_utf8) {
			/* value is still stored as byte array (guchar),
			 * no type conversion here (just info)
			 */
			ma->type[acc] = CAMEL_IMAPX_METADATA_ATTRIB_TYPE_UTF8;
			goto val_add_done;
		}

		/* BINARY is not fully true since we read attrib_value as
		 * 'nstring' (so it cannot contain NUL bytes). It means a
		 * NUL-terminated string with unknown encoding here
		 */
		ma->type[acc] = CAMEL_IMAPX_METADATA_ATTRIB_TYPE_BINARY;
		/* add done */

	val_add_done:

		/* cleanup: restore attrib_name (undo NUL insertion), then free it */
		if (attrib_suffix[0] != NULL)
			attrib_suffix[0][0] = '.';
		if (attrib_suffix[1] != NULL)
			attrib_suffix[1][0] = '.';
		g_free (attrib_name);
	}

	return TRUE;
}

static gboolean
camel_imapx_metadata_parse_metadata_response (CamelImapxMetadata *md,
                                              CamelIMAPXStream *is,
                                              GCancellable *cancellable,
                                              GError **err)
{
	/* TODO implement me*/

	(void)cancellable;

	g_assert (md != NULL);
	g_assert (md->mboxes != NULL);
	g_assert (CAMEL_IS_IMAPX_STREAM (is));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	return TRUE;
}

gboolean
camel_imapx_metadata_add_from_server_response (CamelImapxMetadata *md,
                                               CamelIMAPXStream *is,
                                               GCancellable *cancellable,
                                               GError **err)
{
	GError *tmp_err = NULL;
	gboolean parse_ok = FALSE;

	g_assert (md != NULL);
	g_assert (md->mboxes != NULL);
	g_assert (CAMEL_IS_IMAPX_STREAM (is));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	/* (acquire md lock ) */
	g_mutex_lock (md->md_lock);

	switch (md->proto) {
	case CAMEL_IMAPX_METADATA_PROTO_ANNOTATEMORE:
		parse_ok = camel_imapx_metadata_parse_annotation_response (md,
		                                                           is,
		                                                           cancellable,
		                                                           &tmp_err);
		break;
	case CAMEL_IMAPX_METADATA_PROTO_METADATA:
		parse_ok = camel_imapx_metadata_parse_metadata_response (md,
		                                                         is,
		                                                         cancellable,
		                                                         &tmp_err);
		break;
	case CAMEL_IMAPX_METADATA_PROTO_INVAL:
		/* (release md lock) */
		g_mutex_unlock (md->md_lock);
		g_set_error (err,
		             KOLAB_CAMEL_ERROR,
		             KOLAB_CAMEL_ERROR_GENERIC,
		             _("Invalid IMAP annotation protocol"));
		return FALSE;
	default:
		/* can't happen... */
		g_error ("%s: have unknown metadata protocol type %i",
		         __func__, md->proto);
	}

	/* (release md lock) */
	g_mutex_unlock (md->md_lock);

	if (!parse_ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	return TRUE;
}

GSList*
camel_imapx_metadata_new_commandlist (const CamelImapxMetadata *md)
{
	GSList *commands = NULL;
	GHashTableIter md_iter;
	gpointer md_key = NULL;
	gpointer md_value = NULL;

	if (md == NULL)
		return NULL;

	g_assert (md->md_lock != NULL);
	/* (acquire md lock ) */
	g_mutex_lock (md->md_lock);

	if (md->mboxes == NULL) {
		/* (release md lock) */
		g_mutex_unlock (md->md_lock);
		return NULL;
	}

	g_hash_table_iter_init (&md_iter, md->mboxes);
	while (g_hash_table_iter_next (&md_iter, &md_key, &md_value)) {
		gchar *mbox_name = (gchar *) md_key;
		CamelImapxMetadataAnnotation *man = (CamelImapxMetadataAnnotation *) md_value;
		GHashTableIter man_iter;
		gpointer man_key = NULL;
		gpointer man_value = NULL;

		g_hash_table_iter_init (&man_iter, man->entries);
		while (g_hash_table_iter_next (&man_iter, &man_key, &man_value)) {
			gchar *annot_name = (gchar *) man_key;
			CamelImapxMetadataEntry *me = (CamelImapxMetadataEntry *) man_value;
			GString *cmd = NULL;
			gchar *cmd_top = NULL;
			GHashTableIter me_iter;
			gpointer me_key = NULL;
			gpointer me_value = NULL;
			gboolean have_entries = FALSE;

			switch (md->proto) {
			case CAMEL_IMAPX_METADATA_PROTO_ANNOTATEMORE:
				cmd_top = g_strdup_printf ("SETANNOTATION \"%s\" \"%s\" (",
				                           mbox_name,
				                           annot_name);
				break;
			case CAMEL_IMAPX_METADATA_PROTO_METADATA:
				/* FIXME untested! */
				cmd_top = g_strdup_printf ("SETMETADATA %s (",
				                           mbox_name);
				break;
			case CAMEL_IMAPX_METADATA_PROTO_INVAL:
				/* (release md lock) */
				g_mutex_unlock (md->md_lock);
				if (commands != NULL)
					g_slist_free (commands);
				if (cmd != NULL)
					g_string_free (cmd, TRUE);
				g_debug ("%s: command list empty due to invalid annotation protocol",
				         __func__);
				return NULL;
			default:
				/* FIXME better handling here */
				g_assert_not_reached ();
			}
			cmd = g_string_new (cmd_top);
			g_free (cmd_top);

			g_hash_table_iter_init (&me_iter, me->attributes);
			while (g_hash_table_iter_next (&me_iter, &me_key, &me_value)) {
				gchar *attrib_name = (gchar *) me_key;
				CamelImapxMetadataAttrib *ma = (CamelImapxMetadataAttrib *) me_value;
				gchar *attrib_str = NULL;
				gint acc = 0;

				for (acc = 0; acc < CAMEL_IMAPX_METADATA_LAST_ACCESS; acc++) {
					if (ma->type[acc] == CAMEL_IMAPX_METADATA_ATTRIB_TYPE_UNSET)
						/* caution: UNSET != NIL (for NIL, a command gets issued) */
						continue;

					if (have_entries)
						g_string_append (cmd, " ");

					have_entries = TRUE;

					/* add attribute name and access type to command string */
					switch (md->proto) {
					case CAMEL_IMAPX_METADATA_PROTO_ANNOTATEMORE:
						/* TODO create a lookup table for this */
						/* for ANNOTATEMORE, enclose attrib names with " */
						if (acc == CAMEL_IMAPX_METADATA_ACCESS_PRIVATE)
							attrib_str = g_strdup_printf ("\"%s.priv\"",
							                              attrib_name);
						else
							attrib_str = g_strdup_printf ("\"%s.shared\"",
							                              attrib_name);
						break;
					case CAMEL_IMAPX_METADATA_PROTO_METADATA:
						/* FIXME untested! */
						/* TODO create a lookup table for this */
						/* for METADATA, attrib names go without enclosing " */
						if (acc == CAMEL_IMAPX_METADATA_ACCESS_PRIVATE)
							attrib_str = g_strdup_printf ("/private%s/%s",
							                              annot_name,
							                              attrib_name);
						else
							attrib_str = g_strdup_printf ("/shared%s/%s",
							                              annot_name,
							                              attrib_name);
						break;
					case CAMEL_IMAPX_METADATA_PROTO_INVAL:
						/* (release md lock) */
						g_mutex_unlock (md->md_lock);
						if (commands != NULL)
							g_slist_free (commands);
						if (cmd != NULL)
							g_string_free (cmd, TRUE);
						g_debug ("%s: command list empty due to invalid annotation protocol",
						         __func__);
						return NULL;
					default:
						/* FIXME better handling here */
						g_assert_not_reached ();
					}
					g_string_append (cmd, attrib_str);
					g_free (attrib_str);

					/* add attribute value to command string */
					g_string_append (cmd, " ");
					switch (ma->type[acc]) {
					case CAMEL_IMAPX_METADATA_ATTRIB_TYPE_NIL:
						/* identical for both protocol versions */
						g_string_append (cmd, "NIL");
						break;
					case CAMEL_IMAPX_METADATA_ATTRIB_TYPE_UTF8:
					case CAMEL_IMAPX_METADATA_ATTRIB_TYPE_BINARY:
						/* TODO correct handling of guchar
						 *      In theory, we're able to read true binary data into
						 *      a CamelImapxMetaAttrib from the server, but we aren't
						 *      able to write it back just that way. At least, we *could*
						 *      write strings containing NUL bytes...
						 * FIXME: We read guchar but write gchar. That needs fixing.
						 */
						/* for ANNOTATEMORE as well as METADATA, values need to
						 * be enclosed in "
						 */
						g_assert (ma->data[acc]->data != NULL);
						g_string_append (cmd, "\"");
						g_string_append_len (cmd,
						                     (gchar *) ma->data[acc]->data,
						                     (gssize) ma->data[acc]->len);
						g_string_append (cmd, "\"");
						break;
					default:
						/* FIXME better handling here */
						g_assert_not_reached ();
					}
				}
			}

			if (have_entries) {
				g_string_append (cmd, ")");
				/* insert cmd->str into GSList */
				commands = g_slist_prepend (commands, cmd->str);
				g_string_free (cmd, FALSE); /* get rid of GSList wrapper, leave data alone */
			} else {
				/* no command issued if no entries/attributes present in annotation */
				g_string_free (cmd, TRUE);
			}
		}
	}

	/* (release md lock) */
	g_mutex_unlock (md->md_lock);
	return commands;
}

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

CamelImapxMetadataSpec*
camel_imapx_metadata_spec_new (camel_imapx_metadata_proto_t proto,
                               const gchar *mailbox_name,
                               const gchar *entry_name,
                               const gchar *attrib_name,
                               GError **err)
{
	CamelImapxMetadataSpec *spec = NULL;

	g_assert ((proto > CAMEL_IMAPX_METADATA_PROTO_INVAL) &&
	          (proto < CAMEL_IMAPX_METADATA_LAST_PROTO));
	/* mailbox_name may be NULL when starting search at annotation level */
	g_assert (entry_name != NULL);
	/* attrib_name may be NULL, will then be replaced by "value" */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	/* scan for invalid name tokens */
	switch (proto) {
	case CAMEL_IMAPX_METADATA_PROTO_ANNOTATEMORE: {
		gboolean parse_err = FALSE;
		/* TODO parse for unwanted tokens */
		if (parse_err) {
			g_set_error (err,
			             KOLAB_CAMEL_ERROR,
			             KOLAB_CAMEL_ERROR_GENERIC,
			             _("Invalid token in meta data string"));
			return NULL;
		}
		break;
	}
	case CAMEL_IMAPX_METADATA_PROTO_METADATA: {
		gboolean parse_err = FALSE;
		/* parse for unwanted tokens */
		/* METADATA has no notion of attribute sets.
		 * The only attribute is 'value' and thus is
		 * not specifically referenced. We'll take ownership
		 * here of the string nonetheless to be consistent
		 */
		/* TODO improve (encodings, ...) */
		/* FIXME untested */
		if (mailbox_name != NULL) {
			parse_err = parse_err || (g_strrstr (mailbox_name, "%") != NULL);
			parse_err = parse_err || (g_strrstr (mailbox_name, "*") != NULL);
		}
		if (entry_name != NULL) {
			parse_err = parse_err || (g_strrstr (entry_name,   "%") != NULL);
			parse_err = parse_err || (g_strrstr (entry_name,   "*") != NULL);
		}
		/* attrib_name ignored */
		if (parse_err) {
			g_set_error (err,
			             KOLAB_CAMEL_ERROR,
			             KOLAB_CAMEL_ERROR_GENERIC,
			             _("Invalid token in meta data string"));
			return NULL;
		}
		break;
	}
	case CAMEL_IMAPX_METADATA_PROTO_INVAL:
		break;
	default:
		/* can't happen... */
		g_error ("%s: have unknown metadata protocol type %i",
		         __func__, proto);
	}

	spec = g_new0 (CamelImapxMetadataSpec, 1);
	spec->proto = proto;

	if (mailbox_name != NULL)
		spec->mailbox_name = g_strdup (mailbox_name);
	else
		spec->mailbox_name = NULL;

	spec->entry_name = g_strdup (entry_name);

	if (attrib_name != NULL)
		spec->attrib_name = g_strdup (attrib_name);
	else
		spec->attrib_name = g_strdup ("value");

	return spec;
}

void
camel_imapx_metadata_spec_free (CamelImapxMetadataSpec *spec)
{
	if (spec == NULL)
		return;

	if (spec->mailbox_name)
		g_free (spec->mailbox_name);
	if (spec->entry_name)
		g_free (spec->entry_name);
	if (spec->attrib_name)
		g_free (spec->attrib_name);

	g_free (spec);
}

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

CamelImapxMetadataAttrib*
camel_imapx_metadata_get_attrib_from_entry (CamelImapxMetadataEntry *me,
                                            CamelImapxMetadataSpec *spec)
{
	CamelImapxMetadataAttrib *ma = NULL;
	gchar *attrib_name = NULL;

	if ((me == NULL) || (spec == NULL))
		return NULL;

	g_assert (me->attributes != NULL);

	/* spec->attrib_name can be NULL, subst "value" then */
	if (spec->attrib_name == NULL)
		attrib_name = g_strdup ("value");
	else
		attrib_name = spec->attrib_name;

	ma = g_hash_table_lookup (me->attributes, attrib_name);

	if (spec->attrib_name == NULL)
		g_free (attrib_name);

	return ma;
}

CamelImapxMetadataAttrib*
camel_imapx_metadata_get_attrib_from_annotation (CamelImapxMetadataAnnotation *man,
                                                 CamelImapxMetadataSpec *spec)
{
	CamelImapxMetadataEntry *me = NULL;
	CamelImapxMetadataAttrib *ma = NULL;

	if ((man == NULL) || (spec == NULL))
		return NULL;

	g_assert (man->entries != NULL);
	g_assert (spec->entry_name != NULL);

	me = g_hash_table_lookup (man->entries, spec->entry_name);
	if (me == NULL)
		return NULL;

	ma = camel_imapx_metadata_get_attrib_from_entry (me, spec);
	return ma;
}

CamelImapxMetadataAttrib*
camel_imapx_metadata_get_attrib_from_metadata (CamelImapxMetadata *md,
                                               CamelImapxMetadataSpec *spec)
{
	CamelImapxMetadataAnnotation *man = NULL;
	CamelImapxMetadataAttrib *ma = NULL;

	if ((md == NULL) || (spec == NULL))
		return NULL;

	g_assert (md->mboxes != NULL);
	g_assert (spec->mailbox_name != NULL);

	man = g_hash_table_lookup (md->mboxes, spec->mailbox_name);
	if (man == NULL)
		return NULL;

	ma = camel_imapx_metadata_get_attrib_from_annotation (man, spec);
	return ma;
}

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