/*
 * DiaCanvasElement
 *
 * This is LGPL'ed code.
 */

#include "dia-canvas-element.h"
#include "dia-handle.h"
#include "dia-shape.h"
#include <libart_lgpl/art_affine.h>
#include <math.h>
#include "dia-canvas-i18n.h"

enum {
	PROP_WIDTH = 1,
	PROP_HEIGHT,
	PROP_MIN_WIDTH,
	PROP_MIN_HEIGHT
};

static void dia_canvas_element_class_init	(DiaCanvasElementClass *klass);
static void dia_canvas_element_init		(DiaCanvasElement *item);
static void dia_canvas_element_set_property	(GObject *object,
						 guint property_id,
						 const GValue *value,
						 GParamSpec *pspec);
static void dia_canvas_element_get_property	(GObject *object,
						 guint property_id,
						 GValue *value,
						 GParamSpec *pspec);
static void dia_canvas_element_update		(DiaCanvasItem *item,
						 gdouble affine[6]);
static gdouble dia_canvas_element_point		(DiaCanvasItem *item,
						 gdouble x, gdouble y);
static void dia_canvas_element_handle_motion	(DiaCanvasItem *item,
						 DiaHandle *handle,
						 gdouble *wx, gdouble *wy,
						 DiaEventMask mask);
static gdouble dia_canvas_element_glue		(DiaCanvasItem *item,
						 DiaHandle *handle,
						 gdouble *x, gdouble *y);
static gboolean dia_canvas_element_connect	(DiaCanvasItem *item,
						 DiaHandle *handle);
static gboolean dia_canvas_element_disconnect	(DiaCanvasItem *item,
						 DiaHandle *handle);

#define align_handles(item) \
	dia_canvas_element_align_handles ((DiaCanvasElement*) item)

#define GET_HANDLE(item, dir) (DIA_HANDLE (g_list_nth_data (((DiaCanvasItem*) (item))->handles, dir)))
#define GET_HANDLE_DIR(item, handle) (g_list_index (((DiaCanvasItem*) (item))->handles, handle))

static DiaCanvasItemClass *parent_class = NULL;

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

	if (!object_type) {
		static const GTypeInfo object_info = {
			sizeof (DiaCanvasElementClass),
			(GBaseInitFunc) NULL,
			(GBaseFinalizeFunc) NULL,
			(GClassInitFunc) dia_canvas_element_class_init,
			(GClassFinalizeFunc) NULL,
			(gconstpointer) NULL, /* class_data */
			sizeof (DiaCanvasElement),
			(guint16) 0, /* n_preallocs */
			(GInstanceInitFunc) dia_canvas_element_init,
		};

		object_type = g_type_register_static (DIA_TYPE_CANVAS_ITEM,
						      "DiaCanvasElement",
						      &object_info,
						      G_TYPE_FLAG_ABSTRACT);
	}

	return object_type;
}


