/* dia-handle-layer.c
 * Copyright (C) 2004  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-handle-layer.h"
#include "dia-canvas-groupable.h"
#include "dia-handle.h"
#include <libgnomecanvas/gnome-canvas-util.h>
#include <libart_lgpl/art_rgb_rgba_affine.h>
#include <math.h>
#include <string.h>
#include "dia-canvas-i18n.h"

//#define DEBUG

#ifdef DEBUG
# define D(msg) G_STMT_START { g_print (G_STRLOC": "); g_print msg; g_print ("\n"); } G_STMT_END
#else
# define D(msg)
#endif

#define DIA_HANDLE_EVENTS (GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK)

#define MAX_HANDLE_GLUE_DIST 10

enum {
	ARG_LAST
};

/* Stuff for handles and drawing of handles: */

/* We have the following criteria:
 * - Moveable on itself
 * - Connectable to other objects
 * - Is connected to another object
 */
enum {
	DIA_HANDLE_LAYER_MOVABLE = 1,
	DIA_HANDLE_LAYER_CONNECTABLE = 2,
	DIA_HANDLE_LAYER_IS_CONNECTED = 3,
	
	DIA_HANDLE_LAYER_FOCUS = 4
};


static guint32 _dia_handle_color[8] = {
	/* Just selected objects: */
	0x8080A0FF, /* 0 dark blue -> not moveable */
	0x80F080FF, /* 1 green -> moveable */
	0x80F080FF, /* 2 green w/ cross -> connectable */
	0xF08080FF, /* 3 red w/ cross -> connected */
	/* If object id focused: */
	0x0000D0FF, /* 4  */
	0x00D000FF, /* 5  */
	0x00D000FF, /* 6  */
	0xD00000FF  /* 7  */
};

static gchar *handle_image[8] = {
	NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};

#define PIXEL(p, c) \
	G_STMT_START { \
		*(((char*) &p) + 0) = DIA_COLOR_RED (c); \
		*(((char*) &p) + 1) = DIA_COLOR_GREEN (c); \
		*(((char*) &p) + 2) = DIA_COLOR_BLUE (c); \
		*(((char*) &p) + 3) = DIA_COLOR_ALPHA (c); \
	} G_STMT_END

static void dia_handle_layer_init	(DiaHandleLayer *handle_layer);
static void dia_handle_layer_class_init (DiaHandleLayerClass  *klass);

/* GnomeCanvasItem signals */
static void dia_handle_layer_update     (GnomeCanvasItem *item,
					 double *affine,
					 ArtSVP *clip_path, int flags);
static void dia_handle_layer_draw       (GnomeCanvasItem *item,
					 GdkDrawable *drawable,
					 int x, int y,
					 int width, int height);
static void dia_handle_layer_render     (GnomeCanvasItem *item,
					 GnomeCanvasBuf *buf);

static GnomeCanvasItemClass *parent_class = NULL;

/*
 * Some global initialization:
 */

/**
 * create_handle:
 * @handle_type: type of handle [0..7].
 *
 * Create a handle of size handle_size * handle_size.
 * The handles are created in RGBA format (4 bytes per pixel).
 *
 * Return value: A new handle
 **/
static gchar*
create_handle (int handle_type)
{
	guint32 *handle;
	gint i = 0; /* index */
	gint bufsize;
	gint handle_size;
	guint32 border;
	guint32 fill;
	guint32 cross;

	handle_size = dia_handle_size ();
	bufsize = handle_size * handle_size;

	//PIXEL (border, 0x00000090);
	PIXEL (border, _dia_handle_color[handle_type] & 0x888888FF);
	PIXEL (fill, _dia_handle_color[handle_type] & 0xFFFFFF90);
	PIXEL (cross, _dia_handle_color[handle_type] & 0x555555FF);

	handle = g_new (guint32, bufsize);
	
	g_assert (handle != NULL);

	/* Top and bottom row: */
	for ( ; i < handle_size; i++) {
		handle[i] = border;
		handle[bufsize - 1 - i] = border;
	}

	/* second row: */
	handle[i++] = border;
	for ( ; i < handle_size + handle_size - 1; i++)
		handle[i] = fill;
	handle[i++] = border;

	/* third row .. n-1 */
	for (; i < bufsize - handle_size; i += handle_size)
		memcpy (&handle[i], &handle[handle_size],
			handle_size * sizeof (guint32));

	/* If the handle can connect, draw a cross: */
	if (handle_type & DIA_HANDLE_LAYER_CONNECTABLE) {
		for (i = 2; i < handle_size - 2; i++) {
			/* \ */
			handle[i * handle_size + i] = cross;
			/* / */
			handle[i * handle_size + handle_size - i - 1] = cross;
		}
	}

	return (gchar*) handle;
}

