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

/*
 * e-book-util.c
 *
 * Copyright (C) 2001 Ximian, Inc.
 *
 * Developed by Jon Trowbridge <trow@ximian.com>
 */

/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU 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 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.
 */

#include <config.h>
#include "e-book-util.h"

#include <glib.h>
#include <glib-object.h>
#include "e-card-compare.h"

typedef struct _CommonBookInfo CommonBookInfo;
struct _CommonBookInfo {
	EBookCommonCallback cb;
	gpointer closure;
};

char *
e_book_expand_uri (const char *uri)
{
	if (!strncmp (uri, "file:", 5)) {
		int length = strlen (uri);
		int offset = 5;

		if (!strncmp (uri, "file://", 7))
			offset = 7;

		if (length < 3 || strcmp (uri + length - 3, ".db")) {
			/* we assume it's a dir and glom addressbook.db onto the end. */

			char *ret_val;
			char *file_name;

			file_name = g_build_path(uri + offset, "addressbook.db", NULL);
			ret_val = g_strdup_printf("file://%s", file_name);
			g_free(file_name);
			return ret_val; 
		}
	}

	return g_strdup (uri);
}

static void
got_uri_book_cb (EBook *book, EBookStatus status, gpointer closure)
{
	CommonBookInfo *info = (CommonBookInfo *) closure;

	if (status == E_BOOK_STATUS_SUCCESS) {
		info->cb (book, info->closure);
	} else {
		info->cb (NULL, info->closure);
	}
	g_free (info);
}

gboolean
e_book_load_address_book_by_uri (EBook *book, const char *uri, EBookCallback open_response, gpointer closure)
{
	gboolean rv;
	char *real_uri;

	g_return_val_if_fail (book != NULL,          FALSE);
	g_return_val_if_fail (E_IS_BOOK (book),      FALSE);
	g_return_val_if_fail (open_response != NULL, FALSE);

	real_uri = e_book_expand_uri (uri);

	rv = e_book_load_uri (book, real_uri, open_response, closure);

	if (!rv) {
		g_warning ("Couldn't load addressbook %s", real_uri);
	}

	g_free (real_uri);

	return rv;
}

void
e_book_use_address_book_by_uri (const char *uri, EBookCommonCallback cb, gpointer closure)
{
	EBook *book;
	CommonBookInfo *info;

	g_return_if_fail (cb != NULL);

	info = g_new0 (CommonBookInfo, 1);
	info->cb = cb;
	info->closure = closure;

	book = e_book_new ();
	if (! e_book_load_address_book_by_uri (book, uri, got_uri_book_cb, info)) {
		g_object_unref (book);
		g_free (info);
	}
}

EConfigListener *
e_book_get_config_database ()
{
	static EConfigListener *config_db;

	if (config_db == NULL)
		config_db = e_config_listener_new ();

	return config_db;
}

static EBook *common_default_book = NULL;

static void
got_default_book_cb (EBook *book, EBookStatus status, gpointer closure)
{
	CommonBookInfo *info = (CommonBookInfo *) closure;

	if (status == E_BOOK_STATUS_SUCCESS) {

		/* We try not to leak in a race condition where the
		   default book got loaded twice. */

		if (common_default_book) {
			g_object_unref (book);
			book = common_default_book;
		}
		
		info->cb (book, info->closure);

		if (common_default_book == NULL) {
			common_default_book = book;
		}
		
	} else {

		info->cb (NULL, info->closure);

	}
	g_free (info);
}

void
e_book_use_default_book (EBookCommonCallback cb, gpointer closure)
{
	EBook *book;
	CommonBookInfo *info;

	g_return_if_fail (cb != NULL);

	if (common_default_book != NULL) {
		cb (common_default_book, closure);
		return;
	}

	info = g_new0 (CommonBookInfo, 1);
	info->cb = cb;
	info->closure = closure;

	book = e_book_new ();
	if (! e_book_load_default_book (book, got_default_book_cb, info)) {
		g_object_unref (book);
		g_free (info);
	}
}