static void
dia_canvas_element_class_init (DiaCanvasElementClass *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->get_property = dia_canvas_element_get_property;
	object_class->set_property = dia_canvas_element_set_property;
	
	g_object_class_install_property (object_class,
					 PROP_WIDTH,
					 g_param_spec_double ("width",
						 _("Width"),
						 _("Width of the element"),
						 0.0, G_MAXDOUBLE, 1.0,
						 G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
					 PROP_HEIGHT,
					 g_param_spec_double ("height",
						 _("Height"),
						 _("Height of the element"),
						 0.0, G_MAXDOUBLE, 1.0,
						 G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
					 PROP_MIN_WIDTH,
					 g_param_spec_double ("min_width",
						 _("Minimal width"),
						 _("Minimal width of the element"),
						 0.0, G_MAXDOUBLE, 1.0,
						 G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
					 PROP_MIN_HEIGHT,
					 g_param_spec_double ("min_height",
						 _("Minimal height"),
						 _("Minimal height of the element"),
						 0.0, G_MAXDOUBLE, 1.0,
						 G_PARAM_READWRITE));

	item_class->update = dia_canvas_element_update;
	item_class->point = dia_canvas_element_point;
	item_class->handle_motion = dia_canvas_element_handle_motion;
	item_class->glue = dia_canvas_element_glue;
	item_class->connect = dia_canvas_element_connect;
	item_class->disconnect = dia_canvas_element_disconnect;
}


static void
dia_canvas_element_init (DiaCanvasElement *item)
{
	int i;

	item->width = 100.0;
	item->height = 100.0;
	item->min_width = 0.0;
	item->min_height = 0.0;
	for (i = 0; i < 8; i++) {
		g_object_new (DIA_TYPE_HANDLE,
				"owner", item,
				"strength", DIA_STRENGTH_STRONG,
				"movable", TRUE,
				NULL);
	}
	//DIA_SET_FLAGS ((DiaCanvasItem*)item, DIA_NEED_ALIGN_HANDLES);
	align_handles (item);
}


static void
dia_canvas_element_set_property (GObject *object, guint property_id,
				 const GValue *value, GParamSpec *pspec)
{
	DiaCanvasElement *element = (DiaCanvasElement*) (object);
	
	switch (property_id) {
	case PROP_WIDTH:
		dia_canvas_item_preserve_property (&element->item, "width");
		element->width = g_value_get_double (value);
		align_handles (DIA_CANVAS_ITEM (element));
		dia_canvas_item_request_update (DIA_CANVAS_ITEM (element));
		break;
	case PROP_HEIGHT:
		dia_canvas_item_preserve_property (&element->item, "height");
		element->height = g_value_get_double (value);
		align_handles (DIA_CANVAS_ITEM (element));
		dia_canvas_item_request_update (DIA_CANVAS_ITEM (element));
		break;
	case PROP_MIN_WIDTH:
		dia_canvas_item_preserve_property (&element->item, "min_width");
		element->min_width = g_value_get_double (value);
		if (element->width < element->min_width) {
			dia_canvas_item_preserve_property (&element->item,
							   "width");
			element->width = element->min_width;
			align_handles (DIA_CANVAS_ITEM (element));
			dia_canvas_item_request_update (DIA_CANVAS_ITEM (element));
		}
		dia_canvas_item_request_update (DIA_CANVAS_ITEM (element));
		break;
	case PROP_MIN_HEIGHT:
		dia_canvas_item_preserve_property (&element->item, "min_height");
		element->min_height = g_value_get_double (value);
		if (element->height < element->min_height) {
			dia_canvas_item_preserve_property (&element->item,
							   "height");
			element->height = element->min_height;
			align_handles (DIA_CANVAS_ITEM (element));
			dia_canvas_item_request_update (DIA_CANVAS_ITEM (element));
		}
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}

static void
dia_canvas_element_get_property (GObject *object, guint property_id,
				 GValue *value, GParamSpec *pspec)
{
	switch (property_id) {
	case PROP_WIDTH:
		g_value_set_double (value, DIA_CANVAS_ELEMENT (object)->width);
		break;
	case PROP_HEIGHT:
		g_value_set_double (value, DIA_CANVAS_ELEMENT (object)->height);
		break;
	case PROP_MIN_WIDTH:
		g_value_set_double (value, DIA_CANVAS_ELEMENT (object)->min_width);
		break;
	case PROP_MIN_HEIGHT:
		g_value_set_double (value, DIA_CANVAS_ELEMENT (object)->min_height);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}

/**
 * dia_canvas_element_get_opposite_handle:
 * @item: 
 * @handle: 
 *
 * Return the opposite handle of @handle.
 *
 * Return value: opposite handle.
 **/
DiaHandle*
dia_canvas_element_get_opposite_handle (DiaCanvasItem *item,
					DiaHandle *handle)
{
	int opp = -1;

	switch(GET_HANDLE_DIR(item,handle)){
	case DIA_HANDLE_N:
		opp = DIA_HANDLE_S;
		break;
	case DIA_HANDLE_S:
		opp = DIA_HANDLE_N;
		break;
	case DIA_HANDLE_E:
		opp = DIA_HANDLE_W;
		break;
	case DIA_HANDLE_W:
		opp = DIA_HANDLE_E;
		break;
	case DIA_HANDLE_NW:
		opp = DIA_HANDLE_SE;
		break;
	case DIA_HANDLE_SW:
		opp = DIA_HANDLE_NE;
		break;
	case DIA_HANDLE_NE:
		opp = DIA_HANDLE_SW;
		break;
	case DIA_HANDLE_SE:
		opp = DIA_HANDLE_NW;
		break;
	default:
		g_assert_not_reached ();
	}
	return GET_HANDLE (item, opp);
}

void
dia_canvas_element_align_handles (DiaCanvasElement *element)
{
	DiaCanvasItem *item = DIA_CANVAS_ITEM (element);

	//g_message (__FUNCTION__": w %f h %f", el->width, el->height);

	dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_NW), 0.0, 0.0);

	/* Alter the affine transformation matrix so that the NW handle will
	 * be at (0,0).
	 */
	dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_N),
			      element->width/2, 0.0);

	dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_NE),
			      element->width, 0.0);

	dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_E),
			      element->width, element->height/2);

	dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_SE),
			      element->width, element->height);

	dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_S),
			      element->width/2, element->height);

	dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_SW),
			      0.0, element->height);

	dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_W),
			      0.0, element->height/2);
}