/**
 * dia_handle_layer_create_images:
 *
 * Create images for the handles. This function should be called once from
 * dia_init().
 **/
static void
dia_handle_layer_create_images (void)
{
	int i;

	for (i = 0; i < 8; i++) {
		handle_image[i] = create_handle (i);
	}
}

/**
 * dia_handle_layer_free_images:
 *
 * This function is called via g_atexit().
 **/
static void
dia_handle_layer_free_images (void)
{
	int i;
	
	for (i = 0; i < 8; i++)
		g_free (handle_image[i]);
}


GtkType
dia_handle_layer_get_type (void)
{
	static GtkType handle_layer_type = 0;

	if (!handle_layer_type) {
		static const GtkTypeInfo handle_layer_info =
		{
			"DiaHandleLayer",
			sizeof (DiaHandleLayer),
			sizeof (DiaHandleLayerClass),
			(GtkClassInitFunc) dia_handle_layer_class_init,
			(GtkObjectInitFunc) dia_handle_layer_init,
			/* reserved_1 */ NULL,
			/* reserved_2 */ NULL,
			(GtkClassInitFunc) NULL,
		};
		
		handle_layer_type = gtk_type_unique (gnome_canvas_item_get_type (),
						    &handle_layer_info);
	}

	return handle_layer_type;
}

static void
dia_handle_layer_class_init (DiaHandleLayerClass *klass)
{
	GObjectClass *object_class;
	GnomeCanvasItemClass *item_class;
  
	/* Create an internal list of handles. */
	dia_handle_layer_create_images ();
	g_atexit (dia_handle_layer_free_images);

	object_class = (GObjectClass*) klass;
	item_class = (GnomeCanvasItemClass*) klass;
  
	parent_class = g_type_class_peek_parent (klass);

	item_class->update = dia_handle_layer_update;
	item_class->draw = dia_handle_layer_draw;
	item_class->render = dia_handle_layer_render;
}


static void
dia_handle_layer_init (DiaHandleLayer *hl)
{
}


/* GnomeCanvasItem signals */
static void
dia_handle_layer_update (GnomeCanvasItem *item,
			 double *affine, ArtSVP *clip_path, int flags)
{
	D((" "));

        if (GNOME_CANVAS_ITEM_CLASS (parent_class)->update)
		GNOME_CANVAS_ITEM_CLASS (parent_class)->update (item, affine, clip_path, flags);

	//DIA_HANDLE_LAYER (item)->glue_distance = MAX_HANDLE_GLUE_DIST / affine[0];
	gnome_canvas_request_redraw (item->canvas,
				     G_MININT, G_MININT,
				     G_MAXINT, G_MAXINT);

	item->x1 = item->y1 = G_MININT;
	item->x2 = item->y2 = G_MAXINT;
}

static inline gint
find_handle_color (DiaHandle *handle, DiaCanvasViewItem *item)
{
	gint color;
	
	/* Figure out the handle color. */
	if (handle->connectable) {
		if (handle->connected_to)
			color = DIA_HANDLE_LAYER_IS_CONNECTED;
		else
			color = DIA_HANDLE_LAYER_CONNECTABLE;
	} else if (handle->movable) {
		color = DIA_HANDLE_LAYER_MOVABLE;
	} else
		color = 0;

	/* FixMe: Find out if the item has a child object that has the
	 * focus. The item should highlight if the item has DIA_COMPOSITE.
	 */

	/* Use highlight color if the object is focused: */
	if (DIA_CANVAS_VIEW_ITEM_FOCUS (item)
	    && GTK_WIDGET_HAS_FOCUS(GNOME_CANVAS_ITEM (item)->canvas)) {
		color |= DIA_HANDLE_LAYER_FOCUS;
	}
	return color;
}

typedef struct {
	GdkDrawable *drawable;
	int x, y, width, height;
} DrawHelper;

