#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include "guiutils.h"
#include "imgview.h"
#include "imgviewcrop.h"
#include "config.h"

#include "images/icon_cut_20x20.xpm"
#include "images/icon_cut_32x32.xpm"
#include "images/icon_cancel_20x20.xpm"


/* Callbacks */
static gint ImgViewCropDlgDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static void ImgViewCropDlgCropCB(GtkWidget *widget, gpointer data);
static void ImgViewCropDlgCancelCB(GtkWidget *widget, gpointer data);

/* Crop */
static void ImgViewCropDlgCrop(imgview_struct *iv, GdkRectangle *rect);

/* Public */
imgview_cropdlg_struct *ImgViewCropDlgNew(imgview_struct *iv);
void ImgViewCropDlgMapValues(
	imgview_cropdlg_struct *cd,
	gint x0, gint x1,	/* Crop Bounds */
	gint y0, gint y1,
	gint img_width,		/* Image Size (not Crop Rect) */
	gint img_height
);
gboolean ImgViewCropDlgIsMapped(imgview_cropdlg_struct *cd);
void ImgViewCropDlgMap(imgview_cropdlg_struct *cd);
void ImgViewCropDlgUnmap(imgview_cropdlg_struct *cd);
void ImgViewCropDlgDelete(imgview_cropdlg_struct *cd);


#define MAX(a,b)	(((a) > (b)) ? (a) : (b))
#define MIN(a,b)	(((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)	(MIN(MAX((a),(l)),(h)))
#define ABSOLUTE(x)	(((x) < 0) ? ((x) * -1) : (x))


#define IMGVIEW_CROPDLG_TITLE		"Crop Selection"


/*
 *	Crop Dialog toplevel GtkWindow "delete_event" signal callback.
 */
static gint ImgViewCropDlgDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	ImgViewCropDlgCancelCB(widget, data);
	return(TRUE);
}

/*
 *	Crop Dialog crop callback.
 */
static void ImgViewCropDlgCropCB(GtkWidget *widget, gpointer data)
{
	imgview_struct *iv;
	imgview_cropdlg_struct *cd = IMGVIEW_CROPDLG(data);
	if(cd == NULL)
	    return;

	iv = cd->imgview;
	if(iv == NULL)
	    return;

	/* Reset crop marks on image viewer by marking its crop as
	 * undefined
	 */
	iv->crop_flags &= ~IMGVIEW_CROP_DEFINED;
	iv->crop_flags &= ~IMGVIEW_CROP_FINALIZED;

	/* Perform crop procedure, this will cause the image viewer to
	 * load a new cropped image and redraw itself
	 */
	ImgViewCropDlgCrop(iv, &cd->rect);

	/* Unmap crop dialog */
	ImgViewCropDlgUnmap(cd);
}

/*
 *	Cancel callback.
 */
static void ImgViewCropDlgCancelCB(GtkWidget *widget, gpointer data)
{
	imgview_struct *iv;
	imgview_cropdlg_struct *cd = IMGVIEW_CROPDLG(data);
	if(cd == NULL)
	    return;

	iv = cd->imgview;
	if(iv == NULL)
	    return;

	/* Reset crop marks on image viewer by marking its crop as
	 * undefined
	 */
	iv->crop_flags &= ~IMGVIEW_CROP_DEFINED;
	iv->crop_flags &= ~IMGVIEW_CROP_FINALIZED;

	/* Redraw the image viewer and update its menus */
	ImgViewQueueDrawView(iv);
	ImgViewUpdateMenus(iv);

	/* Unmap crop dialog */
	ImgViewCropDlgUnmap(cd);
}

/*
 *	Crops the image on the Crop Dialog's ImgView to the geometry
 *	specified by rect.
 */
