/* 
 * JGimp - A Java extension for the GIMP
 * Copyright (C) 2002  Georgia Tech Research Corp.
 * Written by Michael Terry, mterry@cc.gatech.edu
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 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 General Public License for more details.
 *
 * You should have received a copy of the GNU 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.
 */

/*
 * This file contains all the native code for JGimpExtendedProxy class
 */

#include <jni.h>

#include <glib.h>
#include "gimpimage.h"
#include "image_render.h"
#include "gximage.h"
#include "jgimp_jni_utils.h"
#include "tools.h"
#include "paint_core.h"
#include "JGimpExtendedProxy.h"

/*
 * Stub: replace this when we can be threaded.
 */
static gboolean in_valid_paint_operation = FALSE;
static const int CURRENT_GIMP_TOOL_ID = -100;

/* Inaccessible static: s_Instance */
/* Inaccessible static: class_00024org_00024gimp_00024jgimp_00024extension_00024JGimpExtension */
/* Inaccessible static: class_00024org_00024gimp_00024jgimp_00024plugin_00024JGimpPlugIn */
/* Inaccessible static: s_Instance */
/*
 * Class:     org_gimp_jgimp_proxy_JGimpExtendedProxyImpl
 * Method:    _renderImageInJavaIntFormat
 * Signature: (IIIII[II)J
 */
