/* 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
 */

#include "gq-comparison.h"

#include <glib/gi18n.h>
#include "gq-difference.h"

struct GqComparisonPrivate {
	GqInputForm  * form1;
	GqInputForm  * form2;
	GtkAdjustment* adj1;
	GtkAdjustment* adj2;
	GqDifference * difference;
};
#define P(i) (G_TYPE_INSTANCE_GET_PRIVATE((i), GQ_TYPE_COMPARISON, struct GqComparisonPrivate))

GtkWidget*
gq_comparison_new(GqInputForm* form1,
		  GqInputForm* form2)
{
	return g_object_new(GQ_TYPE_COMPARISON,
			    "form1", form1,
			    "form2", form2,
			    NULL);
}

GqDifference*
gq_comparison_get_difference(GqComparison const* self)
{
	g_return_val_if_fail(GQ_IS_COMPARISON(self), NULL);

	return P(self)->difference;
}

/* GType */
G_DEFINE_TYPE(GqComparison, gq_comparison, GTK_TYPE_DRAWING_AREA);

enum {
	PROP_0,
	PROP_FORM_1,
	PROP_FORM_2
};

static void
gq_comparison_init(GqComparison* self)
{
	P(self)->difference = gq_difference_new();
	g_signal_connect_swapped(P(self)->difference, "updated",
				 G_CALLBACK(gtk_widget_queue_draw), self);
}

