/*
 * Code for checking for duplicates when doing EContact work.
 *
 * 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 of the License, or (at your option) version 3.
 *
 * 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 the program; if not, see <http://www.gnu.org/licenses/>
 *
 *
 * Authors:
 *		Christopher James Lahey <clahey@ximian.com>
 *		Chris Toshok <toshok@ximian.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

#include <config.h>

#include "eab-contact-merging.h"
#include "eab-contact-compare.h"
#include <gtk/gtk.h>
#include <string.h>
#include "addressbook/gui/widgets/eab-contact-display.h"
#include "e-util/e-util.h"
#include "e-util/e-util-private.h"
#include <glib/gi18n.h>

typedef struct dropdown_data dropdown_data;
typedef enum {
	E_CONTACT_MERGING_ADD,
	E_CONTACT_MERGING_COMMIT,
	E_CONTACT_MERGING_FIND
} EContactMergingOpType;

typedef struct {
	EContactMergingOpType op;
	EBook *book;
	/*contact is the new contact which the user has tried to add to the addressbook*/
	EContact *contact;
	/*match is the duplicate contact already existing in the addressbook*/
	EContact *match;
	GList *avoid;
	EBookIdAsyncCallback id_cb;
	EBookAsyncCallback   cb;
	EBookContactAsyncCallback c_cb;
	gpointer closure;
} EContactMergingLookup;

struct dropdown_data {
	EContact *match;
	EContactField field;
};
static void match_query_callback (EContact *contact, EContact *match, EABContactMatchType type, gpointer closure);

#define SIMULTANEOUS_MERGING_REQUESTS 20

static GList *merging_queue = NULL;
static gint running_merge_requests = 0;

static void
add_lookup (EContactMergingLookup *lookup)
{
	if (running_merge_requests < SIMULTANEOUS_MERGING_REQUESTS) {
		running_merge_requests++;
		eab_contact_locate_match_full (lookup->book, lookup->contact, lookup->avoid, match_query_callback, lookup);
	}
	else {
		merging_queue = g_list_append (merging_queue, lookup);
	}
}

static void
finished_lookup (void)
{
	running_merge_requests--;

	while (running_merge_requests < SIMULTANEOUS_MERGING_REQUESTS) {
		EContactMergingLookup *lookup;

		if (!merging_queue)
			break;

		lookup = merging_queue->data;

		merging_queue = g_list_remove_link (merging_queue, merging_queue);

		running_merge_requests++;
		eab_contact_locate_match_full (lookup->book, lookup->contact, lookup->avoid, match_query_callback, lookup);
	}
}

static void
free_lookup (EContactMergingLookup *lookup)
{
	g_object_unref (lookup->book);
	g_object_unref (lookup->contact);
	g_list_free (lookup->avoid);
	if (lookup->match)
		g_object_unref (lookup->match);
	g_free (lookup);
}

static void
final_id_cb (EBook *book, const GError *error, const gchar *id, gpointer closure)
{
	EContactMergingLookup *lookup = closure;

	if (lookup->id_cb)
		lookup->id_cb (lookup->book, error, id, lookup->closure);

	free_lookup (lookup);

	finished_lookup ();
}

static void
final_cb_as_id (EBook *book, const GError *error, gpointer closure)
{
	EContactMergingLookup *lookup = closure;

	if (lookup->id_cb)
		lookup->id_cb (lookup->book, error, lookup->contact ? e_contact_get_const (lookup->contact, E_CONTACT_UID) : NULL, lookup->closure);

	free_lookup (lookup);

	finished_lookup ();
}

static void
final_cb (EBook *book, const GError *error, gpointer closure)
{
	EContactMergingLookup *lookup = closure;

	if (lookup->cb)
		lookup->cb (lookup->book, error, lookup->closure);

	free_lookup (lookup);

	finished_lookup ();
}

static void
doit (EContactMergingLookup *lookup, gboolean force_commit)
{
	if (lookup->op == E_CONTACT_MERGING_ADD) {
		if (force_commit)
			e_book_commit_contact_async (lookup->book, lookup->contact, final_cb_as_id, lookup);
		else
			e_book_add_contact_async (lookup->book, lookup->contact, final_id_cb, lookup);
	} else if (lookup->op == E_CONTACT_MERGING_COMMIT)
		e_book_commit_contact_async (lookup->book, lookup->contact, final_cb, lookup);
}

