/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/* Copyright (C) 2001-2004 Novell, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * 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
 * 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., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/* e2k-restriction.c: message restrictions (WHERE clauses / Rule conditions) */

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

#include "e2k-restriction.h"
#include "e2k-properties.h"
#include "e2k-rule.h"

#include <stdarg.h>
#include <string.h>

static E2kRestriction *
conjoin (E2kRestrictionType type, int nrns, E2kRestriction **rns, gboolean unref)
{
	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
	int i;

	ret->type = type;
	ret->res.and.nrns = nrns;
	ret->res.and.rns = g_new (E2kRestriction *, nrns);
	for (i = 0; i < nrns; i++) {
		ret->res.and.rns[i] = rns[i];
		if (!unref)
			e2k_restriction_ref (rns[i]);
	}

	return ret;
}

/**
 * e2k_restriction_and:
 * @nrns: length of @rns
 * @rns: an array of #E2kRestriction
 * @unref: whether or not to unref the restrictions when it is done
 *
 * Creates a new restriction which is true if all of the restrictions
 * in @rns are true.
 *
 * If @unref is %TRUE, then e2k_restriction_and() is essentially
 * stealing the caller's references on the restrictions. If it is
 * %FALSE, then e2k_restriction_and() will acquire its own references
 * to each of the restrictions.
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_and (int nrns, E2kRestriction **rns, gboolean unref)
{
	return conjoin (E2K_RESTRICTION_AND, nrns, rns, unref);
}

/**
 * e2k_restriction_or:
 * @nrns: length of @rns
 * @rns: an array of #E2kRestriction
 * @unref: see e2k_restriction_and()
 *
 * Creates a new restriction which is true if any of the restrictions
 * in @rns are true.
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_or (int nrns, E2kRestriction **rns, gboolean unref)
{
	return conjoin (E2K_RESTRICTION_OR, nrns, rns, unref);
}

static E2kRestriction *
conjoinv (E2kRestrictionType type, E2kRestriction *rn, va_list ap)
{
	E2kRestriction *ret = g_new0 (E2kRestriction, 1);
	GPtrArray *rns;

	rns = g_ptr_array_new ();
	while (rn) {
		g_ptr_array_add (rns, rn);
		rn = va_arg (ap, E2kRestriction *);
	}
	va_end (ap);

	ret->type = type;
	ret->res.and.nrns = rns->len;
	ret->res.and.rns = (E2kRestriction **)rns->pdata;
	g_ptr_array_free (rns, FALSE);

	return ret;
}

/**
 * e2k_restriction_andv:
 * @rn: an #E2kRestriction
 * @...: a %NULL-terminated list of additional #E2kRestrictions
 *
 * Creates a new restriction which is true if all of the passed-in
 * restrictions are true. e2k_restriction_andv() steals the caller's
 * reference on each of the passed-in restrictions.
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_andv (E2kRestriction *rn, ...)
{
	va_list ap;

	va_start (ap, rn);
	return conjoinv (E2K_RESTRICTION_AND, rn, ap);
}

/**
 * e2k_restriction_orv:
 * @rn: an #E2kRestriction
 * @...: a %NULL-terminated list of additional #E2kRestrictions
 *
 * Creates a new restriction which is true if any of the passed-in
 * restrictions are true. e2k_restriction_orv() steals the caller's
 * reference on each of the passed-in restrictions.
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_orv (E2kRestriction *rn, ...)
{
	va_list ap;

	va_start (ap, rn);
	return conjoinv (E2K_RESTRICTION_OR, rn, ap);
}

/**
 * e2k_restriction_not:
 * @rn: an #E2kRestriction
 * @unref: see e2k_restriction_and()
 *
 * Creates a new restriction which is true if @rn is false.
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_not (E2kRestriction *rn, gboolean unref)
{
	E2kRestriction *ret = g_new0 (E2kRestriction, 1);

	ret->type = E2K_RESTRICTION_NOT;
	ret->res.not.rn = rn;
	if (!unref)
		e2k_restriction_ref (rn);

	return ret;
}

/**
 * e2k_restriction_content:
 * @propname: text property to compare against
 * @fuzzy_level: how to compare
 * @value: value to compare against
 *
 * Creates a new restriction that is true for objects where the
 * indicated property's value matches @value according to @fuzzy_level.
 *
 * For a WebDAV SEARCH, @fuzzy_level should be %E2K_FL_FULLSTRING,
 * %E2K_FL_SUBSTRING, %E2K_FL_PREFIX, or %E2K_FL_SUFFIX.
 *
 * For a MAPI restriction, @fuzzy_level may not be %E2K_FL_SUFFIX, but
 * may be ORed with any of the additional values %E2K_FL_IGNORECASE,
 * %E2K_FL_IGNORENONSPACE, or %E2K_FL_LOOSE.
 *
 * To compare a property's sort order to another string, use
 * e2k_restriction_prop_string().
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_content (const char *propname,
			 E2kRestrictionFuzzyLevel fuzzy_level,
			 const char *value)
{
	E2kRestriction *ret = g_new0 (E2kRestriction, 1);

	ret->type = E2K_RESTRICTION_CONTENT;
	ret->res.content.fuzzy_level = fuzzy_level;
	e2k_rule_prop_set (&ret->res.content.pv.prop, propname);
	ret->res.content.pv.type = E2K_PROP_TYPE_STRING;
	ret->res.content.pv.value = g_strdup (value);

	return ret;
}

/**
 * e2k_restriction_prop_bool:
 * @propname: boolean property to compare against
 * @relop: %E2K_RELOP_EQ or %E2K_RELOP_NE
 * @value: %TRUE or %FALSE
 *
 * Creates a new restriction that is true for objects where the
 * indicated property matches @relop and @value.
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_prop_bool (const char *propname, E2kRestrictionRelop relop,
			   gboolean value)
{
	E2kRestriction *ret = g_new0 (E2kRestriction, 1);

	ret->type = E2K_RESTRICTION_PROPERTY;
	ret->res.property.relop = relop;
	e2k_rule_prop_set (&ret->res.property.pv.prop, propname);
	ret->res.property.pv.type = E2K_PROP_TYPE_BOOL;
	ret->res.property.pv.value = GUINT_TO_POINTER (value);

	return ret;
}

/**
 * e2k_restriction_prop_int:
 * @propname: integer property to compare against
 * @relop: an #E2kRestrictionRelop
 * @value: number to compare against
 *
 * Creates a new restriction that is true for objects where the
 * indicated property matches @value according to @relop.
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_prop_int (const char *propname, E2kRestrictionRelop relop,
			  int value)
{
	E2kRestriction *ret = g_new0 (E2kRestriction, 1);

	ret->type = E2K_RESTRICTION_PROPERTY;
	ret->res.property.relop = relop;
	e2k_rule_prop_set (&ret->res.property.pv.prop, propname);
	ret->res.property.pv.type = E2K_PROP_TYPE_INT;
	ret->res.property.pv.value = GINT_TO_POINTER (value);

	return ret;
}

/**
 * e2k_restriction_prop_date:
 * @propname: date/time property to compare against
 * @relop: an #E2kRestrictionRelop
 * @value: date/time to compare against (as returned by e2k_make_timestamp())
 *
 * Creates a new restriction that is true for objects where the
 * indicated property matches @value according to @relop.
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_prop_date (const char *propname, E2kRestrictionRelop relop,
			   const char *value)
{
	E2kRestriction *ret = g_new0 (E2kRestriction, 1);

	ret->type = E2K_RESTRICTION_PROPERTY;
	ret->res.property.relop = relop;
	e2k_rule_prop_set (&ret->res.property.pv.prop, propname);
	ret->res.property.pv.type = E2K_PROP_TYPE_DATE;
	ret->res.property.pv.value = g_strdup (value);

	return ret;
}

/**
 * e2k_restriction_prop_string:
 * @propname: text property to compare against
 * @relop: an #E2kRestrictionRelop
 * @value: text to compare against
 *
 * Creates a new restriction that is true for objects where the
 * indicated property matches @value according to @relop.
 *
 * To do a substring match, use e2k_restriction_content().
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_prop_string (const char *propname, E2kRestrictionRelop relop,
			     const char *value)
{
	E2kRestriction *ret = g_new0 (E2kRestriction, 1);

	ret->type = E2K_RESTRICTION_PROPERTY;
	ret->res.property.relop = relop;
	e2k_rule_prop_set (&ret->res.property.pv.prop, propname);
	ret->res.property.pv.type = E2K_PROP_TYPE_STRING;
	ret->res.property.pv.value = g_strdup (value);

	return ret;
}

/**
 * e2k_restriction_prop_binary:
 * @propname: binary property to compare against
 * @relop: %E2K_RELOP_EQ or %E2K_RELOP_NE
 * @data: data to compare against
 * @len: length of @data
 *
 * Creates a new restriction that is true for objects where the
 * indicated property matches @value according to @relop.
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_prop_binary (const char *propname, E2kRestrictionRelop relop,
			     gconstpointer data, int len)
{
	E2kRestriction *ret = g_new0 (E2kRestriction, 1);

	ret->type = E2K_RESTRICTION_PROPERTY;
	ret->res.property.relop = relop;
	e2k_rule_prop_set (&ret->res.property.pv.prop, propname);
	ret->res.property.pv.type = E2K_PROP_TYPE_BINARY;
	ret->res.property.pv.value = g_byte_array_new ();
	g_byte_array_append (ret->res.property.pv.value, data, len);

	return ret;
}

/**
 * e2k_restriction_compare:
 * @propname1: first property
 * @relop: an #E2kRestrictionRelop
 * @propname2: second property
 *
 * Creates a new restriction which is true for objects where
 * @propname1 and @propname2 have the relationship described by
 * @relop.
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_compare (const char *propname1, E2kRestrictionRelop relop,
			 const char *propname2)
{
	E2kRestriction *ret = g_new0 (E2kRestriction, 1);

	ret->type = E2K_RESTRICTION_COMPAREPROPS;
	ret->res.compare.relop = relop;
	e2k_rule_prop_set (&ret->res.compare.prop1, propname1);
	e2k_rule_prop_set (&ret->res.compare.prop2, propname2);

	return ret;
}

/**
 * e2k_restriction_bitmask:
 * @propname: integer property to compare
 * @bitop: an #E2kRestrictionBitop
 * @mask: mask of bits to compare against
 *
 * Creates a new restriction that is true for objects where the
 * indicated bits of the value of @propname either are or aren't zero,
 * as indicated by @bitop.
 *
 * This cannot be used for WebDAV SEARCH restrictions.
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_bitmask (const char *propname, E2kRestrictionBitop bitop,
			 guint32 mask)
{
	E2kRestriction *ret = g_new0 (E2kRestriction, 1);

	ret->type = E2K_RESTRICTION_BITMASK;
	ret->res.bitmask.bitop = bitop;
	e2k_rule_prop_set (&ret->res.bitmask.prop, propname);
	ret->res.bitmask.mask = mask;

	return ret;
}

/**
 * e2k_restriction_size:
 * @propname: property to compare
 * @relop: an #E2kRestrictionRelop
 * @size: the size to compare @propname to
 *
 * Creates a new restriction which is true for objects where the size
 * of the value of @propname matches @size according to @relop.
 *
 * This cannot be used for WebDAV SEARCH restrictions.
 *
 * You probably do not want to use this. The standard idiom for
 * checking the size of a message is to use e2k_restriction_prop_int()
 * on its %PR_MESSAGE_SIZE property, not to use e2k_restriction_size()
 * on its %PR_BODY.
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_size (const char *propname, E2kRestrictionRelop relop,
		      guint32 size)
{
	E2kRestriction *ret = g_new0 (E2kRestriction, 1);

	ret->type = E2K_RESTRICTION_SIZE;
	ret->res.size.relop = relop;
	e2k_rule_prop_set (&ret->res.size.prop, propname);
	ret->res.size.size = size;

	return ret;
}

/**
 * e2k_restriction_exist:
 * @propname: property to check
 *
 * Creates a new restriction which is true for objects that have
 * a @propname property.
 *
 * This cannot be used for WebDAV SEARCH restrictions.
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_exist (const char *propname)
{
	E2kRestriction *ret = g_new0 (E2kRestriction, 1);

	ret->type = E2K_RESTRICTION_EXIST;
	e2k_rule_prop_set (&ret->res.exist.prop, propname);

	return ret;
}

/**
 * e2k_restriction_sub:
 * @subtable: the WebDAV name of a MAPI property of type PT_OBJECT
 * @rn: the restriction to apply against the values of @subtable
 * @unref: see e2k_restriction_and()
 *
 * Creates a new restriction that is true for objects where @rn is
 * true when applied to the value of @subtable on that object.
 *
 * @subtable is generally %PR_MESSAGE_RECIPIENTS (for finding messages
 * whose recipients match a given restriction) or
 * %PR_MESSAGE_ATTACHMENTS (for finding messages whose attachments
 * match a given restriction).
 *
 * This cannot be used for WebDAV SEARCH restrictions.
 *
 * Return value: the new restriction
 **/
