/* 
 * 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 is a set of wrapper functions so that we can
 * either embed jgimp directly in the GIMP's process (for
 * efficiency and speed) or as a normal plug-in.
 */

#ifndef _WIN32
 #include <pthread.h>
#else
 #include <WINDOWS.H>
 #ifndef _MT
 #define _MT /* defines the multi-threading option so we're compiled as multi-threaded */
 #endif
#endif /* _WIN32 */

#include <string.h>

#include <glib.h>
#include "gimprc.h"
#include "jgimp.h"
#include "jgimp_gimp_adapter.h"
#include "gimpdrawable.h"
#include "gimpparasite.h"
#include "procedural_db.h"
#include "appenv.h" /* for the command-line switches like no_interface */

/*
 * Data type definitions
 */
typedef enum _CallbackTypes
{
	RUN_PROCEDURE_CALLBACK
} CallbackTypes;

typedef struct _RunProcedureCallbackData
{
	gchar   *name;
	gint    *nreturn_vals;
	gint     nparams;
	GPParam *params;
	GPParam *return_params;
} RunProcedureCallbackData;

typedef struct _CallbackData
{
	CallbackTypes   callback_type;
	pthread_cond_t  suspend_conditional; /* Thread suspension conditional */
	pthread_mutex_t suspend_mutex;       /* Thread suspension mutex       */
	gboolean       is_finished;

	union {
		RunProcedureCallbackData run_proc_data;
	} data;
} CallbackData;



/* Should probably use glib's thread mutex's here at some point */
/* Mutexes and variables to maintain list of pending functions  */
#ifndef _WIN32
 static pthread_mutex_t callback_data_list_mutex = PTHREAD_MUTEX_INITIALIZER;
#else
 HANDLE callback_data_list_mutex = NULL;
#endif /* _WIN32 */
 static GSList* callback_data_list = NULL;
 static void lock_callback_data_list_mutex();
 static void unlock_callback_data_list_mutex();


 /*
  * Callback processing
  */
static void initialize_callback_data(CallbackData *callback_data, CallbackTypes callback_type);
static void add_callback_data_to_list_and_wait(CallbackData *callback_data);
static void destroy_callback_data(CallbackData *callback_data);
static void process_callback_list();
static void process_run_procedure_callback(CallbackData *callback_data);


/*
 * JGimp initialization
 */
static gboolean  jgimp_initialized = FALSE;
static gboolean  jgimp_is_initializing = FALSE;
static gboolean  jgimp_initialization_failed = FALSE;
static pthread_t jgimp_thread;
static pthread_mutex_t jgimp_initialization_mutex = PTHREAD_MUTEX_INITIALIZER;
static void initialize_jgimp();
static void * start_java(void* arg_ptr);

/*
 * GLib callback functions for event handling
 */
static gboolean prepare_query(gpointer source_data,
		GTimeVal *current_time,
		gint *timeout,
		gpointer user_data);
static gboolean check_query(gpointer source_data,
		GTimeVal *current_time,
		gpointer user_data);
static gboolean dispatch_run(gpointer source_data,
		GTimeVal *current_time,
		gpointer user_data);
static void destroy_notify(gpointer data);


/*
 * Data marshalling functions
 */
static GPParam* convert_args_to_gimp_params(Argument *args, gint nargs);
static void free_temp_args(Argument *args, gint nargs);
static Argument* convert_gimp_params_to_args(GPParam *params, gint nparams);


