/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* 
 * pas-backend-summary.c
 * Copyright 2000, 2001, Ximian, Inc.
 *
 * Authors:
 *   Chris Toshok <toshok@ximian.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License, version 2, as published by the Free Software Foundation.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include "config.h"

#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

#include <netinet/in.h>

#include <gal/widgets/e-unicode.h>

#include "ebook/e-card-simple.h"
#include "pas-backend-summary.h"
#include "e-util/e-sexp.h"

static GObjectClass *parent_class;

struct _PASBackendSummaryPrivate {
	char *summary_path;
	FILE *fp;
	guint32 file_version;
	time_t mtime;
	gboolean upgraded;
	gboolean dirty;
	int flush_timeout_millis;
	int flush_timeout;
	GPtrArray *items;
	GHashTable *id_to_item;
	guint32 num_items; /* used only for loading */
#ifdef SUMMARY_STATS
	int size;
#endif
};

typedef struct {
	char *id;
	char *nickname;
	char *full_name;
	char *given_name;
	char *surname;
	char *file_as;
	char *email_1;
	char *email_2;
	char *email_3;
	gboolean wants_html;
	gboolean wants_html_set;
	gboolean list;
	gboolean list_show_addresses;
} PASBackendSummaryItem;

typedef struct {
	/* these lengths do *not* including the terminating \0, as
	   it's not stored on disk. */
	guint16 id_len;
	guint16 nickname_len;
	guint16 full_name_len; /* version 3.0 field */
	guint16 given_name_len;
	guint16 surname_len;
	guint16 file_as_len;
	guint16 email_1_len;
	guint16 email_2_len;
	guint16 email_3_len;
	guint8  wants_html;
	guint8  wants_html_set;
	guint8  list;
	guint8  list_show_addresses;
} PASBackendSummaryDiskItem;

typedef struct {
	guint32 file_version;
	guint32 num_items;
	guint32 summary_mtime; /* version 2.0 field */
} PASBackendSummaryHeader;

#define PAS_SUMMARY_MAGIC "PAS-SUMMARY"
#define PAS_SUMMARY_MAGIC_LEN 11

#define PAS_SUMMARY_FILE_VERSION_1_0 1000
#define PAS_SUMMARY_FILE_VERSION_2_0 2000
#define PAS_SUMMARY_FILE_VERSION_3_0 3000
#define PAS_SUMMARY_FILE_VERSION_4_0 4000

#define PAS_SUMMARY_FILE_VERSION PAS_SUMMARY_FILE_VERSION_4_0

static void
free_summary_item (PASBackendSummaryItem *item)
{
	g_free (item->id);
	g_free (item->nickname);
	g_free (item->full_name);
	g_free (item->given_name);
	g_free (item->surname);
	g_free (item->file_as);
	g_free (item->email_1);
	g_free (item->email_2);
	g_free (item->email_3);
	g_free (item);
}

static void
clear_items (PASBackendSummary *summary)
{
	int i;
	int num = summary->priv->items->len;
	for (i = 0; i < num; i++) {
		PASBackendSummaryItem *item = g_ptr_array_remove_index_fast (summary->priv->items, 0);
		g_hash_table_remove (summary->priv->id_to_item, item->id);
		free_summary_item (item);
	}
}

PASBackendSummary*
pas_backend_summary_new (const char *summary_path, int flush_timeout_millis)
{
	PASBackendSummary *summary = g_object_new (PAS_TYPE_BACKEND_SUMMARY, NULL);

	summary->priv->summary_path = g_strdup (summary_path);
	summary->priv->flush_timeout_millis = flush_timeout_millis;
	summary->priv->file_version = PAS_SUMMARY_FILE_VERSION_4_0;

	return summary;
}

