/* dia-canvas-group.c
 * Copyright (C) 2000-2003  James Henstridge, Arjan Molenaar
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * 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 "dia-canvas-group.h"
#include "dia-handle.h"
#include "diamarshal.h"
#include <libart_lgpl/art_affine.h>
#include "dia-canvas-i18n.h"

static void dia_canvas_group_init	(DiaCanvasGroup	*canvas_group);
static void dia_canvas_group_class_init	(DiaCanvasGroupClass *klass);
static void dia_canvas_group_groupable_init (DiaCanvasGroupableIface *groupable);
static void dia_canvas_group_dispose	(GObject *object);
static void dia_canvas_group_update  (DiaCanvasItem *item, gdouble affine[6]);
static gdouble dia_canvas_group_point (DiaCanvasItem *item, gdouble x, gdouble y);

/* Interface functions: */
static void dia_canvas_group_groupable_add		(DiaCanvasGroupable *group,
							 DiaCanvasItem *item);
static void dia_canvas_group_groupable_remove		(DiaCanvasGroupable *group,
							 DiaCanvasItem *item);
static gboolean dia_canvas_group_groupable_get_iter	(DiaCanvasGroupable *group,
							 DiaCanvasIter *iter);
static gboolean dia_canvas_group_groupable_next		(DiaCanvasGroupable *group,
							 DiaCanvasIter *iter);
static DiaCanvasItem* dia_canvas_group_groupable_value	(DiaCanvasGroupable *group,
							 DiaCanvasIter *iter);
static gint dia_canvas_group_groupable_length		(DiaCanvasGroupable *group);
static gint dia_canvas_group_groupable_pos		(DiaCanvasGroupable *group,
							 DiaCanvasItem *item);

static DiaCanvasItemClass *parent_class = NULL;

/* Quark for the old item's index. */
static GQuark q_z_order = 0;

GType
dia_canvas_group_get_type (void)
{
	static GType object_type = 0;

	if (!object_type) {
		static const GTypeInfo object_info = {
			sizeof (DiaCanvasGroupClass),
			(GBaseInitFunc) NULL,
			(GBaseFinalizeFunc) NULL,
			(GClassInitFunc) dia_canvas_group_class_init,
			(GClassFinalizeFunc) NULL,
			(gconstpointer) NULL, /* class_data */
			sizeof (DiaCanvasGroup),
			(guint16) 0, /* n_preallocs */
			(GInstanceInitFunc) dia_canvas_group_init,
		};
		static const GInterfaceInfo groupable_info = {
			(GInterfaceInitFunc) dia_canvas_group_groupable_init,
			NULL,
			NULL,
		};
		object_type = g_type_register_static (DIA_TYPE_CANVAS_ITEM,
						      "DiaCanvasGroup",
						      &object_info, 0);
		g_type_add_interface_static (object_type,
					     DIA_TYPE_CANVAS_GROUPABLE,
					     &groupable_info);

		q_z_order = g_quark_from_static_string ("DiaCanvasGroup::z_order");
	}

	return object_type;
}

static void
dia_canvas_group_class_init (DiaCanvasGroupClass *klass)
{
	GObjectClass *object_class;
	DiaCanvasItemClass *item_class;
 
	object_class = (GObjectClass*) klass;
	item_class = DIA_CANVAS_ITEM_CLASS(klass);

	parent_class = g_type_class_peek_parent (klass);

	object_class->dispose = dia_canvas_group_dispose;

	item_class->update = dia_canvas_group_update;
	item_class->point = dia_canvas_group_point;
}

static void
dia_canvas_group_groupable_init (DiaCanvasGroupableIface *groupable)
{
	groupable->add = dia_canvas_group_groupable_add;
	groupable->remove = dia_canvas_group_groupable_remove;
	groupable->get_iter = dia_canvas_group_groupable_get_iter;
	groupable->next = dia_canvas_group_groupable_next;
	groupable->value = dia_canvas_group_groupable_value;
	groupable->length = dia_canvas_group_groupable_length;
	groupable->pos = dia_canvas_group_groupable_pos;
}