GPParam* adapter_run_pdb_procedure(gchar *name,
		gint *nreturn_vals,
		gint nparams,
		GPParam *params)
{
	CallbackData callback_data;
	GPParam * return_params = NULL;

	if ( (name == NULL) || (nreturn_vals == NULL) || ((params == NULL) && (nparams > 0)))
	{
		fprintf(stderr, "JGimp internal error: no name, nreturn_vals, or params in adapter_run_pdb_procedure\n");
		return NULL;
	}

	initialize_callback_data(&callback_data, RUN_PROCEDURE_CALLBACK);

	callback_data.data.run_proc_data.name          = name;
	callback_data.data.run_proc_data.nreturn_vals  = nreturn_vals;
	callback_data.data.run_proc_data.params        = params;
	callback_data.data.run_proc_data.nparams       = nparams;
	callback_data.data.run_proc_data.return_params = NULL;

	add_callback_data_to_list_and_wait(&callback_data);

	return_params = callback_data.data.run_proc_data.return_params;

	destroy_callback_data(&callback_data);

	return return_params;
}
static void process_run_procedure_callback(CallbackData *callback_data)
{
	ProcRecord    *proc_rec;
	Argument      *temp_return_vals;
	GPParam       *return_params = NULL;
	gchar         *name;
	gint          *nreturn_vals;
	gint           nparams;
	GPParam       *params;

	name         = callback_data->data.run_proc_data.name;
	nreturn_vals = callback_data->data.run_proc_data.nreturn_vals;
	nparams      = callback_data->data.run_proc_data.nparams;
	params       = callback_data->data.run_proc_data.params;

	proc_rec = procedural_db_lookup(name);
	if (proc_rec)
	{
		Argument *temp_args;

		temp_args = convert_gimp_params_to_args(params, nparams);
		temp_return_vals = procedural_db_execute (name, temp_args);
		free_temp_args(temp_args, nparams);
	}
	else
	{
		/*  if the name lookup failed, construct a 
		*  dummy "executiuon error" return value --Michael
		*/
		temp_return_vals = g_new (Argument, 1);
		temp_return_vals[0].arg_type      = PDB_STATUS;
		temp_return_vals[0].value.pdb_int = PDB_EXECUTION_ERROR;
	}


	if (!temp_return_vals) 
	{
		/*
		 * Should always have valid return vals from running procedural_db_execute
		 * because it executes all plug-ins synchronously
		 */
		fprintf(stderr, "Internal jgimp error: no return values returned from procedural_db_execute in adapter_run_pdb_procedure\n");
		callback_data->data.run_proc_data.return_params = NULL;
		(*nreturn_vals) = 1;
		return;
	}
	else
	{
		if (proc_rec)
		{
			(*nreturn_vals) = proc_rec->num_values + 1;
			return_params = convert_args_to_gimp_params(temp_return_vals, proc_rec->num_values + 1);
		}
		else
		{
			(*nreturn_vals) = 1;
			return_params = convert_args_to_gimp_params(temp_return_vals, 1);
		}
		procedural_db_destroy_args(temp_return_vals, ((proc_rec) ? (proc_rec->num_values + 1) : 1));
	}

	callback_data->data.run_proc_data.return_params = return_params;
}



void        adapter_install_temp_proc   (gchar        *name,
				           gchar        *blurb,
				           gchar        *help,
				           gchar        *author,
				           gchar        *copyright,
				           gchar        *date,
				           gchar        *menu_path,
				           gchar        *image_types,
				           gint          type,
				           gint          nparams,
				           gint          nreturn_vals,
				           GPParamDef   *params,
				           GPParamDef   *return_vals,
				           void         *run_proc)
{
	printf("Stub: no support for plug-ins yet with the embedded version of jgimp\n");
}

JGimpDrawableInfo adapter_get_drawable_info(gint32 drawable_ID)
{
	JGimpDrawableInfo  return_info;
	GimpDrawable      *drawable = NULL;

	drawable = gimp_drawable_get_ID(drawable_ID);

	if (drawable == NULL)
	{
		return_info.id                = drawable_ID;
		return_info.width             = -1;
		return_info.height            = -1;
		return_info.bpp               = -1;
		return_info.has_alpha         = 0;
		return_info.is_rgb            = 0;
		return_info.is_gray           = 0;
		return_info.is_indexed        = 0;
		return_info.is_valid_drawable = FALSE;
	}
	else
	{
		return_info.id                = drawable_ID;
		return_info.width             = gimp_drawable_width(drawable);
		return_info.height            = gimp_drawable_height(drawable);
		return_info.bpp               = gimp_drawable_bytes(drawable);
		return_info.has_alpha         = gimp_drawable_has_alpha(drawable);
		return_info.is_rgb            = gimp_drawable_is_rgb(drawable);
		return_info.is_gray           = gimp_drawable_is_gray(drawable);
		return_info.is_indexed        = gimp_drawable_is_indexed(drawable);
		return_info.is_valid_drawable = ((return_info.width < 0) ? 0 : 1);
	}

	return return_info;
}