static void
dia_canvas_element_update (DiaCanvasItem *item, gdouble affine[6])
{
        if (DIA_CANVAS_ITEM_CLASS (parent_class)->update)
		DIA_CANVAS_ITEM_CLASS (parent_class)->update (item, affine);

	/* if (item->flags & DIA_NEED_ALIGN_HANDLES) {
		align_handles (item);
		DIA_UNSET_FLAGS (item, DIA_NEED_ALIGN_HANDLES);
	} */

	item->bounds.left = 0.0;
	item->bounds.top = 0.0;
	item->bounds.right = DIA_CANVAS_ELEMENT (item)->width;
	item->bounds.bottom = DIA_CANVAS_ELEMENT (item)->height;
}


static gdouble
dia_canvas_element_point (DiaCanvasItem *item, gdouble x, gdouble y)
{
	DiaPoint p;

	p.x = x;
	p.y = y;

	return dia_distance_rectangle_point (&item->bounds, &p);
}


/**
 * dia_canvas_element_handle_motion_move:
 * @item: 
 * @handle: 
 * @wx: 
 * @wy: 
 *
 * Included in dia_canvas_element_handle_motion(). Used to move a handle.
 **/
static void
dia_canvas_element_handle_motion_move (DiaCanvasItem *item, DiaHandle *handle,
				       gdouble *wx, gdouble *wy)
{
	DiaCanvasElement *el = (DiaCanvasElement*) item;
	gdouble ix, iy;
	gdouble nw_dx = 0.0, nw_dy = 0.0;
	gdouble x, y;
	gdouble dx, dy; /* The movement relative to the handle */
	gint dir;

	/* Do not align handles if the handle is not part of the first 8
	 * handles. */
	if (GET_HANDLE_DIR (item, handle) > DIA_HANDLE_SE)
		return;

	g_object_freeze_notify (G_OBJECT (item));

	dia_canvas_preserve_property (item->canvas, (GObject*) item, "width");
	dia_canvas_preserve_property (item->canvas, (GObject*) item, "height");

	dia_handle_get_pos_i (handle, &x, &y);
	dir = GET_HANDLE_DIR (item, handle);

	ix = *wx;
	iy = *wy;
	dia_canvas_item_affine_point_w2i (item, &ix, &iy);

	dx = ix - x;
	dy = iy - y;

	/* If the north or west side is moved, the object should be moved to
	 * keep the origin in the upper right corner. */
	if ((dir == DIA_HANDLE_NW) || (dir == DIA_HANDLE_N)
	    || (dir == DIA_HANDLE_NE)) {
		register gdouble se_y = dia_variable_get_value (GET_HANDLE (item, DIA_HANDLE_SE)->pos_i.y);
		if (iy > se_y)
			dy = se_y - y;
		nw_dy = dy;
		dy = -dy;
	}
	if ((dir == DIA_HANDLE_NW) || (dir == DIA_HANDLE_W)
	    || (dir == DIA_HANDLE_SW)) {
		register gdouble se_x = dia_variable_get_value (GET_HANDLE (item, DIA_HANDLE_SE)->pos_i.x);
		if (ix > se_x)
			dx = se_x - x;
		nw_dx = dx;
		dx = -dx;
	}

	if ((dir != DIA_HANDLE_N) && (dir != DIA_HANDLE_S)) {
		el->width += dx;
		g_object_notify ((GObject*) item, "width");
	}
	if ((dir != DIA_HANDLE_W) && (dir != DIA_HANDLE_E)) {
		el->height += dy;
		g_object_notify ((GObject*) item, "height");
	}

	if (el->width <= el->min_width) {
		if ((dir == DIA_HANDLE_NW) || (dir == DIA_HANDLE_W)
		    || (dir == DIA_HANDLE_SW))
			nw_dx += el->width - el->min_width;
		el->width = el->min_width;
	}
	if (el->height <= el->min_height) {
		if ((dir == DIA_HANDLE_NW) || (dir == DIA_HANDLE_N)
		    || (dir == DIA_HANDLE_NE))
			nw_dy += el->height - el->min_height;
		el->height = el->min_height;
	}
	
	/* Move the entire object if the north or west handle is moved. */
	if ((nw_dx != 0.0) || (nw_dy != 0.0)) {
		gdouble parent_affine[6], a[6];

		dia_canvas_preserve_property (item->canvas, (GObject*) item, "affine");

		dia_canvas_item_affine_w2i (DIA_CANVAS_ITEM (item->parent),
					    parent_affine);
		art_affine_translate (a, nw_dx, nw_dy);

		art_affine_multiply (a, a, parent_affine);
		art_affine_multiply (item->affine, a, item->affine);
		g_object_notify ((GObject*) item, "affine");
	}

	align_handles (item);
	
	dia_handle_get_pos_w (handle, wx, wy);

	g_object_thaw_notify ((GObject*) item);
}

