/* This file is part of GQ
 *
 * AUTHORS
 *     Sven Herzberg  <herzi@gnome-de.org>
 *
 * Copyright (C) 2006  Sven Herzberg
 *
 * 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#define DEBUG_CHANGES 0

#include "gq-difference.h"

#include <string.h>

enum {
	SIG_UPDATED,
	N_SIGNALS
};

enum GqChangeType {
	GQ_CHANGE_TYPE_NONE,
// FIXME: be more specific about the change type	
//	GQ_CHANGE_TYPE_ONLY_FIRST,
//	GQ_CHANGE_TYPE_ONLY_SECOND,
	GQ_CHANGE_TYPE_DIFFERENT
};

struct GqChangeSet {
	enum GqChangeType  type;
	GqFormfill       * form1;
	GqFormfill       * form2;
};

struct GqDifferencePrivate {
	GqInputForm* form1;
	GqInputForm* form2;

	GSList* changes;

	guint idle;
};
#define P(i) (G_TYPE_INSTANCE_GET_PRIVATE((i), GQ_TYPE_DIFFERENCE, struct GqDifferencePrivate))

static guint difference_signals[N_SIGNALS] = {0};

GqDifference*
gq_difference_new(void)
{
	return g_object_new(GQ_TYPE_DIFFERENCE, NULL);
}

void
gq_difference_foreach(GqDifference const* self,
		      GqDifferenceFunc    func,
		      gpointer            user_data)
{
	GSList* it;
	g_return_if_fail(GQ_IS_DIFFERENCE(self));
	g_return_if_fail(func);

	for(it = P(self)->changes; it; it = g_slist_next(it)) {
		struct GqChangeSet* set = it->data;
		func(set->form1, set->form2, set->type == GQ_CHANGE_TYPE_NONE, user_data);
	}
}

#if DEBUG_CHANGES >= 1
static void
difference_dump_changes(GqDifference* self)
{
	GSList* it;
	g_print("Changes:\n========\n");
	for(it = P(self)->changes; it; it = g_slist_next(it)) {
		struct GqChangeSet* set = it->data;
		gchar const* change;
		switch(set->type) {
		case GQ_CHANGE_TYPE_DIFFERENT:
			change = "!=";
			break;
		case GQ_CHANGE_TYPE_NONE:
		default:
			change = "==";
			break;
		}
		g_print("%s %s\n", change, gq_formfill_get_attrname(set->form1));
	}
	g_print("--------\n");
}
#endif

static gboolean
difference_recalc(GqDifference* self)
{
	GList* formlist1 = gq_input_form_get_formlist(P(self)->form1);
	GList* formlist2 = gq_input_form_get_formlist(P(self)->form2);

#warning "FIXME: free changes list"
	P(self)->changes = NULL;

	for(; formlist1; formlist1 = g_list_next(formlist1)) {
		struct GqChangeSet* set = NULL;
		GqFormfill* ff1 = GQ_FORMFILL(formlist1->data);
		gchar const* form1 = gq_formfill_get_attrname(ff1);
		gboolean form1_done = FALSE;
#if DEBUG_CHANGES >= 2
		g_print("testing %s\n", form1);
#endif
		GList* form2_it;
		for(form2_it = formlist2; form2_it; form2_it = g_list_next(form2_it)) {
			GqFormfill* ff2 = GQ_FORMFILL(form2_it->data);
			gchar const* form2 = gq_formfill_get_attrname(ff2);
			gint input1, input2;
			if(!form1 || !form2 || strcmp(form1, form2)) {
#if DEBUG_CHANGES >= 2
				g_print("  %s doesn't match name\n", form2 ? form2 : "NULL");
#endif
				continue;
			}

			form1_done = TRUE;
#if DEBUG_CHANGES >= 2
			g_print("  %s matches name\n", form2);
#endif
				// matching names
			input1 = gq_formfill_get_n_inputfields(ff1);
			input2 = gq_formfill_get_n_inputfields(ff2);

			for(; form2_it != formlist2; formlist2 = g_list_next(formlist2)) {
				set = g_new0(struct GqChangeSet, 1);
				set->form1 = NULL;
				set->form2 = g_object_ref(formlist2->data);
				set->type = GQ_CHANGE_TYPE_DIFFERENT;
				P(self)->changes = g_slist_prepend(P(self)->changes, set);
#if DEBUG_CHANGES >= 2
				g_print("    adding missing entry %s\n", gq_formfill_get_attrname(formlist2->data));
#endif
			}
			formlist2 = g_list_next(formlist2);
			form2_it = g_list_next(form2_it);
			set = g_new0(struct GqChangeSet, 1);
			set->form1 = g_object_ref(ff1);
			set->form2 = g_object_ref(ff2);
			if(gq_formfill_equals_data(ff1, ff2)) {
				// FIXME: add as equal
				set->type  = GQ_CHANGE_TYPE_NONE;
			}
			else {
				// FIXME: add as different
				set->type  = GQ_CHANGE_TYPE_DIFFERENT;
			}
			P(self)->changes = g_slist_prepend(P(self)->changes, set);
		}
		if(!form1_done) {
			set = g_new0(struct GqChangeSet, 1);
			set->type = GQ_CHANGE_TYPE_DIFFERENT;
			set->form1 = g_object_ref(ff1);
			set->form2 = NULL;
			P(self)->changes = g_slist_prepend(P(self)->changes, set);
		}
	}
	P(self)->changes = g_slist_reverse(P(self)->changes);
#if DEBUG_CHANGES >= 1
	difference_dump_changes(self);
#endif
	// FIXME: compare with old list and emit only on changes
	g_signal_emit(self, difference_signals[SIG_UPDATED], 0);
	P(self)->idle = 0;
	return FALSE;
}

static void
difference_queue_recalc(GqDifference* self)
{
	if(!P(self)->idle) {
		P(self)->idle = g_idle_add((GSourceFunc)difference_recalc, self);
	}
}

void
gq_difference_set_form1(GqDifference* self,
			GqInputForm * form1)
{
	g_return_if_fail(GQ_IS_DIFFERENCE(self));
	g_return_if_fail(!form1 || GQ_IS_INPUT_FORM(form1));

	if(P(self)->form1 == form1) {
		return;
	}

	if(P(self)->form1) {
		g_signal_handlers_disconnect_by_func(P(self)->form1, difference_queue_recalc, self);
		g_object_unref(P(self)->form1);
		P(self)->form1 = NULL;
	}

	if(form1) {
		P(self)->form1 = g_object_ref(form1);
		g_signal_connect_swapped(P(self)->form1, "updated",
					 G_CALLBACK(difference_queue_recalc), self);
	}

	difference_queue_recalc(self);

#warning "FIXME: notify"
	//g_object_notify(G_OBJECT(self), "form1");
}

void
gq_difference_set_form2(GqDifference* self,
			GqInputForm * form2)
{
	g_return_if_fail(GQ_IS_DIFFERENCE(self));
	g_return_if_fail(!form2 || GQ_IS_INPUT_FORM(form2));

	if(P(self)->form2 == form2) {
		return;
	}

	if(P(self)->form2) {
		g_signal_handlers_disconnect_by_func(P(self)->form2, difference_queue_recalc, self);
		g_object_unref(P(self)->form2);
		P(self)->form2 = NULL;
	}

	if(form2) {
		P(self)->form2 = g_object_ref(form2);
		g_signal_connect_swapped(P(self)->form2, "updated",
					 G_CALLBACK(difference_queue_recalc), self);
	}

	// FIXME: add timeout

#warning "FIXME: notify"
	//g_object_notify(G_OBJECT(self), "form2");
}

/* GType */
G_DEFINE_TYPE(GqDifference, gq_difference, G_TYPE_OBJECT);

static void
gq_difference_init(GqDifference* self G_GNUC_UNUSED)
{}

static void
difference_dispose(GObject* object)
{
	gq_difference_set_form1(GQ_DIFFERENCE(object), NULL);
	gq_difference_set_form2(GQ_DIFFERENCE(object), NULL);

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

static void
difference_finalize(GObject* object)
{
	if(P(object)->idle) {
		g_source_remove(P(object)->idle);
		P(object)->idle = 0;
	}

	G_OBJECT_CLASS(gq_difference_parent_class)->finalize(object);
}

static void
gq_difference_class_init(GqDifferenceClass* self_class)
{
	GObjectClass* object_class = G_OBJECT_CLASS(self_class);

	/* GObjectClass */
	object_class->dispose  = difference_dispose;
	object_class->finalize = difference_finalize;

	difference_signals[SIG_UPDATED] = g_signal_new("updated", GQ_TYPE_DIFFERENCE,
		     				       0, 0, NULL, NULL,
		     				       g_cclosure_marshal_VOID__VOID,
		     				       G_TYPE_NONE, 0);

	/* GqDifferenceClass */
	g_type_class_add_private(self_class, sizeof(struct GqDifferencePrivate));
}