static void
comparison_dispose(GObject* object)
{
	if(P(object)->difference) {
		g_signal_handlers_disconnect_by_func(P(object)->difference, gtk_widget_queue_draw, object);
		g_object_unref(P(object)->difference);
		P(object)->difference = NULL;
	}

	if(P(object)->form1) {
		g_object_unref(P(object)->form1);
		P(object)->form1 = NULL;
	}

	if(P(object)->form2) {
		g_object_unref(P(object)->form2);
		P(object)->form2 = NULL;
	}

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

static void
comparison_get_property(GObject   * object,
			guint       prop_id,
			GValue    * value,
			GParamSpec* pspec)
{
	switch(prop_id) {
	case PROP_FORM_1:
		g_value_set_object(value, P(object)->form1);
		break;
	case PROP_FORM_2:
		g_value_set_object(value, P(object)->form2);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
comparison_update_adj1(GqComparison* self)
{
	if(P(self)->adj1) {
		g_signal_handlers_disconnect_by_func(P(self)->adj1, gtk_widget_queue_draw, self);
		g_object_unref(P(self)->adj1);
		P(self)->adj1 = NULL;
	}

	if(P(self)->form1) {
		P(self)->adj1 = g_object_ref(gtk_range_get_adjustment(GTK_RANGE(GTK_SCROLLED_WINDOW(gq_input_form_get_scrolled_window(P(self)->form1))->vscrollbar)));
		g_signal_connect_swapped(P(self)->adj1, "value-changed",
					 G_CALLBACK(gtk_widget_queue_draw), self);
	}
}

static void
comparison_update_adj2(GqComparison* self)
{
	if(P(self)->adj2) {
		g_signal_handlers_disconnect_by_func(P(self)->adj2, gtk_widget_queue_draw, self);
		g_object_unref(P(self)->adj2);
		P(self)->adj2 = NULL;
	}

	if(P(self)->form2) {
		P(self)->adj2 = g_object_ref(gtk_range_get_adjustment(GTK_RANGE(GTK_SCROLLED_WINDOW(gq_input_form_get_scrolled_window(P(self)->form2))->vscrollbar)));
		g_signal_connect_swapped(P(self)->adj2, "value-changed",
					 G_CALLBACK(gtk_widget_queue_draw), self);
	}
}

static void
comparison_set_property(GObject     * object,
			guint         prop_id,
			GValue const* value,
			GParamSpec  * pspec)
{
	switch(prop_id) {
	case PROP_FORM_1:
		if(P(object)->form1) {
			g_signal_handlers_disconnect_by_func(GTK_SCROLLED_WINDOW(gq_input_form_get_scrolled_window(P(object)->form1))->vscrollbar, comparison_update_adj1, object);
			g_object_unref(P(object)->form1);
		}
		P(object)->form1 = (GqInputForm*)g_value_dup_object(value);
		gq_difference_set_form1(P(object)->difference, P(object)->form1);
		if(P(object)->form1) {
			g_signal_connect_swapped(GTK_SCROLLED_WINDOW(gq_input_form_get_scrolled_window(P(object)->form1))->vscrollbar, "notify::adjustment",
						 G_CALLBACK(comparison_update_adj1), object);
		}
		comparison_update_adj1(GQ_COMPARISON(object));
		g_object_notify(object, "form1");
		break;
	case PROP_FORM_2:
		if(P(object)->form2) {
			g_signal_handlers_disconnect_by_func(GTK_SCROLLED_WINDOW(gq_input_form_get_scrolled_window(P(object)->form2))->vscrollbar, comparison_update_adj2, object);
			g_object_unref(P(object)->form2);
		}
		P(object)->form2 = (GqInputForm*)g_value_dup_object(value);
		gq_difference_set_form2(P(object)->difference, P(object)->form2);
		if(P(object)->form2) {
			g_signal_connect_swapped(GTK_SCROLLED_WINDOW(gq_input_form_get_scrolled_window(P(object)->form2))->vscrollbar, "notify::adjustment",
						 G_CALLBACK(comparison_update_adj2), object);
		}
		comparison_update_adj2(GQ_COMPARISON(object));
		g_object_notify(object, "form2");
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
comparison_render_change(GqFormfill* form1,
			 GqFormfill* form2,
			 gboolean    equal,
			 gpointer    user_data)
{
	gpointer* self_and_cr = user_data;
	GqComparison* self = self_and_cr[0];
	cairo_t* cr = self_and_cr[1];
	gdouble offsets[2];

	if(equal) {
		return;
	}

	offsets[0] = gtk_adjustment_get_value(gtk_range_get_adjustment(GTK_RANGE(GTK_SCROLLED_WINDOW(gq_input_form_get_scrolled_window(GQ_INPUT_FORM(P(self)->form1)))->vscrollbar)));
	offsets[1] = gtk_adjustment_get_value(gtk_range_get_adjustment(GTK_RANGE(GTK_SCROLLED_WINDOW(gq_input_form_get_scrolled_window(GQ_INPUT_FORM(P(self)->form1)))->vscrollbar)));

	cairo_set_line_width(cr, 1.0);
	if(form1 && form2) {
		// render difference
		GtkWidget* label = gq_formfill_get_event_box(form1);
		gdouble old = (int)(label->allocation.y - offsets[0] + 0.5*label->allocation.height)+0.5;
		cairo_save(cr);
			cairo_rectangle(cr, 0.5, ((int)(label->allocation.y - offsets[0]))+0.5,
					10.0, label->allocation.height - 1);
			cairo_set_source_rgb(cr, 0.45, 0.62, 0.81);
			cairo_fill_preserve(cr);
			cairo_set_source_rgb(cr, 0.13, 0.29, 0.53);
			cairo_stroke(cr);
		cairo_restore(cr);
		label = gq_formfill_get_event_box(form2);
		cairo_rectangle(cr, 13.5, ((int)(label->allocation.y - offsets[1]))+0.5, 10.0, label->allocation.height - 1);
		cairo_set_source_rgb(cr, 0.45, 0.62, 0.81);
		cairo_fill_preserve(cr);
		cairo_set_source_rgb(cr, 0.13, 0.29, 0.53);
		cairo_stroke(cr);
		cairo_move_to(cr, 5.0, old);
		cairo_line_to(cr, 19.0, (int)(label->allocation.y - offsets[0] + 0.5*label->allocation.height)-0.5);
		cairo_stroke(cr);
	}
	else if(form1) {
		// render only in one
		GtkWidget* label = gq_formfill_get_event_box(form1);
		cairo_rectangle(cr, 0.5, ((int)(label->allocation.y - offsets[0]))+0.5, 10.0, label->allocation.height - 1);
		cairo_set_source_rgb(cr, 0.54, 0.88, 0.2);
		cairo_fill_preserve(cr);
		cairo_set_source_rgb(cr, 0.3, 0.6, 0.02);
		cairo_stroke(cr);
	}
	else if(form2) {
		// render only in two
		GtkWidget* label = gq_formfill_get_event_box(form2);
		cairo_rectangle(cr, 13.5, ((int)(label->allocation.y - offsets[1]))+0.5, 10.0, label->allocation.height - 1);
		cairo_set_source_rgb(cr, 0.54, 0.88, 0.2);
		cairo_fill_preserve(cr);
		cairo_set_source_rgb(cr, 0.3, 0.6, 0.02);
		cairo_stroke(cr);
	}
}

static gboolean
comparison_expose_event(GtkWidget     * widget,
			GdkEventExpose* event  G_GNUC_UNUSED)
{
	gpointer self_and_cr[2];
	cairo_t* cr = gdk_cairo_create(widget->window);
#if 0
	GtkWidget* reference = widget;
	/* this area matches the whole widget */
	cairo_rectangle(cr, 0, 0,
			reference->allocation.width,
			reference->allocation.height);
#elsif 0
	GtkWidget* reference = P(widget)->form1->scwin;
	/* this area matches the scrolled window */
	cairo_rectangle(cr,
			0, reference->allocation.y - widget->allocation.y,
			widget->allocation.width, reference->allocation.height);
#else
	GtkWidget* reference = gtk_bin_get_child(GTK_BIN(gq_input_form_get_scrolled_window(GQ_INPUT_FORM(P(widget)->form1))));
	//cairo_rectangle(cr, 0, reference->allocation.y - widget->allocation.y,
	//		widget->allocation.width, reference->allocation.height);
#endif
#if 0
	cairo_set_source_rgb(cr, 0.68, 0.50, 0.66);
	cairo_fill(cr);
#endif
	cairo_translate(cr, 0.0, reference->allocation.y - widget->allocation.y);
	self_and_cr[0] = widget;
	self_and_cr[1] = cr;
	gq_difference_foreach(P(widget)->difference, comparison_render_change, self_and_cr);

	cairo_destroy(cr);
	return TRUE;
}

static void
comparison_size_request(GtkWidget     * self G_GNUC_UNUSED,
			GtkRequisition* requisition)
{
	requisition->width = 24;
}

static void
gq_comparison_class_init(GqComparisonClass* self_class G_GNUC_UNUSED)
{
	GObjectClass*   object_class = G_OBJECT_CLASS(self_class);
	GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(self_class);

	/* GObjectClass */
	object_class->dispose      = comparison_dispose;
	object_class->get_property = comparison_get_property;
	object_class->set_property = comparison_set_property;

	g_object_class_install_property(object_class, PROP_FORM_1,
					g_param_spec_object("form1",
							    _("Form 1"),
							    _("The first form to be compared by this item"),
							    GQ_TYPE_INPUT_FORM, G_PARAM_READWRITE));
	g_object_class_install_property(object_class, PROP_FORM_2,
					g_param_spec_object("form2",
							    _("Form 2"),
							    _("The second form to be compared by this item"),
							    GQ_TYPE_INPUT_FORM, G_PARAM_READWRITE));

	/* GtkWidgetClass */
	widget_class->expose_event = comparison_expose_event;
	widget_class->size_request = comparison_size_request;

	/* GqComparisonClass */
	g_type_class_add_private(self_class, sizeof(struct GqComparisonPrivate));
}