static gint
real_draw (DiaCanvasViewItem *item, gpointer data)
{
	DrawHelper *buf = data;
	DiaCanvasItem *diaitem = item->item;
	gdouble wx, wy, cx, cy;
	GList *l;
	//gdouble handle_r;
	gint handle_size;
	gint color;

	//D((G_GNUC_PRETTY_FUNCTION));

	/* Groups should draw their children... but invisible items and
	 * unselected leafs should not be drawn. */
	if (!DIA_CANVAS_VIEW_ITEM_SELECT (item)
	    || !DIA_CANVAS_VIEW_ITEM_VISIBLE (item))
		return TRUE;

	handle_size = dia_handle_size ();

	/* First draw all handles: */
	for (l = diaitem->handles; l != NULL; l = l->next) {
		DiaHandle *handle = l->data;

		color = find_handle_color (handle, item);

		dia_handle_get_pos_w (handle, &wx, &wy);
		gnome_canvas_w2c_d (GNOME_CANVAS_ITEM (item)->canvas,
				    wx, wy, &cx, &cy);

		gdk_draw_rgb_32_image (buf->drawable, 
				       item->gc,
				       cx - buf->x + 1, cy - buf->y + 1,
				       handle_size, handle_size,
				       GDK_RGB_DITHER_NORMAL,
				       handle_image[color],
				       handle_size * sizeof (guint32));
	}
	return TRUE;
}

static void
dia_handle_layer_draw (GnomeCanvasItem *hl, GdkDrawable *drawable,
			   int x, int y, int width, int height)
{
	DiaCanvasViewItem *root;
	DrawHelper helper;
	gint handle_r = (dia_handle_size() + 1) / 2;

	D((G_GNUC_PRETTY_FUNCTION));

	root = DIA_CANVAS_VIEW (hl->canvas)->root_item;

	if (!root)
		return;

	helper.drawable = drawable;
	helper.x = x + handle_r;
	helper.y = y + handle_r;
	helper.width = width;
	helper.height = height;

	dia_canvas_view_item_foreach (root, real_draw, &helper);
}

typedef struct {
	GnomeCanvasBuf *buf;
	gdouble *affine;
} RenderHelper;

static gint
real_render (DiaCanvasViewItem *item, gpointer data)
{
	RenderHelper *helper = data;
	GnomeCanvasBuf *buf = helper->buf;
	gdouble affine[6];
	DiaCanvasItem *diaitem = item->item;
	gint i;
	GList *l;
	gdouble handle_r;
	gint handle_size;
	gint color;

	/* Groups should draw their children... but invisible items and
	 * unselected leafs should not be drawn. */
	if (!DIA_CANVAS_VIEW_ITEM_SELECT (item)
	    || !DIA_CANVAS_VIEW_ITEM_VISIBLE (item))
		return TRUE;

	handle_size = dia_handle_size ();
	handle_r = (handle_size) / 2;

	/* First draw all handles: */
	for (l = diaitem->handles, i = 0; (i < item->n_handle_pos * 2) && (l != NULL); l = l->next, i += 2) {
		DiaHandle *handle = l->data;

		color = find_handle_color (handle, item);

		art_affine_translate (affine,
				      item->handle_pos[i] - handle_r,
				      item->handle_pos[i + 1] - handle_r);
		art_affine_multiply (affine, affine, helper->affine);

		art_rgb_rgba_affine (buf->buf,
				     buf->rect.x0, buf->rect.y0,
				     buf->rect.x1, buf->rect.y1,
				     buf->buf_rowstride,
				     handle_image[color],
				     handle_size, handle_size,
				     handle_size * sizeof (guint32),
				     affine, ART_FILTER_NEAREST, NULL);
	}

	return TRUE;
}


/**
 * dia_handle_layer_render:
 * @item: 
 * @buf: 
 *
 * 
 **/
static void
dia_handle_layer_render (GnomeCanvasItem *hl, GnomeCanvasBuf *buf)
{
	RenderHelper helper;
	DiaCanvasViewItem *root;
	gdouble affine[6];

	D((G_GNUC_PRETTY_FUNCTION));

	root = DIA_CANVAS_VIEW (hl->canvas)->root_item;

	if (!root)
		return;
	
	gnome_canvas_buf_ensure_buf (buf);

	gnome_canvas_item_i2w_affine (hl, affine);

	helper.buf = buf;
	helper.affine = affine;

	dia_canvas_view_item_foreach (root, real_render, &helper);
}


/**
 * dia_handle_layer_update_handles:
 * @layer: 
 * @item: 
 *
 * Redraw handles if nessesary. The old handle positions are stored
 * in the #DiaCanvasViewItem to ensure proper redrawing.
 **/