static void
cancelit (EContactMergingLookup *lookup)
{
	GError *error = g_error_new (E_BOOK_ERROR, E_BOOK_ERROR_CANCELLED, _("Cancelled"));

	if (lookup->op == E_CONTACT_MERGING_ADD) {
		final_id_cb (lookup->book, error, NULL, lookup);
	} else if (lookup->op == E_CONTACT_MERGING_COMMIT) {
		final_cb (lookup->book, error, lookup);
	}

	g_error_free (error);
}

static void
dialog_map (GtkWidget *window, GdkEvent *event, GtkWidget *table)
{
	GtkAllocation allocation;
	gint h, w;

	gtk_widget_get_allocation (table, &allocation);

	/* Spacing around the table */
	w = allocation.width + 30;
	/* buttons and outer spacing */
	h = allocation.height + 60;
	if (w > 400)
		w = 400;
	if (h > 450)
		h = 450;

	gtk_widget_set_size_request (window, w, h);
}

static void
dropdown_changed (GtkWidget *dropdown, dropdown_data *data)
{
	gchar *str;
	str = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (dropdown));

	if (g_ascii_strcasecmp(str, ""))
		e_contact_set (data->match, data->field, str);
	else
		e_contact_set (data->match, data->field, NULL);
	return;
}

static gint
mergeit (EContactMergingLookup *lookup)
{
	GtkWidget *scrolled_window, *label, *hbox, *dropdown;
	GtkWidget *content_area;
	GtkDialog *dialog;
	GtkTable *table;
	EContactField field;
	gchar *str = NULL, *string = NULL, *string1 = NULL;
	gint num_of_email;
	GList *email_attr_list;
	gint row = -1;
	gint value = 0, result;

	dialog = (GtkDialog *)(gtk_dialog_new_with_buttons (_("Merge Contact"), NULL, 0, NULL));
	gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);

	content_area = gtk_dialog_get_content_area (dialog);

	scrolled_window = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
					GTK_POLICY_AUTOMATIC,
					GTK_POLICY_AUTOMATIC);

	table = (GtkTable *) gtk_table_new (20, 2, FALSE);
	gtk_container_set_border_width ((GtkContainer *) table, 12);
	gtk_table_set_row_spacings (table, 6);
	gtk_table_set_col_spacings (table, 2);

	gtk_dialog_add_buttons ((GtkDialog *) dialog,
				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
				_("_Merge"), GTK_RESPONSE_OK,
				NULL);

	email_attr_list = e_contact_get_attributes (lookup->match, E_CONTACT_EMAIL);
	num_of_email = g_list_length (email_attr_list);

	/*we match all the string fields of the already existing contact and the new contact.*/
	for (field = E_CONTACT_FULL_NAME; field != (E_CONTACT_LAST_SIMPLE_STRING -1); field++) {
		dropdown_data *data = NULL;
		string = (gchar *)e_contact_get_const (lookup->contact, field);
		string1 = (gchar *)e_contact_get_const (lookup->match, field);

		/*the field must exist in the new as well as the duplicate contact*/
		if (string && *string) {
			/*Four email id's present, should be compared with all email id's in duplicate contact */
			/*Merge only if number of email id's in existing contact is less than 4 */
			if ((field == E_CONTACT_EMAIL_1 || field == E_CONTACT_EMAIL_2
			    || field == E_CONTACT_EMAIL_3 || field == E_CONTACT_EMAIL_4) && (num_of_email < 4)) {
				EContactField use_field = field;
				row++;
				str = (gchar *)e_contact_get_const (lookup->contact, use_field);
				switch (num_of_email)
				{
				case 0:
					use_field = E_CONTACT_EMAIL_1;
					break;
				case 1:
					/*New contact has email that is NOT equal to email in duplicate contact*/
					if ((str && *str) && (g_ascii_strcasecmp (e_contact_get_const (lookup->match, E_CONTACT_EMAIL_1),str))) {
						use_field = E_CONTACT_EMAIL_2;
						break;
					}
					else/*Either the new contact has no email OR the email already exist in the duplicate contact*/
						continue;
				case 2:
					/*New contact has email and it is equal to neither of the 2 emails in the duplicate contact*/
					if ((str && *str) &&
							(g_ascii_strcasecmp (str,e_contact_get_const (lookup->match, E_CONTACT_EMAIL_1))) &&
							(g_ascii_strcasecmp (e_contact_get_const (lookup->match, E_CONTACT_EMAIL_2),str))) {
						use_field = E_CONTACT_EMAIL_3;
						break;
					}
					else
						continue;
				case 3:
					/*New contact has email and it is equal to none of the 3 emails in the duplicate contact*/
					if ((str && *str) &&
							(g_ascii_strcasecmp (e_contact_get_const (lookup->match, E_CONTACT_EMAIL_1),str)) &&
							(g_ascii_strcasecmp (e_contact_get_const (lookup->match, E_CONTACT_EMAIL_2),str)) &&
							(g_ascii_strcasecmp (e_contact_get_const (lookup->match, E_CONTACT_EMAIL_3),str)))
						use_field = E_CONTACT_EMAIL_4;
					else
						continue;
				}
				label = gtk_label_new (_("Email"));
				hbox = gtk_hbox_new (FALSE, 0);
				gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget*)label, FALSE, FALSE, 0);
				gtk_table_attach_defaults (table, (GtkWidget *)hbox, 0, 1, row, row + 1);

				dropdown = gtk_combo_box_text_new ();
				gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), string);

				data = g_new0 (dropdown_data, 1);

				gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), "");

				gtk_combo_box_set_active (GTK_COMBO_BOX (dropdown), 0);
				data->field = use_field;
				data->match = lookup->match;
				e_contact_set (lookup->match, use_field, string);
				g_signal_connect (dropdown, "changed", G_CALLBACK(dropdown_changed), data);

				hbox = gtk_hbox_new (FALSE, 0);
				gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget*)dropdown, FALSE, FALSE, 0);
				gtk_table_attach_defaults (table, (GtkWidget *)hbox, 1, 2, row, row + 1);
				gtk_widget_show ((GtkWidget *)dropdown);
				continue;
			}
			if (((field == E_CONTACT_FULL_NAME) && (!g_ascii_strcasecmp (string, string1)))) {
				row++;
				label = gtk_label_new (e_contact_pretty_name (field));
				hbox = gtk_hbox_new (FALSE, 0);
				gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget*)label, FALSE, FALSE, 0);
				gtk_table_attach_defaults (table, (GtkWidget *)hbox, 0, 1, row, row + 1);

				label = gtk_label_new (string);
				hbox = gtk_hbox_new (FALSE, 0);
				gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget*)label, FALSE, FALSE, 0);
				gtk_table_attach_defaults (table, (GtkWidget*)hbox, 1, 2, row, row + 1);
				continue;
			}

			/*for all string fields except name and email*/
			if (!(string1 && *string1) || (g_ascii_strcasecmp (string, string1))) {
				row++;
				label = gtk_label_new (e_contact_pretty_name (field));
				hbox = gtk_hbox_new (FALSE, 0);
				gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget*)label, FALSE, FALSE, 0);
				gtk_table_attach_defaults (table, (GtkWidget *)hbox, 0, 1, row, row + 1);
				data = g_new0 (dropdown_data, 1);
				dropdown = gtk_combo_box_text_new ();
				gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), string);
				e_contact_set (lookup->match, field, string);

				if (string1 && *string1)
					gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), string1);
				else
					gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), "");

				gtk_combo_box_set_active (GTK_COMBO_BOX (dropdown), 0);
				data->field = field;
				data->match = lookup->match;

				if (field == E_CONTACT_NICKNAME || field == E_CONTACT_GIVEN_NAME)
					gtk_widget_set_sensitive ((GtkWidget *)dropdown, FALSE);

				g_signal_connect (dropdown, "changed", G_CALLBACK(dropdown_changed), data);
				hbox = gtk_hbox_new (FALSE, 0);
				gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget*)dropdown, FALSE, FALSE, 0);
				gtk_table_attach_defaults (table, (GtkWidget *)hbox, 1, 2, row, row + 1);
				gtk_widget_show_all ((GtkWidget *)dropdown);
			}
		}
	}

	gtk_window_set_default_size (GTK_WINDOW (dialog), 420, 300);
	gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_window), GTK_WIDGET (table));
	gtk_box_pack_start (GTK_BOX (content_area), GTK_WIDGET (scrolled_window), TRUE, TRUE, 0);
	gtk_widget_show (scrolled_window);
	g_signal_connect (dialog, "map-event", G_CALLBACK (dialog_map), table);
	gtk_widget_show_all ((GtkWidget *)table);
	result = gtk_dialog_run (dialog);

	switch (result)
	{
	case GTK_RESPONSE_OK:
		     lookup->contact = lookup->match;
		     e_book_remove_contact_async (lookup->book, lookup->match, NULL, lookup);
		     e_book_add_contact_async (lookup->book, lookup->contact, final_id_cb, lookup);
		     value = 1;
		     break;
	case GTK_RESPONSE_CANCEL:
	default:
		     value = 0;
		     break;
	}
	gtk_widget_destroy (GTK_WIDGET (dialog));
	g_list_free (email_attr_list);
	return value;
}