E2kRestriction *
e2k_restriction_sub (const char *subtable, E2kRestriction *rn, gboolean unref)
{
	E2kRestriction *ret = g_new0 (E2kRestriction, 1);

	ret->type = E2K_RESTRICTION_SUBRESTRICTION;
	e2k_rule_prop_set (&ret->res.sub.subtable, subtable);
	ret->res.sub.rn = rn;
	if (!unref)
		e2k_restriction_ref (rn);

	return ret;
}

/**
 * e2k_restriction_unref:
 * @rn: a restriction
 *
 * Unrefs @rn. If there are no more references to @rn, it is freed.
 **/
void
e2k_restriction_unref (E2kRestriction *rn)
{
	int i;

	if (rn->ref_count--)
		return;

	switch (rn->type) {
	case E2K_RESTRICTION_AND:
	case E2K_RESTRICTION_OR:
		for (i = 0; i < rn->res.and.nrns; i++)
			e2k_restriction_unref (rn->res.and.rns[i]);
		g_free (rn->res.and.rns);
		break;

	case E2K_RESTRICTION_NOT:
		e2k_restriction_unref (rn->res.not.rn);
		break;

	case E2K_RESTRICTION_CONTENT:
		e2k_rule_free_propvalue (&rn->res.content.pv);
		break;

	case E2K_RESTRICTION_PROPERTY:
		e2k_rule_free_propvalue (&rn->res.property.pv);
		break;

	default:
		break;
	}

	g_free (rn);
}