void
dia_handle_layer_update_handles (DiaHandleLayer *layer, DiaCanvasViewItem *item)
{
	DiaCanvasItem *diaitem = item->item;
	int i;
	GList *l;

	/* Create a completely new list of handle positions on the
	 * DiaCanvasViewItem if the lengths of the lists differ. */
	if (item->n_handle_pos != g_list_length (diaitem->handles)) {

		/* Request redraw for the old handle positions: */
		for (i = 0; i < item->n_handle_pos * 2; i += 2) {
			dia_handle_layer_request_redraw (layer,
							 item->handle_pos[i],
							 item->handle_pos[i+1]);
		}

		/* Now reconstruct the new (old) handle_coordinates: */
		item->n_handle_pos = g_list_length (diaitem->handles);
		item->handle_pos = g_realloc (item->handle_pos,
					      item->n_handle_pos
					      * sizeof (gdouble) * 2);
		
		for (i = 0, l = diaitem->handles; l != NULL; i += 2, l = l->next) {
			dia_handle_layer_get_pos_c (layer, DIA_HANDLE (l->data),
						    &item->handle_pos[i],
						    &item->handle_pos[i+1]);

			dia_handle_layer_request_redraw (layer,
							 item->handle_pos[i],
							 item->handle_pos[i+1]);
		}
	} else {
		/* Update the position matrix on the canvasviewitem */
		for (i = 0, l = diaitem->handles; l != NULL; i += 2, l = l->next) {
			dia_handle_layer_request_redraw (layer,
							 item->handle_pos[i],
							 item->handle_pos[i+1]);
			dia_handle_layer_get_pos_c (layer, DIA_HANDLE (l->data),
						    &item->handle_pos[i],
						    &item->handle_pos[i+1]);
			dia_handle_layer_request_redraw (layer,
							 item->handle_pos[i],
							 item->handle_pos[i+1]);
		}
	}
}


/**
 * dia_handle_layer_get_pos_c:
 * @layer: 
 * @handle: 
 * @x: 
 * @y: 
 *
 * Given a handle, return the position of that handle in canvas coordinates.
 **/
void
dia_handle_layer_get_pos_c (DiaHandleLayer *layer, DiaHandle *handle,
			    gint *x, gint *y)
{
	GnomeCanvasItem *gitem = GNOME_CANVAS_ITEM (layer)->canvas->root;
	gdouble wx, wy;
	
	dia_handle_get_pos_w (handle, &wx, &wy);

	gnome_canvas_w2c (gitem->canvas, wx, wy, x, y);
}


/**
 * dia_handle_layer_request_redraw:
 * @layer: 
 * @x: Position of the handle, in canvas coordinates
 * @y: 
 *
 * Ask the canvas to do a redraw of the a handle. Only the handles @x and @y
 * position need to be given (in canvas coordinates).
 **/
void
dia_handle_layer_request_redraw (DiaHandleLayer *layer, gint x, gint y)
{
	ArtDRect r1, r2;
	ArtIRect ir;
	gdouble affine[6];
	gdouble r = (((gdouble) dia_handle_size ()) / 2.0)  + 0.5;

	g_return_if_fail (DIA_IS_HANDLE_LAYER (layer));

	gnome_canvas_item_i2w_affine ((GnomeCanvasItem*) layer, affine);

	r1.x0 = x - r;
	r1.y0 = y - r;
	r1.x1 = x + r;
	r1.y1 = y + r;
	/* x1 = (gint) (x - r);
	y1 = (gint) (y - r);
	x2 = (gint) (x + r);
	y2 = (gint) (y + r); */
	art_drect_affine_transform (&r2, &r1, affine);
	art_drect_to_irect (&ir, &r2);
	gnome_canvas_request_redraw (GNOME_CANVAS_ITEM (layer)->canvas,
				     ir.x0, ir.y0, ir.x1, ir.y1);
				     //x1, y1, x2, y2);
}

/**
 * dia_handle_layer_request_redraw_handle:
 * @layer: 
 * @handle: 
 *
 * Do a redraw for @handle.
 **/
void
dia_handle_layer_request_redraw_handle (DiaHandleLayer *layer,
					DiaHandle *handle)
{
	gint x, y;

	/* TODO: try doing a lookup in the handle's handle_pos list if possible
	 */
	dia_handle_layer_get_pos_c (layer, handle, &x, &y);
	dia_handle_layer_request_redraw (layer, x, y);
}

/**
 * dia_handle_layer_grab_handle:
 * @layer: 
 * @handle: 
 *
 * DEPRICATED
 * 
 * Grab a handle. The handle should be grabbed suring a button-press event,
 * since the handle-layer will grab the widgets events.
 **/
void
dia_handle_layer_grab_handle (DiaHandleLayer *layer,
			      DiaHandle *handle)
{
	g_warning(G_STRLOC": The function dia_handle_layer_grab_handle() is depricated");
}