jlong             adapter_get_pixel_rgn(JGimpDrawableInfo* drawable,
										gint x,
										gint y,
										gint width,
										gint height,
										guchar* buffer,
										gint buffer_offset,
										gint buffer_length)
{
	/*
	 * Much of this is shamelessly copied from libgimp/gimppixelrgn.c
	 */
	TileManager   *tm = NULL;
	Tile          *tile;
	GimpDrawable  *this_drawable = NULL;
	guchar        *src;
	guchar        *dest;
	gulong         bufstride;
	gint           xstart, ystart;
	gint           xend, yend;
	gint           xboundary;
	gint           yboundary;
	gint           xstep, ystep;
	gint           ty, bpp;

	if (buffer == NULL)
	{
		fprintf(stderr, "Internal jgimp error: NULL buffer passed to adapter_get_pixel_rgn\n");
		return -1;
	}
	if (drawable == NULL)
	{
		fprintf(stderr, "Internal jgimp error: NULL drawable info passed to adapter_get_pixel_rgn\n");
		return -1;
	}
	if (!(drawable->is_valid_drawable))
	{
		fprintf(stderr, "Internal jgimp error: Invalid drawable info passed to adapter_get_pixel_rgn\n");
		return -1;
	}
	if ((drawable->bpp * width * height) > (buffer_length - buffer_offset))
	{
		fprintf(stderr, "Internal jgimp error: buffer underflow error in adapter_get_pixel_rgn\n");
		return -1;
	}

	this_drawable = gimp_drawable_get_ID(drawable->id);
	if (this_drawable == NULL)
	{
		fprintf(stderr, "Internal jgimp error: could not get drawable in adapter_get_pixel_rgn\n");
		return -1;
	}
	
	tm = gimp_drawable_data(this_drawable);
	if (tm == NULL)
	{
		fprintf(stderr, "Internal jgimp error: could not get drawable's tile manager in adapter_get_pixel_rgn\n");
		return -1;
	}

	bpp = drawable->bpp;
	bufstride = bpp * width;

	xstart = x;
	ystart = y;
	xend = x + width;
	yend = y + height;
	ystep = 0;

	while (y < yend)
	{
		x = xstart;
		while (x < xend)
		{
			tile = tile_manager_get_tile(tm, x, y, TRUE, FALSE);

			if (tile == NULL)
			{
				fprintf(stderr, "Internal jgimp error: Could not get tile x: %d y: %d in adapter_get_pixel_rgn\n", x, y);
				return -1;
			}

			/*
			 * MAT: Don't ask me where this step amount comes from... I just copied
			 *      it from other, existing code snippets
			 */
			xstep = tile_ewidth(tile) - (x % TILE_WIDTH);
			ystep = tile_eheight(tile) - (y % TILE_HEIGHT);
			xboundary = x + xstep;
			yboundary = y + ystep;
			xboundary = MIN (xboundary, xend);
			yboundary = MIN (yboundary, yend);

			for (ty = y; ty < yboundary; ty++)
			{
				src = tile_data_pointer(tile, x % TILE_WIDTH, ty % TILE_HEIGHT);
				dest = buffer + bufstride * (ty - ystart) + bpp * (x - xstart) + buffer_offset;
				memcpy ((void *)dest, (const void *)src, (xboundary-x)*bpp);  
			}

			tile_release(tile, FALSE);
			x += xstep;
		}

		y += ystep;
	}
	return (width * height * drawable->bpp);
}