static char *default_book_uri;

static void
set_default_book_uri_local (void)
{
	char *filename;

	if (default_book_uri)
		g_free (default_book_uri);

	filename = g_build_filename (g_get_home_dir(),
				     "evolution/local/Contacts/addressbook.db",
				     NULL);
	default_book_uri = g_strdup_printf ("file://%s", filename);
	g_free (filename);
}

static void
set_default_book_uri_from_config_db (void)
{
	char *val;
	EConfigListener* config_db;

	config_db = e_book_get_config_database ();
	val = e_config_listener_get_string_with_default (config_db, "/DefaultFolders/contacts_uri", NULL, NULL);

	if (val) {
		default_book_uri = e_book_expand_uri (val);
		g_free (val);
	} else
		set_default_book_uri_local ();
}

typedef struct {
	gpointer closure;
	EBookCallback open_response;
} DefaultBookClosure;

static void
e_book_default_book_open (EBook *book, EBookStatus status, gpointer closure)
{
	DefaultBookClosure *default_book_closure = closure;
	gpointer user_closure = default_book_closure->closure;
	EBookCallback user_response = default_book_closure->open_response;

	g_free (default_book_closure);

	/* If there's a transient error, report it to the caller, but
	 * if the old default folder has disappeared, fall back to
	 * the local contacts folder instead.
	 */
	if (status == E_BOOK_STATUS_PROTOCOL_NOT_SUPPORTED ||
	    status == E_BOOK_STATUS_NO_SUCH_BOOK) {
		set_default_book_uri_local ();
		e_book_load_default_book (book, user_response, user_closure);
	} else {
		user_response (book, status, user_closure);
	}
}

gboolean
e_book_load_default_book (EBook *book, EBookCallback open_response, gpointer closure)
{
	char *uri;
	gboolean rv;
	DefaultBookClosure *default_book_closure;

	g_return_val_if_fail (book != NULL,          FALSE);
	g_return_val_if_fail (E_IS_BOOK (book),      FALSE);
	g_return_val_if_fail (open_response != NULL, FALSE);

	uri = e_book_get_default_book_uri ();

	default_book_closure = g_new (DefaultBookClosure, 1);

	default_book_closure->closure = closure;
	default_book_closure->open_response = open_response;

	rv = e_book_load_uri (book, uri,
			      e_book_default_book_open, default_book_closure);

	if (!rv) {
		g_warning ("Couldn't load default addressbook");
	}

	return rv;
}

char*
e_book_get_default_book_uri ()
{
	if (!default_book_uri)
		set_default_book_uri_from_config_db ();

	return default_book_uri;
}

/*
 *
 * Simple Query Stuff
 *
 */

typedef struct _SimpleQueryInfo SimpleQueryInfo;
struct _SimpleQueryInfo {
	guint tag;
	EBook *book;
	gchar *query;
	EBookSimpleQueryCallback cb;
	gpointer closure;
	EBookView *view;
	guint add_tag;
	guint seq_complete_tag;
	GList *cards;
	gboolean cancelled;
};

static void
book_add_simple_query (EBook *book, SimpleQueryInfo *info)
{
	GList *pending = g_object_get_data (G_OBJECT(book), "sq_pending");
	pending = g_list_prepend (pending, info);
	g_object_set_data (G_OBJECT (book), "sq_pending", pending);
}

static SimpleQueryInfo *
book_lookup_simple_query (EBook *book, guint tag)
{
	GList *pending = g_object_get_data (G_OBJECT (book), "sq_pending");
	while (pending) {
		SimpleQueryInfo *sq = pending->data;
		if (sq->tag == tag)
			return sq;
		pending = g_list_next (pending);
	}
	return NULL;
}

static void
book_remove_simple_query (EBook *book, SimpleQueryInfo *info)
{
	GList *pending = g_object_get_data (G_OBJECT (book), "sq_pending");
	GList *i;

	for (i=pending; i != NULL; i = g_list_next (i)) {
		if (i->data == info) {
			pending = g_list_remove_link (pending, i);
			g_list_free_1 (i);
			break;
		}
	}
	g_object_set_data (G_OBJECT (book), "sq_pending", pending);
}