/**
 * e2k_restriction_ref:
 * @rn: a restriction
 *
 * Refs @rn.
 **/
void
e2k_restriction_ref (E2kRestriction *rn)
{
	rn->ref_count++;
}


/* SQL export */

static gboolean rn_to_sql (E2kRestriction *rn, GString *sql, E2kRestrictionType inside);

static const char *sql_relops[] = { "<", "<=", ">", ">=", "=", "!=" };
static const int n_sql_relops = G_N_ELEMENTS (sql_relops);

static gboolean
rns_to_sql (E2kRestrictionType type, E2kRestriction **rns, int nrns, GString *sql)
{
	int i;
	gboolean need_op = FALSE;
	gboolean rv = FALSE;

	for (i = 0; i < nrns; i++) {
		if (need_op) {
			g_string_append (sql, type == E2K_RESTRICTION_AND ?
					 " AND " : " OR ");
			need_op = FALSE;
		}
		if (rn_to_sql (rns[i], sql, type)) {
			need_op = TRUE;
			rv = TRUE;
		}
	}
	return rv;
}

static void
append_sql_quoted (GString *sql, const char *string)
{
	while (*string) {
		if (*string == '\'')
			g_string_append (sql, "''");
		else
			g_string_append_c (sql, *string);
		string++;
	}
}