jlong             adapter_set_pixel_rgn(JGimpDrawableInfo* drawable,
										gint x,
										gint y,
										gint width,
										gint height,
										guchar* buffer,
										gint buffer_offset,
										gint buffer_length,
										gboolean flush_drawable,
										gboolean merge_shadow,
										gboolean update_drawable)
{
	if (buffer == NULL)
	{
		fprintf(stderr, "Internal jgimp error: NULL buffer passed to adapter_get_pixel_rgn\n");
		return -1;
	}
	if (drawable == NULL)
	{
		fprintf(stderr, "Internal jgimp error: NULL drawable info passed to adapter_get_pixel_rgn\n");
		return -1;
	}
	if (!(drawable->is_valid_drawable))
	{
		fprintf(stderr, "Internal jgimp error: Invalid drawable info passed to adapter_get_pixel_rgn\n");
		return -1;
	}
	if ((drawable->bpp * width * height) > (buffer_length - buffer_offset))
	{
		fprintf(stderr, "Internal jgimp error: buffer underflow error in adapter_get_pixel_rgn\n");
		return -1;
	}

fprintf(stderr, "Set pixel region stub\n");
	return -1;
}

/*
 * Mostly copied straight from app/plug_in.c,
 * but modified to do a shallow copy rather than
 * a full copy for pointer data
 */
static Argument*
convert_gimp_params_to_args(GPParam *params, gint nparams)
{
  Argument  *args;
  guchar    *colorarray;
  gint i;

  if (nparams == 0)
    return NULL;

  args = g_new (Argument, nparams);

  for (i = 0; i < nparams; i++)
    {
      args[i].arg_type = params[i].type;

      switch (args[i].arg_type)
	{
	case PDB_INT32:
	  args[i].value.pdb_int = params[i].data.d_int32;
	  break;
	case PDB_INT16:
	  args[i].value.pdb_int = params[i].data.d_int16;
	  break;
	case PDB_INT8:
	  args[i].value.pdb_int = params[i].data.d_int8;
	  break;
	case PDB_FLOAT:
	  args[i].value.pdb_float = params[i].data.d_float;
	  break;
	case PDB_STRING:
	    args[i].value.pdb_pointer = params[i].data.d_string;
	  break;
	case PDB_INT32ARRAY:
	    args[i].value.pdb_pointer = params[i].data.d_int32array;
	  break;
	case PDB_INT16ARRAY:
	    args[i].value.pdb_pointer = params[i].data.d_int16array;
	  break;
	case PDB_INT8ARRAY:
	    args[i].value.pdb_pointer = params[i].data.d_int8array;
	  break;
	case PDB_FLOATARRAY:
	    args[i].value.pdb_pointer = params[i].data.d_floatarray;
	  break;
	case PDB_STRINGARRAY:
	    args[i].value.pdb_pointer = params[i].data.d_stringarray;
	  break;
	case PDB_COLOR:
	  args[i].value.pdb_pointer = g_new (guchar, 3);
	  colorarray    = args[i].value.pdb_pointer;
	  colorarray[0] = params[i].data.d_color.red;
	  colorarray[1] = params[i].data.d_color.green;
	  colorarray[2] = params[i].data.d_color.blue;
	  break;
	case PDB_REGION:
	  g_message ("the \"region\" arg type is not currently supported");
	  break;
	case PDB_DISPLAY:
	  args[i].value.pdb_int = params[i].data.d_display;
	  break;
	case PDB_IMAGE:
	  args[i].value.pdb_int = params[i].data.d_image;
	  break;
	case PDB_LAYER:
	  args[i].value.pdb_int = params[i].data.d_layer;
	  break;
	case PDB_CHANNEL:
	  args[i].value.pdb_int = params[i].data.d_channel;
	  break;
	case PDB_DRAWABLE:
	  args[i].value.pdb_int = params[i].data.d_drawable;
	  break;
	case PDB_SELECTION:
	  args[i].value.pdb_int = params[i].data.d_selection;
	  break;
	case PDB_BOUNDARY:
	  args[i].value.pdb_int = params[i].data.d_boundary;
	  break;
	case PDB_PATH:
	  args[i].value.pdb_int = params[i].data.d_path;
	  break;
	case PDB_PARASITE:
	    args[i].value.pdb_pointer = (void *)&(params[i].data.d_parasite);
	  break;
	case PDB_STATUS:
	  args[i].value.pdb_int = params[i].data.d_status;
	  break;
	case PDB_END:
	  break;
	}
    }

  return args;
}