JNIEXPORT jlong JNICALL Java_org_gimp_jgimp_proxy_JGimpExtendedProxyImpl__1renderImageInJavaIntFormat
  (JNIEnv *env,
   jclass calling_class,
   jint image_ID,
   jint x,
   jint y,
   jint width,
   jint height,
   jintArray buffer,
   jint buffer_offset)
{
	const gint     SRC_BPP                     = 3;    /* GIMP renders 3 bytes per pixel into a display */
	GimpImage*     the_gimp_image              = NULL;
	jint           java_buffer_length          = 0;
	jint          *java_int_array              = NULL;
	guint32        needed_dest_array_size      = 0;
	guint32        actual_dest_array_size      = 0;
	gboolean       buffer_overflow             = FALSE;
	guint32        adjusted_height             = 0;
	guint          pixel_buf_length            = 0;
	guchar*        pixel_buf                   = NULL;
	int            cur_x                       = 0;
	int            cur_y                       = 0;
	int            this_width                  = 0;
	int            this_height                 = 0;
	int            copy_x                      = 0;
	int            copy_y                      = 0;
	register jint  this_pixel_value            = 0;
	guchar        *pixel_buf_iterator          = NULL;
	jint          *dest_offset                 = NULL;

	if (buffer == NULL)
	{
		fprintf(stderr, "Internal jgimp error: NULL buffer passed to _renderImageInJavaIntFormat\n");
		return -1;
	}

	the_gimp_image = pdb_id_to_image(image_ID);
	if (the_gimp_image == NULL)
	{
		jni_utils_throw_invalid_image_exception(env, image_ID);
		return -1;
	}

	/* Check to make sure we're in bounds */
	/* Throw an exception if we're not in bounds */
	if ( (x < 0) || (y < 0) || (width < 0) || (height < 0) || ((x + width) > gimp_image_get_width(the_gimp_image)) || ((y + height) > gimp_image_get_height(the_gimp_image)) )
	{
		jni_utils_throw_invalid_pixel_region_exception(env, x, y, width, height, gimp_image_get_width(the_gimp_image), gimp_image_get_height(the_gimp_image));
		return -1;
	}

	/* Calculate array size needed as well as the real array size we have */

	java_buffer_length = (*env)->GetArrayLength(env, buffer);
	needed_dest_array_size = width * height;
	actual_dest_array_size = java_buffer_length - buffer_offset;

	/* Calculate the number of pixels to copy */
	if (needed_dest_array_size > actual_dest_array_size)
	{
		adjusted_height = actual_dest_array_size / width;
		buffer_overflow = TRUE;
	}
	else
	{
		adjusted_height = height;
	}
	if (adjusted_height < 1)
	{
		jni_utils_throw_buffer_overflow_exception(env, x, y, width, height, width, adjusted_height, 4, actual_dest_array_size);
		return -1;
	}


	/* Get a copy of the int array */
	java_int_array = (*env)->GetIntArrayElements(env, buffer, NULL);
	if (!java_int_array)
	{
		fprintf(stderr, "JGimp out of memory error: couldn't get copy of int array");
		return -1;
	}


	/* Allocate a buffer for the pixels. See note below about why the buffer is this size */
	pixel_buf_length = GXIMAGE_WIDTH * GXIMAGE_HEIGHT * SRC_BPP;
	pixel_buf = g_new(guchar, pixel_buf_length);
	if (!pixel_buf)
	{
		fprintf(stderr, "JGimp out of memory error: couldn't allocate pixel buffer in _renderImageInJavaIntFormat");
		return -1;
	}

	/*
	 * Invalidate the area so it renders.
	 * Since Java normally only requests pixels after they have been
	 * changed, this is not too sub-optimal
	 */
	gimp_image_invalidate(the_gimp_image, x, y, width, adjusted_height, x, y, x + width, y + adjusted_height);
	/*
	 * OK, here's the deal: The GIMP rendering code is hard-coded to work
	 *						on 256 pixel-width boundaries (actually,
	 *						GXIMAGE_WIDTH * MAX_CHANNELS boundaries) because
	 *						it uses a static, global buffer (pixel_buf in image_render.c) of
	 *						that size to render each line (see
	 *						render_image_tile_fault in image_render.c). This has two implications:
	 *						1), we must only render into a buffer of width
	 *						GXIMAGE_WIDTH pixels, and 2), we must make sure
	 *						we don't call the rendering code from several 
	 *						threads at one time (since it uses a global, static
	 *						buffer when rendering).
	 */
	for (cur_y = 0; cur_y < adjusted_height; cur_y+=GXIMAGE_HEIGHT)
	{
		for (cur_x = 0; cur_x < width; cur_x+=GXIMAGE_WIDTH)
		{
			this_width  = ((width - cur_x) > GXIMAGE_WIDTH) ? GXIMAGE_WIDTH : (width - cur_x);
			this_height = ((adjusted_height - cur_y) > GXIMAGE_HEIGHT) ? GXIMAGE_HEIGHT : (adjusted_height - cur_y);

			render_image_into_buffer(the_gimp_image, pixel_buf, SRC_BPP, SRC_BPP * GXIMAGE_WIDTH, x + cur_x, y + cur_y, this_width, this_height, 1.0, 1.0);

			/* Copy the pixels to the dest array */
			/* ARGB byte order, with alpha initialized to full on */
			for (copy_y = 0; copy_y < this_height; copy_y++)
			{
				/* Get the proper offset into the pixel_buffer */
				pixel_buf_iterator = pixel_buf + (copy_y * GXIMAGE_WIDTH * SRC_BPP);
				/* Get the offset into the proper row and column in the Java pixel buffer */
				dest_offset = java_int_array + (((cur_y + copy_y) * width) + cur_x + buffer_offset);

				for (copy_x = 0; copy_x < this_width; copy_x++)
				{
/* Stub: Need to find out why this is the correct byte ordering... Probably has to do with
 *       the way the displays are set up */
					this_pixel_value       = (*pixel_buf_iterator)        ; pixel_buf_iterator++;  /* Blue  */
					this_pixel_value      |= (*pixel_buf_iterator) <<  8  ; pixel_buf_iterator++;  /* Green */
					this_pixel_value      |= (*pixel_buf_iterator) << 16  ; pixel_buf_iterator++;  /* Red   */
					(*dest_offset)         = this_pixel_value | 0xFF000000; dest_offset++;         /* OR in the Alpha as a solid */
				}
			}
		}
	}

	/* Release our buffer */
	g_free(pixel_buf);
	pixel_buf = NULL;

	/* Copy the array back to the object */
	(*env)->ReleaseIntArrayElements(env, buffer, java_int_array, 0);

	if (buffer_overflow)
	{
		jni_utils_throw_buffer_overflow_exception(env, x, y, width, height, width, adjusted_height, 4, actual_dest_array_size);
	}

	return (adjusted_height * width * 4);
}

/*
 * Class:     org_gimp_jgimp_proxy_JGimpExtendedProxyImpl
 * Method:    _startPainting
 * Signature: (IIDDDDDD)V
 */