static guint
book_issue_tag (EBook *book)
{
	gpointer ptr = g_object_get_data (G_OBJECT (book), "sq_tag");
	guint tag = GPOINTER_TO_UINT (ptr);
	if (tag == 0)
		tag = 1;
	g_object_set_data (G_OBJECT (book), "sq_tag", GUINT_TO_POINTER (tag+1));
	return tag;
}

static SimpleQueryInfo *
simple_query_new (EBook *book, const char *query, EBookSimpleQueryCallback cb, gpointer closure)
{
	SimpleQueryInfo *sq = g_new0 (SimpleQueryInfo, 1);

	sq->tag = book_issue_tag (book);
	sq->book = book;
	g_object_ref (book);
	sq->query = g_strdup (query);
	sq->cb = cb;
	sq->closure = closure;
	sq->cancelled = FALSE;

	/* Automatically add ourselves to the EBook's pending list. */
	book_add_simple_query (book, sq);

	return sq;
}

static void
simple_query_disconnect (SimpleQueryInfo *sq)
{
	if (sq->add_tag) {
		g_signal_handler_disconnect (sq->view, sq->add_tag);
		sq->add_tag = 0;
	}

	if (sq->seq_complete_tag) {
		g_signal_handler_disconnect (sq->view, sq->seq_complete_tag);
		sq->seq_complete_tag = 0;
	}

	if (sq->view) {
		g_object_unref (sq->view);
		sq->view = NULL;
	}
}

static void
simple_query_free (SimpleQueryInfo *sq)
{
	simple_query_disconnect (sq);

	/* Remove ourselves from the EBook's pending list. */
	book_remove_simple_query (sq->book, sq);

	g_free (sq->query);

	if (sq->book)
		g_object_unref (sq->book);

	g_list_foreach (sq->cards, (GFunc) g_object_unref, NULL);
	g_list_free (sq->cards);

	g_free (sq);
}

static void
simple_query_card_added_cb (EBookView *view, const GList *cards, gpointer closure)
{
	SimpleQueryInfo *sq = closure;

	if (sq->cancelled)
		return;

	sq->cards = g_list_concat (sq->cards, g_list_copy ((GList *) cards));
	g_list_foreach ((GList *) cards, (GFunc) g_object_ref, NULL);
}

static void
simple_query_sequence_complete_cb (EBookView *view, EBookViewStatus status, gpointer closure)
{
	SimpleQueryInfo *sq = closure;

	/* Disconnect signals, so that we don't pick up any changes to the book that occur
	   in our callback */
	simple_query_disconnect (sq);
	if (! sq->cancelled)
		sq->cb (sq->book, E_BOOK_SIMPLE_QUERY_STATUS_SUCCESS, sq->cards, sq->closure);
	simple_query_free (sq);
}

static void
simple_query_book_view_cb (EBook *book, EBookStatus status, EBookView *book_view, gpointer closure)
{
	SimpleQueryInfo *sq = closure;

	if (sq->cancelled) {
		simple_query_free (sq);
		return;
	}

	if (status != E_BOOK_STATUS_SUCCESS) {
		simple_query_disconnect (sq);
		sq->cb (sq->book, E_BOOK_SIMPLE_QUERY_STATUS_OTHER_ERROR, NULL, sq->closure);
		simple_query_free (sq);
		return;
	}

	sq->view = book_view;
	g_object_ref (book_view);

	sq->add_tag = g_signal_connect (sq->view, "card_added",
					G_CALLBACK (simple_query_card_added_cb), sq);
	sq->seq_complete_tag = g_signal_connect (sq->view, "sequence_complete",
						 G_CALLBACK (simple_query_sequence_complete_cb), sq);
}