static gboolean
check_if_same (EContact *contact, EContact *match)
{
	EContactField field;
	GList *email_attr_list;
	gint num_of_email;
	gchar *str = NULL, *string = NULL, *string1 = NULL;

	for (field = E_CONTACT_FULL_NAME; field != (E_CONTACT_LAST_SIMPLE_STRING -1); field++) {
		email_attr_list = e_contact_get_attributes (match, E_CONTACT_EMAIL);
		num_of_email = g_list_length (email_attr_list);

		if ((field == E_CONTACT_EMAIL_1 || field == E_CONTACT_EMAIL_2
		     || field == E_CONTACT_EMAIL_3 || field == E_CONTACT_EMAIL_4) && (num_of_email<4)) {
			str = (gchar *)e_contact_get_const (contact, field);
			switch (num_of_email)
			{
			case 0:
				return FALSE;
			case 1:
				if ((str && *str) && (g_ascii_strcasecmp (e_contact_get_const (match, E_CONTACT_EMAIL_1),str)))
					return FALSE;
			case 2:
				if ((str && *str) && (g_ascii_strcasecmp (str,e_contact_get_const (match, E_CONTACT_EMAIL_1))) &&
						(g_ascii_strcasecmp (e_contact_get_const (match, E_CONTACT_EMAIL_2),str)))
					return FALSE;
			case 3:
				if ((str && *str) && (g_ascii_strcasecmp (e_contact_get_const (match, E_CONTACT_EMAIL_1),str)) &&
						(g_ascii_strcasecmp (e_contact_get_const (match, E_CONTACT_EMAIL_2),str)) &&
						(g_ascii_strcasecmp (e_contact_get_const (match, E_CONTACT_EMAIL_3),str)))
					return FALSE;
			}
		}
		else {
			string = (gchar *)e_contact_get_const (contact, field);
			string1 = (gchar *)e_contact_get_const (match, field);
			if ((string && *string) && (string1 && *string1) && (g_ascii_strcasecmp (string1,string)))
				return FALSE;
			/*if the field entry exist in either of the contacts,we'll have to give the choice and thus merge button should be sensitive*/
			else if ((string && *string) && !(string1 && *string1))
				return FALSE;
		}
	}
	g_list_free (email_attr_list);
	return TRUE;
}