static void ImgViewCropDlgCrop(imgview_struct *iv, GdkRectangle *rect)
{
	gboolean was_playing;
	gint x, y, bc, bpp;
	gint swidth, sheight, sbpl;
	const guint8 *src, *src_line, *src_ptr;
	gint tx, ty, twidth, theight, tbpl;
	guint8 *tar, *tar_line, *tar_ptr;
	GList *glist;
	imgview_frame_struct *src_frame;
	imgview_image_struct *src_img, *tar_img;

	if((iv == NULL) || (rect == NULL))
	    return;

	was_playing = ImgViewIsPlaying(iv);

	/* Get source image */
	src_img = ImgViewGetImage(iv);
	if((src_img == NULL) || (rect->width <= 0) || (rect->height <= 0))
	    return;

	/* Get source image geometry and make sure that the values
	 * are valid
	 */
	swidth = src_img->width;
	sheight = src_img->height;
	bpp = src_img->bpp;		/* Source and target use same bpp */
	sbpl = src_img->bpl;
	if((swidth <= 0) || (sheight <= 0) || (bpp <= 0))
	    return;

	/* Adjust crop size to fit in the source image */
	if((rect->x + rect->width) > swidth)
	    rect->width = swidth - rect->x;
	if((rect->y + rect->height) > sheight)
	    rect->height = sheight - rect->y;

	/* Get target image geometry */
	tx = rect->x;
	ty = rect->y;
	twidth = MIN(rect->width, swidth);
	theight = MIN(rect->height, sheight);

	/* Sanitize crop coordinates so they lie within the source image */
	if(tx > (swidth - twidth))
	    tx = swidth - twidth;
	if(ty > (sheight - theight))
	    ty = sheight - theight;
	if(tx < 0)
	    tx = 0;
	if(ty < 0)
	    ty = 0;

	if((twidth <= 0) || (theight <= 0))
	    return;

	tbpl = twidth * bpp;

	/* Note: x and y are now garbage */


	/* Create the target image */
	tar_img = ImgViewImageNew(twidth, theight, bpp, 0);
	if(tar_img == NULL)
	    return;

	for(glist = src_img->frames_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    src_frame = IMGVIEW_FRAME(glist->data);
	    if(src_frame == NULL)
		continue;

	    src = src_frame->buf;
	    if(src == NULL)
		continue;

	    tar = (guint8 *)g_malloc(tbpl * theight);
	    if(tar == NULL)
		continue;

	    /* Iterate through target image */
	    for(y = 0; y < theight; y++)
	    {
	        tar_line = tar + (y * tbpl);
	        src_line = src + ((ty + y) * sbpl);

		for(x = 0; x < twidth; x++)
	        {
		    src_ptr = src_line + ((tx + x) * bpp);
		    tar_ptr = tar_line + (x * bpp);

		    for(bc = 0; bc < bpp; bc++)
		        *tar_ptr++ = *src_ptr++;
	        }
	    }

	    ImgViewImageAppendFrame(tar_img, tar, src_frame->delay);
	}

	/* Set the cropped image as the new image
	 *
	 * Note that the source and target images are now invalid
	 */
	ImgViewSetImageToFit(iv, tar_img);
	src_img = NULL;
	tar_img = NULL;

	if(was_playing)
	    ImgViewPlay(iv);

	/* Notify change */
	if(iv->changed_cb != NULL)
	    iv->changed_cb(
		iv,			/* ImgView */
		iv->orig_img,		/* Image */
		iv->changed_data	/* Data */
	    );
}


/*
 *	Creates a new crop dialog.
 */