/**
 * dia_canvas_element_handle_motion_rotate:
 * @item: 
 * @handle: 
 * @wx: 
 * @wy: 
 *
 * This function is included in dia_canvas_element_handle_motion(), to
 * provide rotation for the corner-handles.
 **/
static inline void
dia_canvas_element_handle_motion_rotate (DiaCanvasItem *item, DiaHandle *handle,
					 gdouble *wx, gdouble *wy,
					 gboolean pinned)
{
	gdouble x, y;
	gdouble ix,iy;
	gdouble a, b;
	gdouble alpha, angle;

	dia_handle_get_pos_i (handle, &x, &y);
	ix = *wx;
	iy = *wy;
	dia_canvas_item_affine_point_w2i (item, &ix, &iy);

	/* (a, b) is the center of the object. */
	a = item->bounds.left + (item->bounds.right - item->bounds.left) / 2;
	b = item->bounds.top + (item->bounds.bottom - item->bounds.top) / 2;

	/* if (pinned) {
		gdouble ta = a, tb = b;
		a = item->affine[0] * ta + item->affine[2] * tb;
		b = item->affine[1] * ta + item->affine[3] * tb;
	} */

	alpha = atan2 ((a - ix), (b - iy));

	if (pinned) {
		alpha = (((int) (alpha / M_PI * 180.0)) / 5) * 5;
		//g_message (__FUNCTION__": alpha = %f", alpha);
		alpha = alpha * M_PI / 180.0;
	}

	angle = (atan2 ((a - x), (b - y)) - alpha) / M_PI * 180.0;

	dia_canvas_item_rotate (item, angle);

	align_handles (item);
	
	dia_handle_get_pos_w (handle, wx, wy);
}