static gboolean
rn_to_sql (E2kRestriction *rn, GString *sql, E2kRestrictionType inside)
{
	E2kPropValue *pv;

	switch (rn->type) {
	case E2K_RESTRICTION_AND:
	case E2K_RESTRICTION_OR: {
		GString *subsql = g_string_new ("");
		gboolean rv;
		if ((rv = rns_to_sql (rn->type, rn->res.and.rns, rn->res.and.nrns, subsql))) {
			if (rn->type != inside)
				g_string_append (sql, "(");
			g_string_append (sql, subsql->str);
			if (rn->type != inside)
				g_string_append (sql, ")");
		}
		g_string_free (subsql, TRUE);

		return rv;
	}

	case E2K_RESTRICTION_NOT: {
		GString *subsql = g_string_new ("");
		gboolean rv;
		if ((rv = rn_to_sql (rn->res.not.rn, subsql, rn->type))) {
			g_string_append (sql, "NOT (");
			g_string_append (sql, subsql->str);
			g_string_append (sql, ")");
		}
		g_string_free (subsql, TRUE);

		return rv;
	}

	case E2K_RESTRICTION_CONTENT:
		pv = &rn->res.content.pv;
		g_string_append_printf (sql, "\"%s\" ", pv->prop.name);

		switch (E2K_FL_MATCH_TYPE (rn->res.content.fuzzy_level)) {
		case E2K_FL_SUBSTRING:
			g_string_append (sql, "LIKE '%");
			append_sql_quoted (sql, pv->value);
			g_string_append (sql, "%'");
			break;

		case E2K_FL_PREFIX:
			g_string_append (sql, "LIKE '");
			append_sql_quoted (sql, pv->value);
			g_string_append (sql, "%'");
			break;

		case E2K_FL_SUFFIX:
			g_string_append (sql, "LIKE '%");
			append_sql_quoted (sql, pv->value);
			g_string_append_c (sql, '\'');
			break;

		case E2K_FL_FULLSTRING:
		default:
			g_string_append (sql, "= '");
			append_sql_quoted (sql, pv->value);
			g_string_append_c (sql, '\'');
			break;
		}
		return TRUE;

	case E2K_RESTRICTION_PROPERTY:
		if (rn->res.property.relop >= n_sql_relops)
			return FALSE;

		pv = &rn->res.property.pv;
		g_string_append_printf (sql, "\"%s\" %s ", pv->prop.name,
					sql_relops[rn->res.property.relop]);

		switch (pv->type) {
		case E2K_PROP_TYPE_INT:
			g_string_append_printf (sql, "%d",
						GPOINTER_TO_UINT (pv->value));
			break;

		case E2K_PROP_TYPE_BOOL:
			g_string_append (sql, pv->value ? "True" : "False");
			break;

		case E2K_PROP_TYPE_DATE:
			g_string_append_printf (sql,
						"cast (\"%s\" as 'dateTime.tz')",
						(char *)pv->value);
			break;

		default:
			g_string_append_c (sql, '\'');
			append_sql_quoted (sql, pv->value);
			g_string_append_c (sql, '\'');
			break;
		}
		return TRUE;

	case E2K_RESTRICTION_COMPAREPROPS:
		if (rn->res.compare.relop >= n_sql_relops)
			return FALSE;

		g_string_append_printf (sql, "\"%s\" %s \"%s\"",
					rn->res.compare.prop1.name,
					sql_relops[rn->res.compare.relop],
					rn->res.compare.prop2.name);
		return TRUE;

	case E2K_RESTRICTION_COMMENT:
		return TRUE;

	case E2K_RESTRICTION_BITMASK:
	case E2K_RESTRICTION_EXIST:
	case E2K_RESTRICTION_SIZE:
	case E2K_RESTRICTION_SUBRESTRICTION:
	default:
		return FALSE;

	}
}