static void
response (GtkWidget *dialog, gint response, EContactMergingLookup *lookup)
{
	static gint merge_response;

	switch (response) {
	case 0:
		doit (lookup, FALSE);
		break;
	case 1:
		cancelit (lookup);
		break;
	case 2:
		merge_response = mergeit (lookup);
		if (merge_response)
			break;
		return;
	case GTK_RESPONSE_DELETE_EVENT:
		cancelit (lookup);
		break;
	}

	gtk_widget_destroy (dialog);
}

static void
match_query_callback (EContact *contact, EContact *match, EABContactMatchType type, gpointer closure)
{
	EContactMergingLookup *lookup = closure;
	gint flag;
	gboolean same_uids;

	if (lookup->op == E_CONTACT_MERGING_FIND) {
		if (lookup->c_cb)
			lookup->c_cb (lookup->book, NULL, (gint) type <= (gint) EAB_CONTACT_MATCH_VAGUE ? NULL : match, lookup->closure);

		free_lookup (lookup);
		finished_lookup ();
		return;
	}

	/* if had same UID, then we are editing old contact, thus force commit change to it */
	same_uids = contact && match
		&& e_contact_get_const (contact, E_CONTACT_UID)
		&& e_contact_get_const (match, E_CONTACT_UID)
		&& g_str_equal (e_contact_get_const (contact, E_CONTACT_UID), e_contact_get_const (match, E_CONTACT_UID));

	if ((gint) type <= (gint) EAB_CONTACT_MATCH_VAGUE || same_uids) {
		doit (lookup, same_uids);
	} else {
		GtkBuilder *builder;
		GtkWidget *container;
		GtkWidget *merge_button;
		GtkWidget *widget;

		/* XXX I think we're leaking the GtkBuilder. */
		builder = gtk_builder_new ();

		lookup->match = g_object_ref (match);
		if (lookup->op == E_CONTACT_MERGING_ADD) {
			/* Compares all the values of contacts and return true, if they match */
			flag = check_if_same (contact, match);
			e_load_ui_builder_definition (
				builder, "eab-contact-duplicate-detected.ui");
			merge_button = e_builder_get_widget (builder, "button5");
			/* Merge Button not sensitive when all values are same */
			if (flag)
				gtk_widget_set_sensitive (GTK_WIDGET (merge_button), FALSE);
		} else if (lookup->op == E_CONTACT_MERGING_COMMIT) {
			e_load_ui_builder_definition (
				builder, "eab-contact-commit-duplicate-detected.ui");
		} else {
			doit (lookup, FALSE);
			return;
		}

		widget = e_builder_get_widget (builder, "custom-old-contact");
		eab_contact_display_set_mode (
			EAB_CONTACT_DISPLAY (widget),
			EAB_CONTACT_DISPLAY_RENDER_COMPACT);
		eab_contact_display_set_contact (
			EAB_CONTACT_DISPLAY (widget), match);

		widget = e_builder_get_widget (builder, "custom-new-contact");
		eab_contact_display_set_mode (
			EAB_CONTACT_DISPLAY (widget),
			EAB_CONTACT_DISPLAY_RENDER_COMPACT);
		eab_contact_display_set_contact (
			EAB_CONTACT_DISPLAY (widget), contact);

		widget = e_builder_get_widget (builder, "dialog-duplicate-contact");

		gtk_widget_ensure_style (widget);

		container = gtk_dialog_get_action_area (GTK_DIALOG (widget));
		gtk_container_set_border_width (GTK_CONTAINER (container), 12);

		container = gtk_dialog_get_content_area (GTK_DIALOG (widget));
		gtk_container_set_border_width (GTK_CONTAINER (container), 0);

		g_signal_connect (
			widget, "response",
			G_CALLBACK (response), lookup);

		gtk_widget_show_all (widget);
	}
}