guint
e_book_simple_query (EBook *book, const char *query, EBookSimpleQueryCallback cb, gpointer closure)
{
	SimpleQueryInfo *sq;

	g_return_val_if_fail (book && E_IS_BOOK (book), 0);
	g_return_val_if_fail (query, 0);
	g_return_val_if_fail (cb, 0);

	sq = simple_query_new (book, query, cb, closure);
	e_book_get_book_view (book, (gchar *) query, simple_query_book_view_cb, sq);

	return sq->tag;
}

void
e_book_simple_query_cancel (EBook *book, guint tag)
{
	SimpleQueryInfo *sq;

	g_return_if_fail (book && E_IS_BOOK (book));

	sq = book_lookup_simple_query (book, tag);

	if (sq) {
		sq->cancelled = TRUE;
		sq->cb (sq->book, E_BOOK_SIMPLE_QUERY_STATUS_CANCELLED, NULL, sq->closure);
	} else {
		g_warning ("Simple query tag %d is unknown", tag);
	}
}

/*
 *
 * Specialized Queries
 *
 */

typedef struct _NameEmailQueryInfo NameEmailQueryInfo;
struct _NameEmailQueryInfo {
	gchar *name;
	gchar *email;
	EBookSimpleQueryCallback cb;
	gpointer closure;
};

static void
name_email_query_info_free (NameEmailQueryInfo *info)
{
	if (info) {
		g_free (info->name);
		g_free (info->email);
		g_free (info);
	}
}

static void
name_and_email_cb (EBook *book, EBookSimpleQueryStatus status, const GList *cards, gpointer closure)
{
	NameEmailQueryInfo *info = closure;
	GList *filtered_cards = NULL;

	while (cards) {
		ECard *card = E_CARD (cards->data);
		if ((info->name == NULL || e_card_compare_name_to_string (card, info->name) >= E_CARD_MATCH_VAGUE)
		    && (info->email == NULL || e_card_email_match_string (card, info->email))) {
			filtered_cards = g_list_append (filtered_cards, card);
		}
		cards = g_list_next (cards);
	}

	info->cb (book, status, filtered_cards, info->closure);

	g_list_free (filtered_cards);

	name_email_query_info_free (info);
}

guint
e_book_name_and_email_query (EBook *book,
			     const gchar *name,
			     const gchar *email,
			     EBookSimpleQueryCallback cb,
			     gpointer closure)
{
	NameEmailQueryInfo *info;
	gchar *email_query=NULL, *name_query=NULL, *query;
	guint tag;

	g_return_val_if_fail (book && E_IS_BOOK (book), 0);
	g_return_val_if_fail (cb != NULL, 0);

	if (name && !*name)
		name = NULL;
	if (email && !*email)
		email = NULL;

	if (name == NULL && email == NULL)
		return 0;

	/* Build our e-mail query.
	 * We only query against the username part of the address, to avoid not matching
	 * fred@foo.com and fred@mail.foo.com.  While their may be namespace collisions
	 * in the usernames of everyone out there, it shouldn't be that bad.  (Famous last words.)
	 */
	if (email) {
		const gchar *t = email;
		while (*t && *t != '@')
			++t;
		if (*t == '@') {
			email_query = g_strdup_printf ("(beginswith \"email\" \"%.*s@\")", t-email, email);

		} else {
			email_query = g_strdup_printf ("(beginswith \"email\" \"%s\")", email);
		}
	}

	/* Build our name query.
	 * We only do name-query stuff if we don't have an e-mail address.  Our basic assumption
	 * is that the username part of the email is good enough to keep the amount of stuff returned
	 * in the query relatively small.
	 */
	if (name && !email) {
		gchar *name_cpy = g_strdup (name), *qjoined;
		gchar **namev;
		gint i, count=0;

		g_strstrip (name_cpy);
		namev = g_strsplit (name_cpy, " ", 0);
		for (i=0; namev[i]; ++i) {
			if (*namev[i]) {
				char *str = namev[i];

				namev[i] = g_strdup_printf ("(contains \"file_as\" \"%s\")", namev[i]);
				++count;

				g_free (str);
			}
		}

		qjoined = g_strjoinv (" ", namev);
		if (count > 1) {
			name_query = g_strdup_printf ("(or %s)", qjoined);
		} else {
			name_query = qjoined;
			qjoined = NULL;
		}
		
		g_free (name_cpy);
		g_strfreev (namev);
		g_free (qjoined);
	}

	/* Assemble our e-mail & name queries */
	if (email_query && name_query) {
		query = g_strdup_printf ("(and %s %s)", email_query, name_query);
	} else if (email_query) {
		query = email_query;
		email_query = NULL;
	} else if (name_query) {
		query = name_query;
		name_query = NULL;
	} else
		return 0;

	info = g_new0 (NameEmailQueryInfo, 1);
	info->name = g_strdup (name);
	info->email = g_strdup (email);
	info->cb = cb;
	info->closure = closure;

	tag = e_book_simple_query (book, query, name_and_email_cb, info);

	g_free (email_query);
	g_free (name_query);
	g_free (query);

	return tag;
}