/**
 * e2k_restriction_to_sql:
 * @rn: a restriction
 *
 * Converts @rn to an SQL WHERE clause to be used with the WebDAV
 * SEARCH method. Note that certain restriction types cannot be used
 * in SQL, as mentioned in their descriptions above.
 *
 * If the restriction matches all objects, the return value will
 * be the empty string. Otherwise it will start with "WHERE ".
 *
 * Return value: the SQL WHERE clause, which the caller must free,
 * or %NULL if @rn could not be converted to SQL.
 **/
char *
e2k_restriction_to_sql (E2kRestriction *rn)
{
	GString *sql;
	char *ret;

	sql = g_string_new (NULL);
	if (!rn_to_sql (rn, sql, E2K_RESTRICTION_AND)) {
		g_string_free (sql, TRUE);
		return NULL;
	}

	if (sql->len)
		g_string_prepend (sql, "WHERE ");

	ret = sql->str;
	g_string_free (sql, FALSE);
	return ret;
}


/* Binary import/export */

static gboolean
extract_restriction (guint8 **data, int *len, E2kRestriction **rn)
{
	int type;

	if (*len == 0)
		return FALSE;
	type = (*data)[0];
	(*data)++;
	(*len)--;

	switch (type) {
	case E2K_RESTRICTION_AND:
	case E2K_RESTRICTION_OR:
	{
		E2kRestriction **rns;
		guint16 nrns;
		int i;

		if (!e2k_rule_extract_uint16 (data, len, &nrns))
			return FALSE;
		rns = g_new0 (E2kRestriction *, nrns);
		for (i = 0; i < nrns; i++) {
			if (!extract_restriction (data, len, &rns[i])) {
				while (i--)
					e2k_restriction_unref (rns[i]);
				g_free (rns);
				return FALSE;
			}
		}

		*rn = conjoin (type, nrns, rns, TRUE);
		return TRUE;
	}

	case E2K_RESTRICTION_NOT:
	{
		E2kRestriction *subrn;

		if (!extract_restriction (data, len, &subrn))
			return FALSE;
		*rn = e2k_restriction_not (subrn, TRUE);
		return TRUE;
	}

	case E2K_RESTRICTION_CONTENT:
	{
		guint32 fuzzy_level;
		E2kRuleProp prop;
		E2kPropValue pv;

		if (!e2k_rule_extract_uint32 (data, len, &fuzzy_level) ||
		    !e2k_rule_extract_proptag (data, len, &prop) ||
		    !e2k_rule_extract_propvalue (data, len, &pv))
			return FALSE;

		pv.prop = prop;

		*rn = g_new0 (E2kRestriction, 1);
		(*rn)->type = type;
		(*rn)->res.content.fuzzy_level = fuzzy_level;
		(*rn)->res.content.pv = pv;
		return TRUE;
	}

	case E2K_RESTRICTION_PROPERTY:
	{
		guint8 relop;
		E2kRuleProp prop;
		E2kPropValue pv;

		if (!e2k_rule_extract_byte (data, len, &relop) ||
		    !e2k_rule_extract_proptag (data, len, &prop) ||
		    !e2k_rule_extract_propvalue (data, len, &pv))
			return FALSE;

		pv.prop = prop;

		*rn = g_new0 (E2kRestriction, 1);
		(*rn)->type = type;
		(*rn)->res.property.relop = relop;
		(*rn)->res.property.pv = pv;
		return TRUE;
	}

	case E2K_RESTRICTION_COMPAREPROPS:
	{
		/* FIXME */
		return FALSE;
	}

	case E2K_RESTRICTION_BITMASK:
	{
		guint8 bitop;
		guint32 mask;
		E2kRuleProp prop;

		if (!e2k_rule_extract_byte (data, len, &bitop) ||
		    !e2k_rule_extract_proptag (data, len, &prop) ||
		    !e2k_rule_extract_uint32 (data, len, &mask))
			return FALSE;

		*rn = g_new0 (E2kRestriction, 1);
		(*rn)->type = type;
		(*rn)->res.bitmask.bitop = bitop;
		(*rn)->res.bitmask.prop = prop;
		(*rn)->res.bitmask.mask = mask;
		return TRUE;
	}

	case E2K_RESTRICTION_SIZE:
	{
		/* FIXME */
		return FALSE;
	}

	case E2K_RESTRICTION_EXIST:
	{
		E2kRuleProp prop;

		if (!e2k_rule_extract_proptag (data, len, &prop))
			return FALSE;

		*rn = g_new0 (E2kRestriction, 1);
		(*rn)->type = type;
		(*rn)->res.exist.prop = prop;
		return TRUE;
	}

	case E2K_RESTRICTION_SUBRESTRICTION:
	{
		E2kRuleProp subtable;
		E2kRestriction *subrn;

		if (!e2k_rule_extract_proptag (data, len, &subtable) ||
		    !extract_restriction (data, len, &subrn))
			return FALSE;

		*rn = g_new0 (E2kRestriction, 1);
		(*rn)->type = type;
		(*rn)->res.sub.subtable = subtable;
		(*rn)->res.sub.rn = subrn;
		return TRUE;
	}

	case E2K_RESTRICTION_COMMENT:
	{
		guint8 nprops, dummy;
		E2kPropValue *props;
		int i;

		if (!e2k_rule_extract_byte (data, len, &nprops))
			return FALSE;
		
		props = g_new0 (E2kPropValue, nprops);
		for (i = 0; i < nprops; i++) {
			if (!e2k_rule_extract_propvalue (data, len, &props[i])) {
				while (i--)
					e2k_rule_free_propvalue (&props[i]);
				g_free (props);
				return FALSE;
			}
		}

		*rn = g_new0 (E2kRestriction, 1);
		(*rn)->type = type;
		(*rn)->res.comment.nprops = nprops;
		(*rn)->res.comment.props = props;

		/* FIXME: There is always a "1" byte here, but I don't
		 * know why.
		 */
		if (!e2k_rule_extract_byte (data, len, &dummy) || dummy != 1) {
			e2k_restriction_unref (*rn);
			return FALSE;
		}

		if (!extract_restriction (data, len, &(*rn)->res.comment.rn)) {
			e2k_restriction_unref (*rn);
			return FALSE;
		}

		return TRUE;
	}

	default:
		return FALSE;
	}
}