/*
 * Mostly copied straight from app/plug_in.c,
 * except we always make shallow copies of the args
 */
static void
free_temp_args(Argument *args, gint nargs)
{
	gint    i;

	for (i = 0; i < nargs; i++)
	{
		if (args[i].arg_type == PDB_COLOR)
		{
		  g_free (args[i].value.pdb_pointer);
		}
	}
	g_free (args);
}

/*
 * Mostly copied straight from app/plug_in.c,
 * except that we do a full (deep) copy of the arguments
 * to the parameters, so that we can free memory from
 * the arguments later
 */
static GPParam*
convert_args_to_gimp_params(Argument *args, gint nargs)
{
  GPParam  *params;
  gchar   **stringarray;
  guchar   *colorarray;
  gint i, j;
  GimpParasite *tmp_parasite;

  if (nargs == 0)
    return NULL;

  params = g_new (GPParam, nargs);

  for (i = 0; i < nargs; i++)
    {
      params[i].type = args[i].arg_type;

      switch (args[i].arg_type)
	{
	case PDB_INT32:
	  params[i].data.d_int32 = args[i].value.pdb_int;
	  break;
	case PDB_INT16:
	  params[i].data.d_int16 = args[i].value.pdb_int;
	  break;
	case PDB_INT8:
	  params[i].data.d_int8 = args[i].value.pdb_int;
	  break;
	case PDB_FLOAT:
	  params[i].data.d_float = args[i].value.pdb_float;
	  break;
	case PDB_STRING:
	    params[i].data.d_string = g_strdup (args[i].value.pdb_pointer);
	  break;
	case PDB_INT32ARRAY:
	  params[i].data.d_int32array = g_new (gint32, params[i-1].data.d_int32);
	  memcpy (params[i].data.d_int32array, args[i].value.pdb_pointer, params[i-1].data.d_int32 * 4);
	  break;
	case PDB_INT16ARRAY:
	  params[i].data.d_int16array = g_new (gint16, params[i-1].data.d_int32);
	  memcpy (params[i].data.d_int16array, args[i].value.pdb_pointer, params[i-1].data.d_int32 * 2);
	  break;
	case PDB_INT8ARRAY:
	  params[i].data.d_int8array = g_new (gint8, params[i-1].data.d_int32);
	  memcpy (params[i].data.d_int8array, args[i].value.pdb_pointer, params[i-1].data.d_int32);
	  break;
	case PDB_FLOATARRAY:
	  params[i].data.d_floatarray = g_new (gdouble, params[i-1].data.d_int32);
	  memcpy (params[i].data.d_floatarray, args[i].value.pdb_pointer, params[i-1].data.d_int32 * 8);
	  break;
	case PDB_STRINGARRAY:
	  params[i].data.d_stringarray = g_new (gchar*, params[i-1].data.d_int32);
	  stringarray = args[i].value.pdb_pointer;

	  for (j = 0; j < params[i-1].data.d_int32; j++)
		params[i].data.d_stringarray[j] = g_strdup (stringarray[j]);
	  break;
	case PDB_COLOR:
	  colorarray = args[i].value.pdb_pointer;
	  if( colorarray )
	    {
	      params[i].data.d_color.red   = colorarray[0];
	      params[i].data.d_color.green = colorarray[1];
	      params[i].data.d_color.blue  = colorarray[2];
	    }
	  else
	    {
	      params[i].data.d_color.red   = 0;
	      params[i].data.d_color.green = 0;
	      params[i].data.d_color.blue  = 0;
	    }
	  break;
	case PDB_REGION:
	  break;
	case PDB_DISPLAY:
	  params[i].data.d_display = args[i].value.pdb_int;
	  break;
	case PDB_IMAGE:
	  params[i].data.d_image = args[i].value.pdb_int;
	  break;
	case PDB_LAYER:
	  params[i].data.d_layer = args[i].value.pdb_int;
	  break;
	case PDB_CHANNEL:
	  params[i].data.d_channel = args[i].value.pdb_int;
	  break;
	case PDB_DRAWABLE:
	  params[i].data.d_drawable = args[i].value.pdb_int;
	  break;
	case PDB_SELECTION:
	  params[i].data.d_selection = args[i].value.pdb_int;
	  break;
	case PDB_BOUNDARY:
	  params[i].data.d_boundary = args[i].value.pdb_int;
	  break;
	case PDB_PATH:
	  params[i].data.d_path = args[i].value.pdb_int;
	  break;
	case PDB_PARASITE:
		tmp_parasite = gimp_parasite_copy (args[i].value.pdb_pointer);
		if (tmp_parasite == NULL)
		{
			params[i].data.d_parasite.name  = 0;
			params[i].data.d_parasite.flags = 0;
			params[i].data.d_parasite.size  = 0;
			params[i].data.d_parasite.data  = 0;
		}
		else
		{
			memcpy (&params[i].data.d_parasite, tmp_parasite, sizeof (GimpParasite));
			g_free (tmp_parasite);
		}
	  break;
	case PDB_STATUS:
	  params[i].data.d_status = args[i].value.pdb_int;
	  break;
	case PDB_END:
	  break;
	}
    }

  return params;
}