/**
 * dia_canvas_element_handle_motion_shear:
 * @item: 
 * @handle: 
 * @wx: 
 * @wy: 
 *
 * Included in dia_canvas_element_handle_motion(). Used to shear the North,
 * South, east and West handles (when CTRL is pressed).
 **/
static inline void
dia_canvas_element_handle_motion_shear (DiaCanvasItem *item, DiaHandle *handle,
					gdouble *wx, gdouble *wy)
{
	gdouble x, y;
	gdouble ix, iy;
	//gdouble tx, ty;
	gdouble affine[6];

	dia_handle_get_pos_i (handle, &x, &y);
	//dia_handle_get_pos_w (handle, &ix, &iy);
	dia_canvas_item_affine_w2i (item, affine);

	ix = *wx;
	iy = *wy;
	dia_canvas_item_affine_point_w2i (item, &ix, &iy);

	/* We need to ignore the x or y variable of the cursor position
	 * (*wx, *wy). Give it the handle's position. */
	switch (GET_HANDLE_DIR (item, handle)) {
	case DIA_HANDLE_N:
		dia_canvas_item_shear_x (item, x - ix, y - iy);
		break;
	case DIA_HANDLE_S:
		dia_canvas_item_shear_x (item, ix - x, iy - y);
		break;
	case DIA_HANDLE_E:
		dia_canvas_item_shear_y (item, ix - x, iy - y);
		break;
	case DIA_HANDLE_W:
		dia_canvas_item_shear_y (item, x - ix, y - iy);
		break;
	default:
		g_assert_not_reached ();
	}
	align_handles (item);
	
	/* Force an i->w update */
	dia_handle_get_pos_w (handle, wx, wy);
}

/**
 * dia_canvas_element_handle_motion:
 * @item: 
 * @handle: 
 * @wx: 
 * @wy: 
 * @mask: 
 *
 * Move a handle, inlines dia_canvas_element_handle_motion_move(),
 * dia_canvas_element_handle_motion_rotate() and
 * dia_canvas_element_handle_motion_shear().
 **/
static void
dia_canvas_element_handle_motion (DiaCanvasItem *item, DiaHandle *handle,
			          gdouble *wx, gdouble *wy, DiaEventMask mask)
{
	if (mask & DIA_EVENT_MASK_CTRL) {
		guint dir = GET_HANDLE_DIR (item, handle);
		switch (dir) {
		case DIA_HANDLE_NW:
		case DIA_HANDLE_NE:
		case DIA_HANDLE_SE:
		case DIA_HANDLE_SW:
			dia_canvas_element_handle_motion_rotate (item, handle,
					 wx, wy, mask & DIA_EVENT_MASK_SHIFT);
			break;
		case DIA_HANDLE_N:
		case DIA_HANDLE_S:
		case DIA_HANDLE_E:
		case DIA_HANDLE_W:
			dia_canvas_element_handle_motion_shear (item, handle,
								wx, wy);
			break;
		default:
			g_assert_not_reached ();
		}
	} else if (mask & DIA_EVENT_MASK_SHIFT) {
		gdouble hx, hy, ox, oy;
		DiaHandle *opphandle = dia_canvas_element_get_opposite_handle (item, handle);
		dia_handle_get_pos_w (handle, &hx, &hy);
		dia_handle_get_pos_w (opphandle, &ox, &oy);
		ox -= *wx - hx;
		oy -= *wy - hy;
		g_object_freeze_notify ((GObject*) item);
		dia_canvas_element_handle_motion_move (item, handle, wx, wy);
		dia_canvas_element_handle_motion_move (item, opphandle, &ox, &oy);
		g_object_thaw_notify ((GObject*) item);
	} else {
		dia_canvas_element_handle_motion_move (item, handle, wx, wy);
	}

	dia_canvas_item_request_update (item);
}

/**
 * calc_glue_point:
 * @item: 
 * @handle: 
 * @p: in item coordinates!
 * @segment: First segment, clockwise.
 *
 *
 *
 * Return value: 
 **/