/**
 * e2k_restriction_extract:
 * @data: pointer to data pointer
 * @len: pointer to data length
 * @rn: pointer to variable to store the extracted restriction in
 *
 * Attempts to extract a restriction from *@data, which contains
 * a binary-encoded restriction from a server-side rule.
 *
 * On success, *@rn will contain the extracted restriction, *@data
 * will be advanced past the end of the restriction data, and *@len
 * will be decremented accordingly.
 *
 * Return value: success or failure
 **/
gboolean
e2k_restriction_extract (guint8 **data, int *len, E2kRestriction **rn)
{
	guint32 rnlen;

	if (!e2k_rule_extract_uint32 (data, len, &rnlen))
		return FALSE;
	if (rnlen > *len)
		return FALSE;

	if (rnlen == 1 && (*data)[0] == 0xFF) {
		(*data)++;
		(*len)--;
		*rn = NULL;
		return TRUE;
	}

	if (*len < 2)
		return FALSE;
	if ((*data)[0] != 0 || (*data)[1] != 0)
		return FALSE;
	(*data) += 2;
	(*len) -= 2;

	return extract_restriction (data, len, rn);
}

static void
append_restriction (GByteArray *ba, E2kRestriction *rn)
{
	int i;

	e2k_rule_append_byte (ba, rn->type);

	switch (rn->type) {
	case E2K_RESTRICTION_AND:
	case E2K_RESTRICTION_OR:
		e2k_rule_append_uint16 (ba, rn->res.and.nrns);
		for (i = 0; i < rn->res.and.nrns; i++)
			append_restriction (ba, rn->res.and.rns[i]);
		break;

	case E2K_RESTRICTION_NOT:
		append_restriction (ba, rn->res.not.rn);
		break;

	case E2K_RESTRICTION_CONTENT:
		e2k_rule_append_uint32 (ba, rn->res.content.fuzzy_level);
		e2k_rule_append_proptag (ba, &rn->res.content.pv.prop);
		e2k_rule_append_propvalue (ba, &rn->res.content.pv);
		break;

	case E2K_RESTRICTION_PROPERTY:
		e2k_rule_append_byte (ba, rn->res.property.relop);
		e2k_rule_append_proptag (ba, &rn->res.property.pv.prop);
		e2k_rule_append_propvalue (ba, &rn->res.property.pv);
		break;

	case E2K_RESTRICTION_COMPAREPROPS:
		/* FIXME */
		break;

	case E2K_RESTRICTION_BITMASK:
		e2k_rule_append_byte (ba, rn->res.bitmask.bitop);
		e2k_rule_append_proptag (ba, &rn->res.bitmask.prop);
		e2k_rule_append_uint32 (ba, rn->res.bitmask.mask);
		break;

	case E2K_RESTRICTION_SIZE:
		break;

	case E2K_RESTRICTION_EXIST:
		e2k_rule_append_proptag (ba, &rn->res.exist.prop);
		break;

	case E2K_RESTRICTION_SUBRESTRICTION:
		e2k_rule_append_proptag (ba, &rn->res.sub.subtable);
		append_restriction (ba, rn->res.sub.rn);
		break;

	case E2K_RESTRICTION_COMMENT:
		e2k_rule_append_byte (ba, rn->res.comment.nprops);

		for (i = 0; i < rn->res.comment.nprops; i++)
			e2k_rule_append_propvalue (ba, &rn->res.comment.props[i]);

		/* FIXME: There is always a "1" byte here, but I don't
		 * know why.
		 */
		e2k_rule_append_byte (ba, 1);

		append_restriction (ba, rn->res.comment.rn);
		break;

	default:
		break;
	}
}

/**
 * e2k_restriction_append:
 * @ba: a buffer into which a server-side rule is being constructed
 * @rn: the restriction to append to @ba
 *
 * Appends @rn to @ba as part of a server-side rule.
 **/
void
e2k_restriction_append (GByteArray *ba, E2kRestriction *rn)
{
	int rnlen_offset, rnlen;

	if (!rn) {
		e2k_rule_append_uint32 (ba, 1);
		e2k_rule_append_byte (ba, 0xFF);
		return;
	}

	/* Save space for the length field */
	rnlen_offset = ba->len;
	e2k_rule_append_uint32 (ba, 0);

	/* FIXME: ??? */
	e2k_rule_append_uint16 (ba, 0);

	append_restriction (ba, rn);

	rnlen = ba->len - rnlen_offset - 4;
	e2k_rule_write_uint32 (ba->data + rnlen_offset, rnlen);
}