void adapter_destroy_pdb_procedure_return_vals(GPParam *params, gint nparams)
{
/*
 * Mostly copied straight from app/plug_in.c,
 * except that we always do a complete clean-up
 * since it was a deep copy
 */

  gint i, j;

  if (params == NULL)
  {
	return;
  }

  for (i = 0; i < nparams; i++)
    {
      switch (params[i].type)
	{
	case PDB_INT32:
	case PDB_INT16:
	case PDB_INT8:
	case PDB_FLOAT:
	  break;
	case PDB_STRING:
	  g_free (params[i].data.d_string);
	  break;
	case PDB_INT32ARRAY:
	  g_free (params[i].data.d_int32array);
	  break;
	case PDB_INT16ARRAY:
	  g_free (params[i].data.d_int16array);
	  break;
	case PDB_INT8ARRAY:
	  g_free (params[i].data.d_int8array);
	  break;
	case PDB_FLOATARRAY:
	  g_free (params[i].data.d_floatarray);
	  break;
	case PDB_STRINGARRAY:
	  for (j = 0; j < params[i-1].data.d_int32; j++)
	  {
		g_free (params[i].data.d_stringarray[j]);
	  }
	  g_free (params[i].data.d_stringarray);
	  break;
	case PDB_COLOR:
	  break;
	case PDB_REGION:
	  break;
	case PDB_DISPLAY:
	case PDB_IMAGE:
	case PDB_LAYER:
	case PDB_CHANNEL:
	case PDB_DRAWABLE:
	case PDB_SELECTION:
	case PDB_BOUNDARY:
	case PDB_PATH:
	  break;
	case PDB_PARASITE:
	    if (params[i].data.d_parasite.data)
	    {
	      g_free (params[i].data.d_parasite.name);
	      g_free (params[i].data.d_parasite.data);
	      params[i].data.d_parasite.name = 0;
	      params[i].data.d_parasite.data = 0;
	    }
	  break;
	case PDB_STATUS:
	  break;
	case PDB_END:
	  break;
	}
	  /* Just to be paranoid... */
	  params[i].data.d_string = NULL;
    }

  g_free (params);
}