JNIEXPORT void JNICALL Java_org_gimp_jgimp_proxy_JGimpExtendedProxyImpl__1startPainting
  (JNIEnv *env, jclass calling_class, jint paint_tool_ID, jint drawable_ID, jdouble x, jdouble y, jdouble pressure, jdouble xtilt, jdouble ytilt, jdouble wheel)
{
	GimpDrawable *drawable         = NULL;

	in_valid_paint_operation = FALSE;

	if (paint_tool_ID == CURRENT_GIMP_TOOL_ID)
	{
		if ((active_tool == NULL) || ((active_tool->type < PENCIL) || (active_tool->type > SMUDGE)))
		{
			jni_utils_throw_invalid_paint_tool_exception(env, paint_tool_ID);
			return;
		}
	}
	else if ((paint_tool_ID < PENCIL) || (paint_tool_ID > SMUDGE))
	{
		jni_utils_throw_invalid_paint_tool_exception(env, paint_tool_ID);
		return;
	}

	drawable = gimp_drawable_get_ID(drawable_ID);
	if (drawable == NULL)
	{
		jni_utils_throw_invalid_drawable_exception(env, drawable_ID);
		return;
	}

	if ((active_tool == NULL) || (active_tool->type != paint_tool_ID))
	{
		if (paint_tool_ID != CURRENT_GIMP_TOOL_ID)
		{
			tools_select(paint_tool_ID);
		}
	}

	if (active_tool == NULL)
	{
		jni_utils_throw_proxy_exception(env, "JGimp internal error: Couldn't select active paint tool in startPainting\n");
		return;
	}

	active_tool->drawable = drawable;

	paint_core_non_gui_start_painting(active_tool,
			active_tool->drawable,
			NON_GUI_PAINT_REINITIALIZE_INPUT_VALUES,
			x,
			y,
			pressure,
			xtilt,
			ytilt
#ifdef GTK_HAVE_SIX_VALUATORS
			 , wheel
#endif /* GTK_HAVE_SIX_VALUATORS */
		);
	in_valid_paint_operation = TRUE;
}

/*
 * Class:     org_gimp_jgimp_proxy_JGimpExtendedProxyImpl
 * Method:    _addToPainting
 * Signature: (IIDDDDDD)Ljava/awt/Rectangle;
 */
JNIEXPORT jobject JNICALL Java_org_gimp_jgimp_proxy_JGimpExtendedProxyImpl__1addToPainting
  (JNIEnv *env, jclass calling_class, jint paint_tool_ID, jint drawable_ID, jdouble x, jdouble y, jdouble pressure, jdouble xtilt, jdouble ytilt, jdouble wheel)
{
	if ((active_tool == NULL) || (active_tool->drawable == NULL) || (!in_valid_paint_operation))
	{
		jni_utils_throw_proxy_exception(env, "JGimp internal error. Call to addToPainting invalid. Did you call startPainting first?");
		return NULL;
	}
	if (paint_tool_ID == CURRENT_GIMP_TOOL_ID)
	{
		if ((active_tool->type < PENCIL) || (active_tool->type > SMUDGE))
		{
			jni_utils_throw_invalid_paint_tool_exception(env, paint_tool_ID);
			return;
		}
	}
	paint_core_non_gui_motion_painting(active_tool,
			active_tool->drawable,
			x,
			y,
			pressure,
			xtilt,
			ytilt
#ifdef GTK_HAVE_SIX_VALUATORS
		 , wheel
#endif /* GTK_HAVE_SIX_VALUATORS */
		);
	return NULL;
}

/*
 * Class:     org_gimp_jgimp_proxy_JGimpExtendedProxyImpl
 * Method:    _stopPainting
 * Signature: (II)Ljava/awt/Rectangle;
 */
JNIEXPORT jobject JNICALL Java_org_gimp_jgimp_proxy_JGimpExtendedProxyImpl__1stopPainting
  (JNIEnv *env, jclass calling_class, jint paint_tool_ID, jint drawable_ID)
{
	if ((active_tool == NULL) || (active_tool->drawable == NULL) || (!in_valid_paint_operation))
	{
		jni_utils_throw_proxy_exception(env, "JGimp internal error. Call to stopPainting invalid. Did you call startPainting first?");
		return NULL;
	}
	if (paint_tool_ID == CURRENT_GIMP_TOOL_ID)
	{
		if ((active_tool->type < PENCIL) || (active_tool->type > SMUDGE))
		{
			jni_utils_throw_invalid_paint_tool_exception(env, paint_tool_ID);
			return;
		}
	}
	paint_core_non_gui_stop_painting(active_tool, active_tool->drawable);
	in_valid_paint_operation = FALSE;
	return NULL;
}