gboolean
eab_merging_book_add_contact (EBook                *book,
			      EContact             *contact,
			      EBookIdAsyncCallback  cb,
			      gpointer              closure)
{
	EContactMergingLookup *lookup;

	lookup = g_new (EContactMergingLookup, 1);

	lookup->op = E_CONTACT_MERGING_ADD;
	lookup->book = g_object_ref (book);
	lookup->contact = g_object_ref (contact);
	lookup->id_cb = cb;
	lookup->closure = closure;
	lookup->avoid = NULL;
	lookup->match = NULL;

	add_lookup (lookup);

	return TRUE;
}

gboolean
eab_merging_book_commit_contact (EBook              *book,
				 EContact           *contact,
				 EBookAsyncCallback  cb,
				 gpointer            closure)
{
	EContactMergingLookup *lookup;

	lookup = g_new (EContactMergingLookup, 1);

	lookup->op = E_CONTACT_MERGING_COMMIT;
	lookup->book = g_object_ref (book);
	lookup->contact = g_object_ref (contact);
	lookup->cb = cb;
	lookup->closure = closure;
	lookup->avoid = g_list_append (NULL, contact);
	lookup->match = NULL;

	add_lookup (lookup);

	return TRUE;
}

gboolean
eab_merging_book_find_contact (EBook                     *book,
			       EContact                  *contact,
			       EBookContactAsyncCallback  cb,
			       gpointer                   closure)
{
	EContactMergingLookup *lookup;

	lookup = g_new (EContactMergingLookup, 1);

	lookup->op = E_CONTACT_MERGING_FIND;
	lookup->book = g_object_ref (book);
	lookup->contact = g_object_ref (contact);
	lookup->c_cb = cb;
	lookup->closure = closure;
	lookup->avoid = g_list_append (NULL, contact);
	lookup->match = NULL;

	add_lookup (lookup);

	return TRUE;
}