static void
pas_backend_summary_dispose (GObject *object)
{
	PASBackendSummary *summary = PAS_BACKEND_SUMMARY (object);

	if (summary->priv->dirty)
		g_warning ("Destroying dirty summary");

	if (summary->priv->flush_timeout) {
		g_source_remove (summary->priv->flush_timeout);
		summary->priv->flush_timeout = 0;
	}

	if (summary->priv->fp)
		fclose (summary->priv->fp);

	g_free (summary->priv->summary_path);
	clear_items (summary);
	g_ptr_array_free (summary->priv->items, TRUE);

	g_hash_table_destroy (summary->priv->id_to_item);

	g_free (summary->priv);

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

static void
pas_backend_summary_class_init (PASBackendSummaryClass *klass)
{
	GObjectClass  *object_class = G_OBJECT_CLASS (klass);

	parent_class = g_type_class_ref (G_TYPE_OBJECT);

	/* Set the virtual methods. */

	object_class->dispose = pas_backend_summary_dispose;
}

static void
pas_backend_summary_init (PASBackendSummary *summary)
{
	PASBackendSummaryPrivate *priv;

	priv             = g_new(PASBackendSummaryPrivate, 1);

	summary->priv = priv;

	priv->summary_path = NULL;
	priv->fp = NULL;
	priv->dirty = FALSE;
	priv->upgraded = FALSE;
	priv->items = g_ptr_array_new();
	priv->id_to_item = g_hash_table_new (g_str_hash, g_str_equal);
	priv->flush_timeout_millis = 0;
	priv->flush_timeout = 0;
#ifdef SUMMARY_STATS
	priv->size = 0;
#endif
}

/**
 * pas_backend_summary_get_type:
 */
GType
pas_backend_summary_get_type (void)
{
	static GType type = 0;

	if (! type) {
		GTypeInfo info = {
			sizeof (PASBackendSummaryClass),
			NULL, /* base_class_init */
			NULL, /* base_class_finalize */
			(GClassInitFunc)  pas_backend_summary_class_init,
			NULL, /* class_finalize */
			NULL, /* class_data */
			sizeof (PASBackendSummary),
			0,    /* n_preallocs */
			(GInstanceInitFunc) pas_backend_summary_init
		};

		type = g_type_register_static (G_TYPE_OBJECT, "PASBackendSummary", &info, 0);
	}

	return type;
}


static gboolean
pas_backend_summary_check_magic (PASBackendSummary *summary, FILE *fp)
{
	char buf [PAS_SUMMARY_MAGIC_LEN + 1];
	int rv;

	memset (buf, 0, sizeof (buf));

	rv = fread (buf, PAS_SUMMARY_MAGIC_LEN, 1, fp);
	if (rv != 1)
		return FALSE;
	if (strcmp (buf, PAS_SUMMARY_MAGIC))
		return FALSE;

	return TRUE;
}

static gboolean
pas_backend_summary_load_header (PASBackendSummary *summary, FILE *fp,
				 PASBackendSummaryHeader *header)
{
	int rv;

	rv = fread (&header->file_version, sizeof (header->file_version), 1, fp);
	if (rv != 1)
		return FALSE;

	header->file_version = ntohl (header->file_version);

	if (header->file_version < PAS_SUMMARY_FILE_VERSION) {
		return FALSE; /* this will cause the entire summary to be rebuilt */
	}

	rv = fread (&header->num_items, sizeof (header->num_items), 1, fp);
	if (rv != 1)
		return FALSE;

	header->num_items = ntohl (header->num_items);

	rv = fread (&header->summary_mtime, sizeof (header->summary_mtime), 1, fp);
	if (rv != 1)
		return FALSE;
	header->summary_mtime = ntohl (header->summary_mtime);

	return TRUE;
}

static char *
read_string (FILE *fp, int len)
{
	char *buf;
	int rv;

	buf = g_new0 (char, len + 1);

	rv = fread (buf, len, 1, fp);
	if (rv != 1) {
		g_free (buf);
		return NULL;
	}

	return buf;
}

static gboolean
pas_backend_summary_load_item (PASBackendSummary *summary,
			       PASBackendSummaryItem **new_item)
{
	PASBackendSummaryItem *item;
	char *buf;
	FILE *fp = summary->priv->fp;

	if (summary->priv->file_version >= PAS_SUMMARY_FILE_VERSION_4_0) {
		PASBackendSummaryDiskItem disk_item;
		int rv = fread (&disk_item, sizeof (disk_item), 1, fp);
		if (rv != 1)
			return FALSE;

		disk_item.id_len = ntohs (disk_item.id_len);
		disk_item.nickname_len = ntohs (disk_item.nickname_len);
		disk_item.full_name_len = ntohs (disk_item.full_name_len);
		disk_item.given_name_len = ntohs (disk_item.given_name_len);
		disk_item.surname_len = ntohs (disk_item.surname_len);
		disk_item.file_as_len = ntohs (disk_item.file_as_len);
		disk_item.email_1_len = ntohs (disk_item.email_1_len);
		disk_item.email_2_len = ntohs (disk_item.email_2_len);
		disk_item.email_3_len = ntohs (disk_item.email_3_len);

		item = g_new0 (PASBackendSummaryItem, 1);

		item->wants_html = disk_item.wants_html;
		item->wants_html_set = disk_item.wants_html_set;
		item->list = disk_item.list;
		item->list_show_addresses = disk_item.list_show_addresses;

		if (disk_item.id_len) {
			buf = read_string (fp, disk_item.id_len);
			if (!buf) {
				free_summary_item (item);
				return FALSE;
			}
			item->id = buf;
		}

		if (disk_item.nickname_len) {
			buf = read_string (fp, disk_item.nickname_len);
			if (!buf) {
				free_summary_item (item);
				return FALSE;
			}
			item->nickname = buf;
		}

		if (disk_item.full_name_len) {
			buf = read_string (fp, disk_item.full_name_len);
			if (!buf) {
				free_summary_item (item);
				return FALSE;
			}
			item->full_name = buf;
		}

		if (disk_item.given_name_len) {
			buf = read_string (fp, disk_item.given_name_len);
			if (!buf) {
				free_summary_item (item);
				return FALSE;
			}
			item->given_name = buf;
		}

		if (disk_item.surname_len) {
			buf = read_string (fp, disk_item.surname_len);
			if (!buf) {
				free_summary_item (item);
				return FALSE;
			}
			item->surname = buf;
		}

		if (disk_item.file_as_len) {
			buf = read_string (fp, disk_item.file_as_len);
			if (!buf) {
				free_summary_item (item);
				return FALSE;
			}
			item->file_as = buf;
		}

		if (disk_item.email_1_len) {
			buf = read_string (fp, disk_item.email_1_len);
			if (!buf) {
				free_summary_item (item);
				return FALSE;
			}
			item->email_1 = buf;
		}

		if (disk_item.email_2_len) {
			buf = read_string (fp, disk_item.email_2_len);
			if (!buf) {
				free_summary_item (item);
				return FALSE;
			}
			item->email_2 = buf;
		}

		if (disk_item.email_3_len) {
			buf = read_string (fp, disk_item.email_3_len);
			if (!buf) {
				free_summary_item (item);
				return FALSE;
			}
			item->email_3 = buf;
		}

		/* the only field that has to be there is the id */
		if (!item->id) {
			free_summary_item (item);
			return FALSE;
		}
	}
	else {
		/* unhandled file version */
		return FALSE;
	}

	*new_item = item;
	return TRUE;
}

/* opens the file and loads the header */
static gboolean
pas_backend_summary_open (PASBackendSummary *summary)
{
	FILE *fp;
	PASBackendSummaryHeader header;
	struct stat sb;

	if (summary->priv->fp)
		return TRUE;

	if (stat (summary->priv->summary_path, &sb) == -1) {
		/* if there's no summary present, look for the .new
		   file and rename it if it's there, and attempt to
		   load that */
		char *new_filename = g_strconcat (summary->priv->summary_path, ".new", NULL);
		if (stat (new_filename, &sb) == -1) {
			g_warning ("no summary present");
			g_free (new_filename);
			return FALSE;
		}
		else {
			rename (new_filename, summary->priv->summary_path);
			g_free (new_filename);
		}
	}

	fp = fopen (summary->priv->summary_path, "r");
	if (!fp) {
		g_warning ("failed to open summary file");
		return FALSE;
	}

	if (!pas_backend_summary_check_magic (summary, fp)) {
		g_warning ("file is not a valid summary file");
		fclose (fp);
		return FALSE;
	}

	if (!pas_backend_summary_load_header (summary, fp, &header)) {
		g_warning ("failed to read summary header");
		fclose (fp);
		return FALSE;
	}

	summary->priv->num_items = header.num_items;
	summary->priv->file_version = header.file_version;
	summary->priv->mtime = header.summary_mtime;
	summary->priv->fp = fp;

	return TRUE;
}

gboolean
pas_backend_summary_load (PASBackendSummary *summary)
{
	PASBackendSummaryItem *new_item;
	int i;
	
	if (!pas_backend_summary_open (summary))
		return FALSE;

	for (i = 0; i < summary->priv->num_items; i ++) {
		if (!pas_backend_summary_load_item (summary, &new_item)) {
			g_warning ("error while reading summary item");
			clear_items (summary);
			fclose (summary->priv->fp);
			summary->priv->fp = NULL;
			summary->priv->dirty = FALSE;
			return FALSE;
		}

		g_ptr_array_add (summary->priv->items, new_item);
		g_hash_table_insert (summary->priv->id_to_item, new_item->id, new_item);
	}

	if (summary->priv->upgraded) {
		pas_backend_summary_save (summary);
	}
	summary->priv->dirty = FALSE;

	return TRUE;
}

static gboolean
pas_backend_summary_save_magic (FILE *fp)
{
	int rv;
	rv = fwrite (PAS_SUMMARY_MAGIC, PAS_SUMMARY_MAGIC_LEN, 1, fp);
	if (rv != 1)
		return FALSE;

	return TRUE;
}

static gboolean
pas_backend_summary_save_header (PASBackendSummary *summary, FILE *fp)
{
	PASBackendSummaryHeader header;
	int rv;

	header.file_version = htonl (PAS_SUMMARY_FILE_VERSION);
	header.num_items = htonl (summary->priv->items->len);
	header.summary_mtime = htonl (time (NULL));

	rv = fwrite (&header, sizeof (header), 1, fp);
	if (rv != 1)
		return FALSE;

	return TRUE;
}

static gboolean
save_string (const char *str, FILE *fp)
{
	int rv;

	if (!str || !*str)
		return TRUE;

	rv = fwrite (str, strlen (str), 1, fp);
	return (rv == 1);
}

static gboolean
pas_backend_summary_save_item (PASBackendSummary *summary, FILE *fp, PASBackendSummaryItem *item)
{
	PASBackendSummaryDiskItem disk_item;
	int len;
	int rv;

	len = item->id ? strlen (item->id) : 0;
	disk_item.id_len = htons (len);

	len = item->nickname ? strlen (item->nickname) : 0;
	disk_item.nickname_len = htons (len);

	len = item->given_name ? strlen (item->given_name) : 0;
	disk_item.given_name_len = htons (len);

	len = item->full_name ? strlen (item->full_name) : 0;
	disk_item.full_name_len = htons (len);

	len = item->surname ? strlen (item->surname) : 0;
	disk_item.surname_len = htons (len);

	len = item->file_as ? strlen (item->file_as) : 0;
	disk_item.file_as_len = htons (len);

	len = item->email_1 ? strlen (item->email_1) : 0;
	disk_item.email_1_len = htons (len);

	len = item->email_2 ? strlen (item->email_2) : 0;
	disk_item.email_2_len = htons (len);

	len = item->email_3 ? strlen (item->email_3) : 0;
	disk_item.email_3_len = htons (len);

	disk_item.wants_html = item->wants_html;
	disk_item.wants_html_set = item->wants_html_set;
	disk_item.list = item->list;
	disk_item.list_show_addresses = item->list_show_addresses;

	rv = fwrite (&disk_item, sizeof(disk_item), 1, fp);
	if (rv != 1)
		return FALSE;

	if (!save_string (item->id, fp))
		return FALSE;
	if (!save_string (item->nickname, fp))
		return FALSE;
	if (!save_string (item->full_name, fp))
		return FALSE;
	if (!save_string (item->given_name, fp))
		return FALSE;
	if (!save_string (item->surname, fp))
		return FALSE;
	if (!save_string (item->file_as, fp))
		return FALSE;
	if (!save_string (item->email_1, fp))
		return FALSE;
	if (!save_string (item->email_2, fp))
		return FALSE;
	if (!save_string (item->email_3, fp))
		return FALSE;

	return TRUE;
}

gboolean
pas_backend_summary_save (PASBackendSummary *summary)
{
	struct stat sb;
	FILE *fp = NULL;
	char *new_filename = NULL;
	int i;

	if (!summary->priv->dirty)
		return TRUE;

	new_filename = g_strconcat (summary->priv->summary_path, ".new", NULL);

	fp = fopen (new_filename, "w");
	if (!fp) {
		g_warning ("could not create new summary file");
		goto lose;
	}

	if (!pas_backend_summary_save_magic (fp)) {
		g_warning ("could not write magic to new summary file");
		goto lose;
	}

	if (!pas_backend_summary_save_header (summary, fp)) {
		g_warning ("could not write header to new summary file");
		goto lose;
	}

	for (i = 0; i < summary->priv->items->len; i ++) {
		PASBackendSummaryItem *item = g_ptr_array_index (summary->priv->items, i);
		if (!pas_backend_summary_save_item (summary, fp, item)) {
			g_warning ("failed to write an item to new summary file, errno = %d", errno);
			goto lose;
		}
	}

	fclose (fp);

	/* if we have a queued flush, clear it (since we just flushed) */
	if (summary->priv->flush_timeout) {
		g_source_remove (summary->priv->flush_timeout);
		summary->priv->flush_timeout = 0;
	}

	/* unlink the old summary and rename the new one */
	unlink (summary->priv->summary_path);
	rename (new_filename, summary->priv->summary_path);

	g_free (new_filename);

	/* lastly, update the in memory mtime to that of the file */
	if (stat (summary->priv->summary_path, &sb) == -1) {
		g_warning ("error stat'ing saved summary");
	}
	else {
		summary->priv->mtime = sb.st_mtime;
	}

	return TRUE;

 lose:
	if (fp)
		fclose (fp);
	if (new_filename)
		unlink (new_filename);
	g_free (new_filename);
	return FALSE;
}

void
pas_backend_summary_add_card (PASBackendSummary *summary, const char *vcard)
{
	ECard *card;
	ECardSimple *simple;
	PASBackendSummaryItem *new_item;

	card = e_card_new ((char*)vcard);
	simple = e_card_simple_new (card);

	new_item = g_new (PASBackendSummaryItem, 1);

	new_item->id         = g_strdup (e_card_simple_get_id (simple));
	new_item->nickname   = e_card_simple_get (simple, E_CARD_SIMPLE_FIELD_NICKNAME);
	new_item->full_name = e_card_simple_get (simple, E_CARD_SIMPLE_FIELD_FULL_NAME);
	new_item->given_name = e_card_simple_get (simple, E_CARD_SIMPLE_FIELD_GIVEN_NAME);
	new_item->surname    = e_card_simple_get (simple, E_CARD_SIMPLE_FIELD_FAMILY_NAME);
	new_item->file_as    = e_card_simple_get (simple, E_CARD_SIMPLE_FIELD_FILE_AS);
	new_item->email_1    = e_card_simple_get (simple, E_CARD_SIMPLE_FIELD_EMAIL);
	new_item->email_2    = e_card_simple_get (simple, E_CARD_SIMPLE_FIELD_EMAIL_2);
	new_item->email_3    = e_card_simple_get (simple, E_CARD_SIMPLE_FIELD_EMAIL_3);
	new_item->list       = e_card_evolution_list (card);
	new_item->list_show_addresses = e_card_evolution_list_show_addresses (card);
	new_item->wants_html = card->wants_html;
	new_item->wants_html_set = card->wants_html_set;

	g_ptr_array_add (summary->priv->items, new_item);
	g_hash_table_insert (summary->priv->id_to_item, new_item->id, new_item);

	g_object_unref (simple);
	g_object_unref (card);

#ifdef SUMMARY_STATS
	summary->priv->size += sizeof (PASBackendSummaryItem);
	summary->priv->size += new_item->id ? strlen (new_item->id) : 0;
	summary->priv->size += new_item->nickname ? strlen (new_item->nickname) : 0;
	summary->priv->size += new_item->full_name ? strlen (new_item->full_name) : 0;
	summary->priv->size += new_item->given_name ? strlen (new_item->given_name) : 0;
	summary->priv->size += new_item->surname ? strlen (new_item->surname) : 0;
	summary->priv->size += new_item->file_as ? strlen (new_item->file_as) : 0;
	summary->priv->size += new_item->email_1 ? strlen (new_item->email_1) : 0;
	summary->priv->size += new_item->email_2 ? strlen (new_item->email_2) : 0;
	summary->priv->size += new_item->email_3 ? strlen (new_item->email_3) : 0;
#endif
	pas_backend_summary_touch (summary);
}

void
pas_backend_summary_remove_card (PASBackendSummary *summary, const char *id)
{
	PASBackendSummaryItem *item = g_hash_table_lookup (summary->priv->id_to_item, id);

	if (item) {
		g_ptr_array_remove (summary->priv->items, item);
		g_hash_table_remove (summary->priv->id_to_item, id);
		free_summary_item (item);
		pas_backend_summary_touch (summary);
		return;
	}

	g_warning ("pas_backend_summary_remove_card: unable to locate id `%s'", id);
}

static gboolean
summary_flush_func (gpointer data)
{
	PASBackendSummary *summary = PAS_BACKEND_SUMMARY (data);

	if (!summary->priv->dirty) {
		summary->priv->flush_timeout = 0;
		return FALSE;
	}

	if (!pas_backend_summary_save (summary)) {
		/* this isn't fatal, as we can just either 1) flush
		   out with the next change, or 2) regen the summary
		   when we next load the uri */
		g_warning ("failed to flush summary file to disk");
		return TRUE; /* try again after the next timeout */
	}

	g_warning ("flushed summary to disk");

	/* we only want this to execute once, so return FALSE and set
	   summary->flush_timeout to 0 */
	summary->priv->flush_timeout = 0;
	return FALSE;
}

void
pas_backend_summary_touch (PASBackendSummary *summary)
{
	summary->priv->dirty = TRUE;
	if (!summary->priv->flush_timeout
	    && summary->priv->flush_timeout_millis)
		summary->priv->flush_timeout = g_timeout_add (summary->priv->flush_timeout_millis,
							      summary_flush_func, summary);
}

gboolean
pas_backend_summary_is_up_to_date (PASBackendSummary *summary, time_t t)
{
	if (!pas_backend_summary_open (summary))
		return FALSE;
	else
		return summary->priv->mtime >= t;
}


/* we only want to do summary queries if the query is over the set fields in the summary */

static ESExpResult *
func_check(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
{
	ESExpResult *r;
	int truth = FALSE;

	if (argc == 2
	    && argv[0]->type == ESEXP_RES_STRING
	    && argv[1]->type == ESEXP_RES_STRING) {
		char *query_name = argv[0]->value.string;

		if (!strcmp (query_name, "nickname") ||
		    !strcmp (query_name, "full_name") ||
		    !strcmp (query_name, "file_as") ||
		    !strcmp (query_name, "email")) {
			truth = TRUE;
		}
	}

	r = e_sexp_result_new(f, ESEXP_RES_BOOL);
	r->value.bool = truth;
	
	return r;
}

/* 'builtin' functions */
static struct {
	char *name;
	ESExpFunc *func;
	int type;		/* set to 1 if a function can perform shortcut evaluation, or
				   doesn't execute everything, 0 otherwise */
} check_symbols[] = {
	{ "contains", func_check, 0 },
	{ "is", func_check, 0 },
	{ "beginswith", func_check, 0 },
	{ "endswith", func_check, 0 },
};

gboolean
pas_backend_summary_is_summary_query (PASBackendSummary *summary, const char *query)
{
	ESExp *sexp;
	ESExpResult *r;
	gboolean retval;
	int i;
	int esexp_error;

	sexp = e_sexp_new();

	for(i=0;i<sizeof(check_symbols)/sizeof(check_symbols[0]);i++) {
		if (check_symbols[i].type == 1) {
			e_sexp_add_ifunction(sexp, 0, check_symbols[i].name,
					     (ESExpIFunc *)check_symbols[i].func, summary);
		} else {
			e_sexp_add_function(sexp, 0, check_symbols[i].name,
					    check_symbols[i].func, summary);
		}
	}

	e_sexp_input_text(sexp, query, strlen(query));
	esexp_error = e_sexp_parse(sexp);

	if (esexp_error == -1) {
		return FALSE;
	}

	r = e_sexp_eval(sexp);

	retval = (r && r->type == ESEXP_RES_BOOL && r->value.bool);

	e_sexp_result_free(sexp, r);

	e_sexp_unref (sexp);

	return retval;
}



/* the actual query mechanics */
static ESExpResult *
do_compare (PASBackendSummary *summary, struct _ESExp *f, int argc,
	    struct _ESExpResult **argv,
	    char *(*compare)(const char*, const char*))
{
	GPtrArray *result = g_ptr_array_new ();
	ESExpResult *r;
	int i;

	if (argc == 2
	    && argv[0]->type == ESEXP_RES_STRING
	    && argv[1]->type == ESEXP_RES_STRING) {

		for (i = 0; i < summary->priv->items->len; i ++) {
			PASBackendSummaryItem *item = g_ptr_array_index (summary->priv->items, i);
			if (!strcmp (argv[0]->value.string, "full_name")) {
				char *given = item->given_name;
				char *surname = item->surname;
				if ((given && compare (given, argv[1]->value.string))
				    || (surname && compare (surname, argv[1]->value.string)))
					g_ptr_array_add (result, item->id);
			}
			else if (!strcmp (argv[0]->value.string, "email")) {
				char *email_1 = item->email_1;
				char *email_2 = item->email_2;
				char *email_3 = item->email_3;
				if ((email_1 && compare (email_1, argv[1]->value.string))
				    || (email_2 && compare (email_2, argv[1]->value.string))
				    || (email_3 && compare (email_3, argv[1]->value.string)))
					g_ptr_array_add (result, item->id);
			}
			else if (!strcmp (argv[0]->value.string, "file_as")) {
				char *file_as = item->file_as;
				if (file_as && compare (file_as, argv[1]->value.string))
					g_ptr_array_add (result, item->id);
			}
			else if (!strcmp (argv[0]->value.string, "nickname")) {
				char *nickname = item->nickname;
				if (nickname && compare (nickname, argv[1]->value.string))
					g_ptr_array_add (result, item->id);
			}
		}
	}

	r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
	r->value.ptrarray = result;

	return r;
}

static ESExpResult *
func_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
{
	PASBackendSummary *summary = data;

	return do_compare (summary, f, argc, argv, (char *(*)(const char*, const char*)) e_utf8_strstrcase);
}

static char *
is_helper (const char *s1, const char *s2)
{
	if (!strcasecmp(s1, s2))
		return (char*)s1;
	else
		return NULL;
}

static ESExpResult *
func_is(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
{
	PASBackendSummary *summary = data;

	return do_compare (summary, f, argc, argv, is_helper);
}

static char *
endswith_helper (const char *s1, const char *s2)
{
	char *p;
	if ((p = (char*)e_utf8_strstrcase(s1, s2))
	    && (strlen(p) == strlen(s2)))
		return p;
	else
		return NULL;
}

static ESExpResult *
func_endswith(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
{
	PASBackendSummary *summary = data;

	return do_compare (summary, f, argc, argv, endswith_helper);
}

static char *
beginswith_helper (const char *s1, const char *s2)
{
	char *p;
	if ((p = (char*)e_utf8_strstrcase(s1, s2))
	    && (p == s1))
		return p;
	else
		return NULL;
}

static ESExpResult *
func_beginswith(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
{
	PASBackendSummary *summary = data;

	return do_compare (summary, f, argc, argv, beginswith_helper);
}

/* 'builtin' functions */
static struct {
	char *name;
	ESExpFunc *func;
	int type;		/* set to 1 if a function can perform shortcut evaluation, or
				   doesn't execute everything, 0 otherwise */
} symbols[] = {
	{ "contains", func_contains, 0 },
	{ "is", func_is, 0 },
	{ "beginswith", func_beginswith, 0 },
	{ "endswith", func_endswith, 0 },
};

GPtrArray*
pas_backend_summary_search (PASBackendSummary *summary, const char *query)
{
	ESExp *sexp;
	ESExpResult *r;
	GPtrArray *retval = g_ptr_array_new();
	int i;
	int esexp_error;

	sexp = e_sexp_new();

	for(i=0;i<sizeof(symbols)/sizeof(symbols[0]);i++) {
		if (symbols[i].type == 1) {
			e_sexp_add_ifunction(sexp, 0, symbols[i].name,
					     (ESExpIFunc *)symbols[i].func, summary);
		} else {
			e_sexp_add_function(sexp, 0, symbols[i].name,
					    symbols[i].func, summary);
		}
	}

	e_sexp_input_text(sexp, query, strlen(query));
	esexp_error = e_sexp_parse(sexp);

	if (esexp_error == -1) {
		return NULL;
	}

	r = e_sexp_eval(sexp);

	if (r && r->type == ESEXP_RES_ARRAY_PTR && r->value.ptrarray) {
		GPtrArray *ptrarray = r->value.ptrarray;
		int i;

		for (i = 0; i < ptrarray->len; i ++)
			g_ptr_array_add (retval, g_ptr_array_index (ptrarray, i));
	}

	e_sexp_result_free(sexp, r);

	e_sexp_unref (sexp);

	return retval;
}

char*
pas_backend_summary_get_summary_vcard(PASBackendSummary *summary, const char *id)
{
	PASBackendSummaryItem *item = g_hash_table_lookup (summary->priv->id_to_item, id);

	if (item) {
		ECard *card = e_card_new ("");
		ECardSimple *simple = e_card_simple_new (card);
		char *vcard;

		e_card_simple_set_id (simple, item->id);
		e_card_simple_set (simple, E_CARD_SIMPLE_FIELD_FILE_AS, item->file_as);
		e_card_simple_set (simple, E_CARD_SIMPLE_FIELD_GIVEN_NAME, item->given_name);
		e_card_simple_set (simple, E_CARD_SIMPLE_FIELD_FAMILY_NAME, item->surname);
		e_card_simple_set (simple, E_CARD_SIMPLE_FIELD_NICKNAME, item->nickname);
		e_card_simple_set (simple, E_CARD_SIMPLE_FIELD_FULL_NAME, item->full_name);
		e_card_simple_set_email (simple, E_CARD_SIMPLE_EMAIL_ID_EMAIL, item->email_1);
		e_card_simple_set_email (simple, E_CARD_SIMPLE_EMAIL_ID_EMAIL_2, item->email_2);
		e_card_simple_set_email (simple, E_CARD_SIMPLE_EMAIL_ID_EMAIL_3, item->email_3);

		e_card_simple_sync_card (simple);

		card->list = item->list;
		card->wants_html = item->wants_html;
		card->wants_html_set = item->wants_html_set;
		card->list_show_addresses = item->list_show_addresses;

		vcard = e_card_simple_get_vcard (simple);

		g_object_unref (simple);
		g_object_unref (card);

		return vcard;
	}
	else {
		g_warning ("in unable to locate card `%s' in summary", id);
		return NULL;
	}
}