guint jgimp_install_g_main_callback_hooks()
{
	static GSourceFuncs funcs;
	gint                priority;

	funcs.prepare  = prepare_query;
	funcs.check    = check_query;
	funcs.dispatch = dispatch_run;
	funcs.destroy  = destroy_notify; 

	/*
	 * We need to set the priority lower than the redraw priority
	 * for the GTK (G_PRIORITY_HIGH_IDLE + 20) if
	 * we are running the GIMP at the same time, or it won't be able
	 * to update its GUI fast enough. It seems that G_PRIORITY_LOW
	 * is necessary to enable real-time updates to the canvas (like
	 * applying a filter, adding text, etc.).
	 */
	if (no_interface)
	{
		priority = G_PRIORITY_DEFAULT;
	}
	else
	{
		priority = G_PRIORITY_LOW;
	}
	return g_source_add(priority,
						TRUE,               /* can recurse */
						&funcs,
						NULL,               /* source data */
						NULL,               /* user data   */
						free);
					
}

static gboolean prepare_query(gpointer source_data,
		GTimeVal *current_time,
		gint *timeout,
		gpointer user_data)
{
	return TRUE;
}

static gboolean check_query(gpointer source_data,
		GTimeVal *current_time,
		gpointer user_data)
{
	return TRUE;
}

static gboolean dispatch_run(gpointer source_data,
		GTimeVal *current_time,
		gpointer user_data)
{
	if (!jgimp_initialized)
	{
		initialize_jgimp();
	}
	if (callback_data_list != NULL)
	{
		process_callback_list();
	}
	return TRUE;
}

static void destroy_notify(gpointer data)
{
	printf("STUB: In destroy_notify\n");
}

static void initialize_jgimp()
{
	/* Do a quick check before locking our mutex */
	if (jgimp_initialized || jgimp_initialization_failed || jgimp_is_initializing)
	{
		return;
	}
	pthread_mutex_lock(&jgimp_initialization_mutex);
	if (!(jgimp_initialized || jgimp_is_initializing || jgimp_initialization_failed))
	{
		/*
		* The following constants are defined in the Makefile by the configure script in UNIX.
		* These arguments are passed to initialize the JNI
		* stuff and to be able to load the jar files
		*/
		static gchar* args[] = { JGIMP_JARFILE_LOCATION, JGIMP_DEFAULT_EXTENSIONS_DIR };
		static gchar* JGIMP_JARFILE_EXTENSIONS_DIR_KEY = "jgimp_jarfile_extensions_dir";
		gchar* gimprc_jarfile_extension_dir   = NULL;
		int result = 0;

		jgimp_is_initializing = TRUE;

		/*
		 * Check if the jarfile extensions directory has already been set in
		 * the gimprc file. If it is, use it, if not, use the default
		 * directory and set that value in the gimprc file.
		 */
		gimprc_jarfile_extension_dir = gimprc_find_token(JGIMP_JARFILE_EXTENSIONS_DIR_KEY);
		if (gimprc_jarfile_extension_dir == NULL)
		{
			save_gimprc_strings(JGIMP_JARFILE_EXTENSIONS_DIR_KEY, JGIMP_DEFAULT_EXTENSIONS_DIR);
		}
		else
		{
			args[1] = gimprc_jarfile_extension_dir;
		}

		/* 
		* Load the JVM, passing the JGimp jarfile to be set as the CLASSPATH
		*/
		result = jgimp_load(JGIMP_JARFILE_LOCATION);

		/* Check our return result */
		if (result < 0)
		{
			fprintf(stderr, "Failed to load JVM when loading the JGimp server. Jarfile location given: %s\nBailing out\n", JGIMP_JARFILE_LOCATION);
			jgimp_initialization_failed = TRUE;
		}
		else
		{
			/*
			 * Create a new thread to start up the java code. Why? Because 
			 * when the Java code starts up, it will probably call the GIMP,
			 * and if we sit and wait for the Java code to return, then
			 * this thread is blocked, meaning we can't service the request
			 * (we reach deadlock). Starting from another thread lets us
			 * continue and receive requests normally.
			 */

			result = pthread_create(&jgimp_thread, NULL, start_java, args);
			if (result)
			{
				jgimp_initialization_failed = TRUE;
			}
		}
	}
	pthread_mutex_unlock(&jgimp_initialization_mutex);
}