imgview_cropdlg_struct *ImgViewCropDlgNew(
	imgview_struct *iv
)
{
	const gint	border_major = 5,
			bw = GUI_BUTTON_HLABEL_WIDTH_DEF,
			bh = GUI_BUTTON_HLABEL_HEIGHT_DEF;
	GdkWindow *window;
	GtkAccelGroup *accelgrp;
	GtkWidget *w, *parent, *parent2, *parent3, *parent4, *parent5, 
	    *parent6;
	GtkWidget *main_vbox;
	imgview_cropdlg_struct *cd = IMGVIEW_CROPDLG(g_malloc0(
	    sizeof(imgview_cropdlg_struct)
	));

	/* Reset values */
	cd->imgview = iv;
	memset(&cd->rect, 0x00, sizeof(GdkRectangle));

	/* Create keyboard accelerator group */
	cd->accelgrp = accelgrp = gtk_accel_group_new();

	/* Create toplevel */
	cd->toplevel = w = gtk_window_new(GTK_WINDOW_DIALOG);
	gtk_window_set_title(GTK_WINDOW(w), IMGVIEW_CROPDLG_TITLE);
#ifdef PROG_NAME
	gtk_window_set_wmclass(
	    GTK_WINDOW(w), "dialog", PROG_NAME
	);
#endif
	gtk_widget_realize(w);
	window = w->window;
	if(window != NULL)
	{
	    gdk_window_set_decorations(
		window,
		GDK_DECOR_BORDER | GDK_DECOR_TITLE | GDK_DECOR_MENU |
		GDK_DECOR_MINIMIZE
	    );
	    gdk_window_set_functions(
		window,
		GDK_FUNC_MOVE | GDK_FUNC_MINIMIZE | GDK_FUNC_CLOSE
	    );
	    GUISetWMIcon(window, (guint8 **)icon_cut_32x32_xpm);
	}
	gtk_signal_connect(
	    GTK_OBJECT(w), "delete_event",
	    GTK_SIGNAL_FUNC(ImgViewCropDlgDeleteEventCB), cd
	);
	gtk_window_add_accel_group(GTK_WINDOW(w), accelgrp);
	if(ImgViewToplevelIsWindow(iv))
	{
	    GtkWidget *ref_toplevel = ImgViewGetToplevelWidget(iv);
	    if(ref_toplevel != NULL)
	    {
#if 0
/* Do not set modal as user may want to work with image viewer
 * some more and key events still need to be sent to the image
 * viewer's view window
 */
		gtk_window_set_modal(GTK_WINDOW(w), TRUE);
#endif
		gtk_window_set_transient_for(
		    GTK_WINDOW(w), GTK_WINDOW(ref_toplevel)
		);
	    }
	}
	parent = w;

	main_vbox = w = gtk_vbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);
	parent = w;

	/* Hbox for multiple columns */
	w = gtk_hbox_new(FALSE, border_major);
	gtk_container_border_width(GTK_CONTAINER(w), border_major);
	gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent2 = w;


	/* Vbox for the left column */
	w = gtk_vbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent3 = w;
	/* Icon */
	w = gtk_pixmap_new_from_xpm_d(
	    window, NULL, (guint8 **)icon_cut_32x32_xpm
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);


	/* Vbox for the right column */
	w = gtk_vbox_new(FALSE, border_major);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent3 = w;

	w = gtk_hbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent4 = w;

	/* Hbox for Parameters & Values 1 */
	w = gtk_hbox_new(FALSE, border_major);
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, border_major);
	gtk_widget_show(w);     
	parent5 = w;

	/* Vbox for Parameters */
	w = gtk_vbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent5), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent6 = w;
	/* Label */
	w = gtk_label_new("\
X Origin:\n\
Y Origin:\n\
Width:\n\
Height:"
	);
	gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_RIGHT);
	gtk_box_pack_start(GTK_BOX(parent6), w, FALSE, FALSE, 0);
	gtk_widget_show(w);

	/* Vbox for Values */
	w = gtk_vbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent5), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent6 = w;
	/* Label */
	cd->val_label = w = gtk_label_new("");
	gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_LEFT);
	gtk_box_pack_start(GTK_BOX(parent6), w, FALSE, FALSE, 0);
	gtk_widget_show(w);


	/* Hbox for Parameters & Values 2 */
	w = gtk_hbox_new(FALSE, border_major);
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, border_major);
	gtk_widget_show(w);
	parent5 = w;

	/* Vbox for Parameters */
	w = gtk_vbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent5), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent6 = w;
	/* Label */
	w = gtk_label_new("\
Aspect:\n\
Area:"
	);
	gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_RIGHT);
	gtk_box_pack_start(GTK_BOX(parent6), w, FALSE, FALSE, 0);
	gtk_widget_show(w);

	/* Vbox for Values */
	w = gtk_vbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent5), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent6 = w;
	/* Label */
	cd->val2_label = w = gtk_label_new("");
	gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_LEFT);
	gtk_box_pack_start(GTK_BOX(parent6), w, FALSE, FALSE, 0);
	gtk_widget_show(w);



	w = gtk_hseparator_new();
	gtk_box_pack_start(GTK_BOX(main_vbox), w, FALSE, FALSE, 0);
	gtk_widget_show(w);

	/* Hbox for the buttons */
	w = gtk_hbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(main_vbox), w, FALSE, FALSE, border_major);
	gtk_widget_show(w);
	parent2 = w;

	/* Crop Button */
	cd->crop_btn = w = GUIButtonPixmapLabelH(
	    (guint8 **)icon_cut_20x20_xpm, "Crop", NULL
	);
	gtk_widget_set_usize(w, bw, bh);
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(ImgViewCropDlgCropCB), cd
	);
	gtk_accel_group_add(
	    accelgrp, GDK_r, 0, GTK_ACCEL_VISIBLE,
	    GTK_OBJECT(w), "clicked"
	);
	GUIButtonLabelUnderline(w, GDK_r);
	gtk_widget_show(w);

	/* Cancel button */
	cd->cancel_btn = w = GUIButtonPixmapLabelH(
	    (guint8 **)icon_cancel_20x20_xpm, "Cancel", NULL
	);
	gtk_widget_set_usize(w, bw, bh);
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(ImgViewCropDlgCancelCB), cd
	);
	gtk_accel_group_add(
	    accelgrp, GDK_Escape, 0, GTK_ACCEL_VISIBLE,
	    GTK_OBJECT(w), "clicked"
	);
	gtk_accel_group_add(
	    accelgrp, GDK_c, 0, GTK_ACCEL_VISIBLE,
	    GTK_OBJECT(w), "clicked"
	);
	GUIButtonLabelUnderline(w, GDK_c);
	gtk_widget_show(w);


	return(cd);
}