static void
dia_canvas_group_init (DiaCanvasGroup *group)
{
  group->children = NULL;
  DIA_UNSET_FLAGS ((DiaCanvasItem*)group, DIA_INTERACTIVE);
}

static void
dia_canvas_group_dispose (GObject *object)
{
	DiaCanvasGroup *group = (DiaCanvasGroup*) object;

	/* unref all children */
	while (group->children)
		dia_canvas_groupable_remove (DIA_CANVAS_GROUPABLE (group),
					     group->children->data);

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

static void
dia_canvas_group_update (DiaCanvasItem *item, gdouble affine[6])
{
	DiaCanvasGroup *group;
	DiaRectangle new_bounds = { 0.0, 0.0, 0.0, 0.0 };
	gboolean set = FALSE;
	static guint update_signal_id = 0;
	GList *tmp;
	
	g_return_if_fail(item != NULL);
	g_return_if_fail(DIA_IS_CANVAS_GROUP(item));
	
	//g_message (__FUNCTION__);

	parent_class->update (item, affine);

	group = DIA_CANVAS_GROUP(item);
	
	if (!update_signal_id)
		update_signal_id = g_signal_lookup("update",
						   DIA_TYPE_CANVAS_ITEM);
	
	for (tmp = group->children; tmp != NULL; tmp = tmp->next) {
		DiaCanvasItem *child = tmp->data;
		
		dia_canvas_item_update_child (item, child, affine);
		
		if (DIA_CANVAS_ITEM_VISIBLE (child)) {
			gdouble x1, x2, y1, y2;

			dia_canvas_item_bb_affine (child, child->affine,
						   &x1, &y1, &x2, &y2);

			if (!set) {
				new_bounds.left = x1;
				new_bounds.top = y1;
				new_bounds.right = x2;
				new_bounds.bottom = y2;
				set = TRUE;
			} else {
				new_bounds.left = MIN(x1, new_bounds.left);
				new_bounds.top = MIN(y1, new_bounds.top);
				new_bounds.right = MAX(x2, new_bounds.right);
				new_bounds.bottom = MAX(y2, new_bounds.bottom);
			}
			//g_message (__FUNCTION__": item: (%f, %f) (%f, %f)",
			//	x1, y1, x2, y2);
			//g_message (__FUNCTION__": bond: (%f, %f) (%f, %f)",
			//	new_bounds.left, new_bounds.top,
			//	new_bounds.right, new_bounds.bottom);
			set = TRUE;
		}
	}
	if (!set) {
		item->bounds.left = item->bounds.top =
		item->bounds.right = item->bounds.bottom = 0.0;
	} else {
		item->bounds = new_bounds;
	}
	//g_message (__FUNCTION__": (%f, %f) (%f, %f)", item->bounds.left,
	//		item->bounds.top, item->bounds.right, item->bounds.bottom);
	//g_message (__FUNCTION__": bond: (%f, %f) (%f, %f)",
	//	new_bounds.left, new_bounds.top,
	//	new_bounds.right, new_bounds.bottom);
}

static gdouble
dia_canvas_group_point (DiaCanvasItem *item, gdouble x, gdouble y)
{
	if ((x > item->bounds.left) && (x < item->bounds.right)
	    && (y > item->bounds.top) && (y < item->bounds.bottom))
		return 0.0;
	else
		return G_MAXDOUBLE;
}

static void
dia_canvas_group_groupable_add (DiaCanvasGroupable *group, DiaCanvasItem *item)
{
	g_assert (DIA_IS_CANVAS_ITEM (item));

	DIA_CANVAS_GROUP (group)->children = g_list_append (DIA_CANVAS_GROUP (group)->children, item);
	g_object_ref (item);
	dia_canvas_item_set_child_of (item, DIA_CANVAS_ITEM (group));

	/* TODO: Remove this as soon as we can set properties on interfaces. */
	if (DIA_CANVAS_ITEM (group)->canvas
	    && DIA_CANVAS_ITEM (group)->canvas->in_undo) {
		guint index = GPOINTER_TO_SIZE(g_object_get_qdata (G_OBJECT (item),
					 q_z_order));
		if (index > 0) {
			guint length = g_list_length (DIA_CANVAS_GROUP (group)->children);
			g_object_set_qdata (G_OBJECT (item), q_z_order, NULL);
			dia_canvas_group_lower_item (DIA_CANVAS_GROUP (group), item, length - index);
		}
	}

	g_object_set_qdata (G_OBJECT (item), q_z_order, NULL);
}

static void
dia_canvas_group_groupable_remove (DiaCanvasGroupable *group, DiaCanvasItem *item)
{
	guint index;

	/* Save the current position so the removal can be undone. */
	index = g_list_index (DIA_CANVAS_GROUP (group)->children, item) + 1;
	g_object_set_qdata (G_OBJECT (item), q_z_order,
			    GSIZE_TO_POINTER(index));

	//dia_canvas_item_preserve_property (item, "z_order");

	DIA_CANVAS_GROUP (group)->children = g_list_remove (DIA_CANVAS_GROUP (group)->children, item);
	g_object_unref (item);
	dia_canvas_item_set_child_of (item, NULL);
}

static gboolean
dia_canvas_group_groupable_get_iter (DiaCanvasGroupable *group, DiaCanvasIter *iter)
{
	g_return_val_if_fail (DIA_IS_CANVAS_GROUP (group),  FALSE);

	iter->data[0] = DIA_CANVAS_GROUP (group)->children;

	return iter->data[0] ? TRUE : FALSE;
}

static gboolean
dia_canvas_group_groupable_next	 (DiaCanvasGroupable *group, DiaCanvasIter *iter)
{
	g_return_val_if_fail (DIA_IS_CANVAS_GROUP (group),  FALSE);

	iter->data[0] = g_list_next (iter->data[0]);

	return iter->data[0] ? TRUE : FALSE;
}

static DiaCanvasItem*
dia_canvas_group_groupable_value (DiaCanvasGroupable *group, DiaCanvasIter *iter)
{
	g_return_val_if_fail (DIA_IS_CANVAS_GROUP (group),  NULL);

	return iter->data[0] ? ((GList*) iter->data[0])->data : NULL;
}

static gint
dia_canvas_group_groupable_length (DiaCanvasGroupable *group)
{
	g_return_val_if_fail (DIA_IS_CANVAS_GROUP (group),  -1);

	return g_list_length (DIA_CANVAS_GROUP (group)->children);
}

static gint
dia_canvas_group_groupable_pos (DiaCanvasGroupable *group, DiaCanvasItem *item)
{
	g_return_val_if_fail (DIA_IS_CANVAS_GROUP (group),  -1);

	return g_list_index (DIA_CANVAS_GROUP (group)->children, item);
}

/*
 * Public functions
 */

/**
 * dia_canvas_group_create_item:
 * @group: 
 * @type: 
 * @first_arg_name: 
 * @...: 
 *
 * Create a new #DiaCanvasItem of @type as a child of the @group.
 * You can add properties that have to be set on the newly created item. The
 * last value should be %NULL. Note that the property 'parent' has no effect.
 *
 * Return value: The newly created canvas item. Note that this is a borrowed
 * 	reference, you should reference it yourself if you want to keep a
 * 	reference to the newly created item.
 **/
DiaCanvasItem*
dia_canvas_group_create_item (DiaCanvasGroup *group, GType type,
			      const gchar* first_arg_name, ...)
{
	DiaCanvasItem *item = NULL;
	gboolean old_allow_undo = FALSE;
	va_list va_args;

	g_return_val_if_fail (DIA_IS_CANVAS_GROUP (group), NULL);
	g_return_val_if_fail (g_type_is_a (type, DIA_TYPE_CANVAS_ITEM), NULL);

	if (DIA_CANVAS_ITEM (group)->canvas) {
		old_allow_undo = DIA_CANVAS_ITEM (group)->canvas->allow_undo;
		DIA_CANVAS_ITEM (group)->canvas->allow_undo = FALSE;
	}

	if (first_arg_name) {
		va_start (va_args, first_arg_name);
		item = (DiaCanvasItem*) g_object_new_valist (type,
							     first_arg_name,
							     va_args);
		va_end (va_args);
	} else
		item = g_object_new (type, NULL);

	if (DIA_CANVAS_ITEM (group)->canvas)
		DIA_CANVAS_ITEM (group)->canvas->allow_undo = old_allow_undo;

	if (!item->parent)
		dia_canvas_groupable_add (DIA_CANVAS_GROUPABLE (group), item);

	/* We unref the item since the 'add' method did ref it. If it was
	 * not ref'ed we destroy it this way... */
	if (G_OBJECT (item)->ref_count == 1) {
		g_object_unref (item);
		return NULL;
	}

	g_object_unref (item);
	return item;
}

static void
z_order (DiaCanvasGroup *group, DiaCanvasItem *item, gint pos)
{
	guint old_pos;
	
	/* item->parent may be NULL, since it is set after the "add" signal */
	g_assert (g_list_index (group->children, item) >= 0);

	if (pos == 0)
		return;

	old_pos = g_list_index (group->children, item);

	group->children = g_list_remove (group->children, item);
	group->children = g_list_insert (group->children, item, old_pos + pos);

	g_signal_emit_by_name (G_OBJECT (item), "z_order", pos);
}

/**
 * dia_canvas_group_raise_item:
 * @group:
 * @item: 
 * @pos: 
 *
 * Bring an object @pos positions to the front.
 **/
void
dia_canvas_group_raise_item (DiaCanvasGroup *group,
			     DiaCanvasItem *item, gint pos)
{
	g_return_if_fail (DIA_IS_CANVAS_GROUP (group));
	g_return_if_fail (DIA_IS_CANVAS_ITEM (item));
	/* In case of an undo action, item->parent may be NULL */
	//g_return_if_fail (item->parent == (DiaCanvasItem*) group);
	g_return_if_fail (g_list_index (group->children, item) >= 0);
	g_return_if_fail (pos >= 0);

	z_order (group, item, pos);
}

/**
 * dia_canvas_group_lower_item:
 * @group:
 * @item: 
 * @pos: 
 *
 * Bring @item @pos position to the back.
 **/
void
dia_canvas_group_lower_item (DiaCanvasGroup *group,
			     DiaCanvasItem *item, gint pos)
{
	g_return_if_fail (DIA_IS_CANVAS_GROUP (group));
	g_return_if_fail (DIA_IS_CANVAS_ITEM (item));
	/* In case of an undo action, item->parent may be NULL */
	//g_return_if_fail (item->parent == (DiaCanvasItem*) group);
	g_return_if_fail (g_list_index (group->children, item) >= 0);
	g_return_if_fail (pos >= 0);

	z_order (group, item, -pos);
}

/**
 * dia_canvas_group_foreach:
 * @item: 
 * @func: 
 * @data: 
 *
 * Call the function @func for every child of @item. You can pass extra data
 * via the @data value.
 *
 * Return value: 
 **/
gint
dia_canvas_group_foreach (DiaCanvasItem *item,
                          DiaCanvasItemForeachFunc func, gpointer data)
{
        GList *l;
        gint result = FALSE;

        g_return_val_if_fail (DIA_IS_CANVAS_ITEM (item), FALSE);
        g_return_val_if_fail (func != NULL, FALSE);

        if (func (item, data))
                return TRUE;

	if (!DIA_IS_CANVAS_GROUP (item))
		return FALSE;

        for (l = DIA_CANVAS_GROUP (item)->children; l != NULL; l = l->next)
		result |= dia_canvas_group_foreach ((DiaCanvasItem*) l->data,
						    func, data);

	return result;
}