static void * start_java(void* arg_ptr)
{
	gchar** args = arg_ptr;

	/* Load the Java jar file to take over */
	jgimp_load_class(JGIMP_EXTENDED_PROXY_IMPL_CLASS, 2, args); 

/*
	{
	char* temp_args[] = {JGIMP_JARFILE_LOCATION, NULL, JGIMP_DEFAULT_EXTENSIONS_DIR};
	jgimp_load_class(JGIMP_PLUG_IN_PROXY_CLASS, 3, temp_args);
	}
*/
	
	jgimp_initialized     = TRUE;
	jgimp_is_initializing = FALSE;

	return NULL;
}

static void initialize_callback_data(CallbackData *callback_data, CallbackTypes callback_type)
{
	callback_data->callback_type       = callback_type;
	callback_data->is_finished         = FALSE;
	pthread_cond_init(&(callback_data->suspend_conditional), NULL);
	pthread_mutex_init(&(callback_data->suspend_mutex), NULL);
}
static void destroy_callback_data(CallbackData *callback_data)
{
	pthread_cond_destroy(&(callback_data->suspend_conditional));
	pthread_mutex_destroy(&(callback_data->suspend_mutex));
}
static void add_callback_data_to_list_and_wait(CallbackData *callback_data)
{
	/*
	 * Lock the suspend mutex before adding it to the list, so that we 
	 * don't get signaled before suspending
	 */
	pthread_mutex_lock(&(callback_data->suspend_mutex));

	lock_callback_data_list_mutex();
	callback_data_list = g_slist_append(callback_data_list, callback_data);
	unlock_callback_data_list_mutex();

	while (!(callback_data->is_finished))
	{
		pthread_cond_wait(&(callback_data->suspend_conditional), &(callback_data->suspend_mutex));
	}

	pthread_mutex_unlock(&(callback_data->suspend_mutex));
}


static void process_callback_list()
{
	GSList* original_top = NULL;
	GSList* cur_item     = NULL;

	lock_callback_data_list_mutex();
	original_top = callback_data_list;
	cur_item     = callback_data_list;
	callback_data_list = NULL;
	unlock_callback_data_list_mutex();

	for ( ; cur_item; cur_item = cur_item->next)
	{
		CallbackData *this_callback_data = cur_item->data;

		switch (this_callback_data->callback_type)
		{
			case RUN_PROCEDURE_CALLBACK:
				process_run_procedure_callback(this_callback_data);
				break;
			default:
				fprintf(stderr, "JGimp internal error: invalid callback type %d\n", this_callback_data->callback_type);
		}
		pthread_mutex_lock(&(this_callback_data->suspend_mutex));
		this_callback_data->is_finished = TRUE;
		pthread_cond_signal(&(this_callback_data->suspend_conditional));
		pthread_mutex_unlock(&(this_callback_data->suspend_mutex));
	}
	g_slist_free(original_top);
}


static void lock_callback_data_list_mutex()
{
#ifndef _WIN32
	pthread_mutex_lock(&callback_data_list_mutex);
#else
	if (callback_data_list_mutex == NULL)
	{
		callback_data_list_mutex = CreateMutex(NULL, FALSE, NULL);
		if (callback_data_list_mutex == NULL)
		{
			fprintf(stderr, "Error creating new callback_data_list_mutex in lock_callback_data_list_mutex\n");
			return;
		}
	}
	WaitForSingleObject(callback_data_list_mutex, INFINITE);
#endif /* _WIN32 */
}

static void unlock_callback_data_list_mutex()
{
#ifndef _WIN32
	pthread_mutex_unlock(&callback_data_list_mutex);
#else
	ReleaseMutex(callback_data_list_mutex);
#endif /* _WIN32 */
}