/*
 *	Maps the Crop Dialog with the specified crop bounds.
 */
void ImgViewCropDlgMapValues(
	imgview_cropdlg_struct *cd,
	gint x0, gint x1,	/* Crop Bounds */
	gint y0, gint y1,
	gint img_width,		/* Image Size (not Crop Rect) */
	gint img_height
)
{
	gint x, y, width, height;
	GdkRectangle *rect;
	GtkWidget *w;
	imgview_struct *iv = (cd != NULL) ? cd->imgview : NULL;
	if((iv == NULL) || (img_width <= 0) || (img_height <= 0))
	    return;

	/* Get crop geometry from the specified crop bounds */
	x = MIN(x0, x1);
	y = MIN(y0, y1);
	width = ABSOLUTE(x1 - x0) + 1;
	height = ABSOLUTE(y1 - y0) + 1;

	/* Adjust crop size to fit in the image */
	if((x + width) > img_width)
	    width = img_width - x;
	if((y + height) > img_height)
	    height = img_height - y;
	if(x < 0)
	{
	    width = width + x;
	    x = 0;
	}
	if(y < 0)
	{
	    height = height + y;
	    y = 0;
	}

	/* Crop rectangle has no size? */
	if((width <= 0) || (height <= 0))
	    return;

	/* Record crop rectangle geometry on the Crop Dialog */
	rect = &cd->rect;
	rect->x = x;
	rect->y = y;
	rect->width = width;
	rect->height = height;

	/* Set values label */
	w = cd->val_label;
	if(w != NULL)
	{
	    gchar *buf = g_strdup_printf("\
%i\n\
%i\n\
%i\n\
%i",
		x, y, width, height
	    );
	    gtk_label_set_text(GTK_LABEL(w), buf);
	    g_free(buf);
	}

	w = cd->val2_label;
	if(w != NULL)   
	{
	    gchar *buf = g_strdup_printf("\
%.4f\n\
%.0f%%",
		(gfloat)width / (gfloat)height,
		(gfloat)(width * height) /
		(gfloat)(img_width * img_height) * 100.0f
	    );
	    gtk_label_set_text(GTK_LABEL(w), buf);
	    g_free(buf);
	}         


	/* Map the Crop Dialog */
	ImgViewCropDlgMap(cd);
}

/*
 *	Checks if the Crop Dialog is mapped.
 */
gboolean ImgViewCropDlgIsMapped(imgview_cropdlg_struct *cd)
{
	GtkWidget *w = (cd != NULL) ? cd->toplevel : NULL; 
	return((w != NULL) ? GTK_WIDGET_MAPPED(w) : FALSE);
}

/*
 *	Maps the Crop Dialog.
 */
void ImgViewCropDlgMap(imgview_cropdlg_struct *cd)
{
	GtkWidget *w;

	if(cd == NULL)
	    return;

	w = cd->toplevel;
	if(w != NULL)   
	    gtk_widget_show_raise(w);

	w = cd->crop_btn;
	if(w != NULL)
	{
	    gtk_widget_grab_default(w);
	    gtk_widget_grab_focus(w);
	}
}

/*
 *	Unmaps the Crop Dialog.
 */
void ImgViewCropDlgUnmap(imgview_cropdlg_struct *cd)
{
	GtkWidget *w = (cd != NULL) ? cd->toplevel : NULL;
	if(w == NULL)
	    return;

	gtk_widget_hide(w);
}

/*
 *	Deletes the Crop Dialog.
 */
void ImgViewCropDlgDelete(imgview_cropdlg_struct *cd)
{
	if(cd == NULL)
	    return;

	GTK_WIDGET_DESTROY(cd->val2_label) 
	GTK_WIDGET_DESTROY(cd->val_label)
	GTK_WIDGET_DESTROY(cd->crop_btn)
	GTK_WIDGET_DESTROY(cd->cancel_btn)
	GTK_WIDGET_DESTROY(cd->toplevel)

	GTK_ACCEL_GROUP_UNREF(cd->accelgrp)

	g_free(cd);
}