static gdouble
calc_glue_point (DiaCanvasItem *item, DiaHandle *handle,
		 DiaPoint *p, gint *segment)
{
	DiaPoint ul, ur, ll, lr;
	DiaPoint pol1, pol2, pol3, pol4;
	gdouble min, d1, d2, d3, d4;

	dia_handle_get_pos_i (GET_HANDLE (item, DIA_HANDLE_NW), &ul.x, &ul.y);
	dia_handle_get_pos_i (GET_HANDLE (item, DIA_HANDLE_SE), &lr.x, &lr.y);
	ur.x = lr.x;
	ur.y = ul.y;
	ll.x = ul.x;
	ll.y = lr.y;

	d1 = dia_distance_line_point (&ul, &ur, p, 0.0, DIA_CAP_BUTT, &pol1);
	d2 = dia_distance_line_point (&ur, &lr, p, 0.0, DIA_CAP_BUTT, &pol2);
	d3 = dia_distance_line_point (&ll, &lr, p, 0.0, DIA_CAP_BUTT, &pol3);
	d4 = dia_distance_line_point (&ul, &ll, p, 0.0, DIA_CAP_BUTT, &pol4);

	min = MIN (MIN(d1, d2), MIN(d3, d4));

	//art_affine_invert (inv, a);

	if (min == d1) {
		if (p) *p = pol1;
		if (segment) *segment = DIA_HANDLE_NW;
		return d1;
	} else if (min == d2) {
		if (p) *p = pol2;
		if (segment) *segment = DIA_HANDLE_NE;
		return d2;
	} else if (min == d3) {
		if (p) *p = pol3;
		if (segment) *segment = DIA_HANDLE_SE;
		return d3;
	} else {
		if (p) *p = pol4;
		if (segment) *segment = DIA_HANDLE_SW;
		return d4;
	}
}

static gdouble
dia_canvas_element_glue (DiaCanvasItem *item, DiaHandle *handle,
			 gdouble *x, gdouble *y)
{
	DiaPoint p;
	gdouble a[6], inv[6], dist;

	dia_canvas_item_affine_w2i (item, a);
	p.x = *x * a[0] + *y * a[2] + a[4];
	p.y = *x * a[1] + *y * a[3] + a[5];

	dist = calc_glue_point (item, handle, &p, NULL);

	art_affine_invert (inv, a);

	*x = p.x * inv[0] + p.y * inv[2] + inv[4];
	*y = p.x * inv[1] + p.y * inv[3] + inv[5];

	return dist;
}

static gboolean
dia_canvas_element_connect (DiaCanvasItem *item, DiaHandle *handle)
{
	//DiaCanvasElement *el = (DiaCanvasElement*) item;
	DiaHandle *hbegin, *hend = NULL;
	gint segment;
	DiaPoint p;

	if (!parent_class->connect (item, handle))
		return FALSE;
	
	dia_handle_get_pos_w (handle, &p.x, &p.y);
	dia_canvas_item_affine_point_w2i (item, &p.x, &p.y);

	calc_glue_point (item, handle, &p, &segment);

	dia_canvas_item_affine_point_i2w (item, &p.x, &p.y);
	dia_handle_set_pos_w (handle, p.x, p.y);

	hbegin = GET_HANDLE(item, segment);
	switch (segment) {
	case DIA_HANDLE_NW:
		hend = GET_HANDLE (item, DIA_HANDLE_NE);
		break;
	case DIA_HANDLE_NE:
		hend = GET_HANDLE (item, DIA_HANDLE_SE);
		break;
	case DIA_HANDLE_SE:
		hend = GET_HANDLE (item, DIA_HANDLE_SW);
		break;
	case DIA_HANDLE_SW:
		hend = GET_HANDLE (item, DIA_HANDLE_NW);
		break;
	default:
		g_assert_not_reached ();
	}

	g_assert (hbegin != NULL);
	g_assert (hend != NULL);

	dia_handle_add_line_constraint (hbegin, hend, handle);

	return TRUE;
}

static gboolean
dia_canvas_element_disconnect (DiaCanvasItem *item, DiaHandle *handle)
{
	return parent_class->disconnect (item, handle);
}