/*
 * Simple nickname query
 */

typedef struct _NicknameQueryInfo NicknameQueryInfo;
struct _NicknameQueryInfo {
	gchar *nickname;
	EBookSimpleQueryCallback cb;
	gpointer closure;
};

static void
nickname_cb (EBook *book, EBookSimpleQueryStatus status, const GList *cards, gpointer closure)
{
	NicknameQueryInfo *info = closure;

	if (info->cb)
		info->cb (book, status, cards, info->closure);

	g_free (info->nickname);
	g_free (info);
}

guint
e_book_nickname_query (EBook *book,
		       const char *nickname,
		       EBookSimpleQueryCallback cb,
		       gpointer closure)
{
	NicknameQueryInfo *info;
	gchar *query;
	guint retval;

	g_return_val_if_fail (E_IS_BOOK (book), 0);
	g_return_val_if_fail (nickname != NULL, 0);

	/* The empty-string case shouldn't generate a warning. */
	if (! *nickname)
		return 0;

	info = g_new0 (NicknameQueryInfo, 1);
	info->nickname = g_strdup (nickname);
	info->cb = cb;
	info->closure = closure;

	query = g_strdup_printf ("(is \"nickname\" \"%s\")", info->nickname);

	retval = e_book_simple_query (book, query, nickname_cb, info);

	g_free (query);

	return retval;
}

/*
 *  Convenience routine to check for addresses in the local address book.
 */

typedef struct _HaveAddressInfo HaveAddressInfo;
struct _HaveAddressInfo {
	gchar *email;
	EBookHaveAddressCallback cb;
	gpointer closure;
};

static void
have_address_query_cb (EBook *book, EBookSimpleQueryStatus status, const GList *cards, gpointer closure)
{
	HaveAddressInfo *info = (HaveAddressInfo *) closure;
	
	info->cb (book, 
		  info->email,
		  cards && (status == E_BOOK_SIMPLE_QUERY_STATUS_SUCCESS) ? E_CARD (cards->data) : NULL,
		  info->closure);

	g_free (info->email);
	g_free (info);
}

static void
have_address_book_open_cb (EBook *book, gpointer closure)
{
	HaveAddressInfo *info = (HaveAddressInfo *) closure;

	if (book) {

		e_book_name_and_email_query (book, NULL, info->email, have_address_query_cb, info);

	} else {

		info->cb (NULL, info->email, NULL, info->closure);

		g_free (info->email);
		g_free (info);

	}
}

void
e_book_query_address_default (const gchar *email,
			      EBookHaveAddressCallback cb,
			      gpointer closure)
{
	HaveAddressInfo *info;

	g_return_if_fail (email != NULL);
	g_return_if_fail (cb != NULL);

	info = g_new0 (HaveAddressInfo, 1);
	info->email = g_strdup (email);
	info->cb = cb;
	info->closure = closure;

	e_book_use_default_book (have_address_book_open_cb, info);
}
