/* 
 * 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 handles plug-in requests from the GIMP and sends them on to
 * the proxy
 */

/* 
 * The variables below are used to keep track of data that has been returned
 * to the GIMP, but which should be deallocated when it is no longer needed.
 */

#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 <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <jni.h>

#include "jgimp_constants.h"
#include "jgimp_plugin_handler.h"
#include "jgimp_data_conversions.h"
#include "jgimp_jni_utils.h"

#ifndef _WIN32
 static pthread_mutex_t* return_params_stack_mutex = NULL;
#else
 HANDLE return_params_stack_mutex = NULL;
#endif

/* Wrapper functions to make the code a bit cleaner across ports */
static void lock_return_params_stack_mutex();
static void unlock_return_params_stack_mutex();

static int return_params_stack_count              = 0;
static GSList* return_params_list                 = NULL;

struct ParamListItem
{
	int                          num_items;
	struct ConvertedGimpParam**  converted_array;
	GimpParam*                   return_array;
};

void increment_plugin_stack(); /* Called when "run" is called */
void decrement_plugin_stack(); /* Called when "run" is done   */
/*
 * Appends data returned to the GIMP to a list so that
 * it can be deallocated when no longer needed
 */
void append_return_params(struct ParamListItem* return_set);

void handle_java_plug_in(
	gchar      *name, 
	gint        n_params, 
	GimpParam  *param, 
	gint       *nreturn_vals,
	GimpParam **return_vals)
{
	struct ParamListItem* return_set      = NULL;

	static GimpParam error_return_vals[1];

	JNIEnv      *env                      = NULL; 
	jclass       java_plug_in_class_ID    = NULL;
	jmethodID    method_ID                = NULL;
	jobject      java_plug_in_object      = NULL;
	jclass       jgimp_data_class_ID      = NULL;
	jobjectArray param_array              = NULL;
	jobject      return_values            = NULL;
	int          i                        = 0;
	jthrowable   throwable                = NULL;
	int          last_int32_value         = 0;
	jclass       return_values_class_ID   = NULL;
	jobject      pdb_status_object        = NULL;
	jclass       pdb_status_class_ID      = NULL;
	int          return_status            = 0;
	jobjectArray return_array             = NULL;

	env = jni_utils_attach_thread();
	if (!env)
	{
		fprintf(stderr, "JGimp error: Couldn't attach thread in handle_java_plug_in\n");
		error_return_vals[0].type = GIMP_PDB_STATUS;
		error_return_vals[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
		(*nreturn_vals) = 1;
		(*return_vals)  = error_return_vals;
		return;
	}

	increment_plugin_stack();

	/* Get JavaPlugInProxy object */
	java_plug_in_class_ID = (*env)->FindClass(env, JGIMP_PLUG_IN_PROXY_CLASS);
	if (java_plug_in_class_ID == NULL)
	{
		fprintf(stderr, "JGimp error: Couldn't get JGimpPlugInProxy class ID in handle_java_plug_in\n");
		goto cleanup;
	}
	method_ID = (*env)->GetStaticMethodID(env, java_plug_in_class_ID, "_getInstance", "()Lorg/gimp/jgimp/proxy/JGimpPlugInProxy;");
	if (method_ID == NULL)
	{
		fprintf(stderr, "JGimp error: Couldn't get JGimpPlugInProxy _getInstance ID in handle_java_plug_in\n");
		goto cleanup;
	}
	java_plug_in_object = (*env)->CallStaticObjectMethod(env, java_plug_in_class_ID, method_ID);
	if (java_plug_in_object == NULL)
	{
		fprintf(stderr, "JGimp error: Couldn't get reference to JGimpPlugInProxy in handle_java_plug_in\n");
		goto cleanup;
	}

	/* Create param array */
	jgimp_data_class_ID = (*env)->FindClass(env, JGIMP_DATA_CLASS);
	if (jgimp_data_class_ID == NULL)
	{
		fprintf(stderr, "JGimp internal error: Couldn't find class ID for JGimpData in handle_java_plug_in\n");
		goto cleanup;
	}
	param_array = (*env)->NewObjectArray(env, n_params, jgimp_data_class_ID, NULL);
	if (param_array == NULL)
	{
		fprintf(stderr, "JGimp internal error: Couldn't create param array in handle_java_plug_in\n");
		goto cleanup;
	}
	last_int32_value = 0;
	for (i = 0; i < n_params; i++)
	{
		jobject this_param = GimpParam_to_JGimpData(env, &(param[i]), last_int32_value);
		if (this_param == NULL)
		{
			fprintf(stderr, "JGimp internal error: Couldn't allocate JGimpData object in handle_java_plug_in\n");
			goto cleanup;
		}

		(*env)->SetObjectArrayElement(env, param_array, i, this_param);
		(*env)->DeleteLocalRef(env, this_param);
		this_param = NULL;

		/* Update, in case this param was the length of an upcoming array... */
		last_int32_value = param[i].data.d_int32;
	}

	/* Get method ID, call it */
	method_ID = (*env)->GetMethodID(env, java_plug_in_class_ID, "_executeJavaPlugIn", "(Ljava/lang/String;[Lorg/gimp/jgimp/JGimpData;)Lorg/gimp/jgimp/plugin/JGimpPlugInReturnValues;");
	if (method_ID == NULL)
	{
		fprintf(stderr, "JGimp internal error: Couldn't get _executeJavaPlugIn method ID in handle_java_plug_in\n");
		goto cleanup;
	}
	return_values = (*env)->CallObjectMethod(env, java_plug_in_object, method_ID, (*env)->NewStringUTF(env, name), param_array);
	throwable = (*env)->ExceptionOccurred(env);
	if (throwable)
	{
		fprintf(stderr, "JGimp: Caught exception calling _executeJavaPlugIn in handle_java_plug_in\n");
		(*env)->ExceptionDescribe(env);
		(*env)->ExceptionClear(env);
		goto cleanup;
	}

	/* Convert return values and put in array */
	if (return_values == NULL)
	{
		fprintf(stderr, "JGimp internal error: no return values returned in handle_java_plug_in\n");
		goto cleanup;
	}

	/* Get the return status */
	return_values_class_ID = (*env)->FindClass(env, JGIMP_PLUG_IN_RETURN_VALUES_CLASS);
	if (return_values_class_ID == NULL)
	{
		fprintf(stderr, "JGimp: problem getting class ID for JGimpPlugInReturnValues in handle_java_plug_in\n");
		goto cleanup;
	}
	method_ID = (*env)->GetMethodID(env, return_values_class_ID, "getReturnStatus", "()Lorg/gimp/jgimp/nativewrappers/JGimpPDBStatus;");
	if (method_ID == NULL)
	{
		fprintf(stderr, "JGimp: problem getting method ID for getReturnStatus in handle_java_plug_in\n");
		goto cleanup;
	}
	pdb_status_object = (*env)->CallObjectMethod(env, return_values, method_ID);
	if (pdb_status_object == NULL)
	{
		fprintf(stderr, "JGimp: problem getting return status object in handle_java_plug_in\n");
		goto cleanup;
	}
	pdb_status_class_ID = (*env)->FindClass(env, JGIMP_PDB_STATUS_CLASS);
	if (pdb_status_class_ID == NULL)
	{
		fprintf(stderr, "JGimp: problem getting JGimpPDBStatus class ID in handle_java_plug_in\n");
		goto cleanup;
	}
	method_ID = (*env)->GetMethodID(env, pdb_status_class_ID, "getStatus", "()I");
	if (method_ID == NULL)
	{
		fprintf(stderr, "JGimp: problem getting getStatus method ID for JGimpPDBStatus class in handle_java_plug_in\n");
		goto cleanup;
	}
	return_status = (*env)->CallIntMethod(env, pdb_status_object, method_ID);


	/* Get JGimpData array */
	method_ID = (*env)->GetMethodID(env, return_values_class_ID, "getReturnData", "()[Lorg/gimp/jgimp/JGimpData;");
	if (method_ID == NULL)
	{
		fprintf(stderr, "JGimp: problem getting getReturnData method ID for JGimpPlugInReturnValues class in handle_java_plug_in\n");
		goto cleanup;
	}
	return_array = (*env)->CallObjectMethod(env, return_values, method_ID);
	if (return_array == NULL)
	{
		fprintf(stderr, "JGimp: problem getting getReturnData method ID for JGimpPlugInReturnValues class in handle_java_plug_in\n");
		goto cleanup;
	}

	/* Convert values */
	return_set = g_new(struct ParamListItem, 1);
	return_set->num_items = (*env)->GetArrayLength(env, return_array) + 1; /* Add 1 for the status object */
	return_set->converted_array = g_new(struct ConvertedGimpParam*, return_set->num_items);
	return_set->return_array = g_new(GimpParam, return_set->num_items);

	/* Convert the status object */
	(return_set->converted_array)[0] = JGimpData_to_GimpParam(env, pdb_status_object);
	if ((return_set->converted_array)[0] == NULL)
	{
		fprintf(stderr, "JGimp internal error: could not convert PDB status object in handle_java_plug_in\n");
		g_free(return_set->return_array);
		g_free(return_set->converted_array);
		return_set->return_array = NULL;
		return_set->converted_array = NULL;
		g_free(return_set);
		return_set = NULL;
		goto cleanup;
	}
	(return_set->return_array)[0] = (return_set->converted_array)[0]->param;

	/* Convert the data array */
	for (i = 1; i < return_set->num_items; i++)
	{
		jobject this_param = (*env)->GetObjectArrayElement(env, return_array, i - 1);
		(return_set->converted_array)[i] = JGimpData_to_GimpParam(env, this_param);
		if ((return_set->converted_array)[i] == NULL)
		{
			int j = 0;

			fprintf(stderr, "JGimp internal error: could not convert param object in handle_java_plug_in\n");
			for (j = 0; j < i; j++)
			{
				free_ConvertedGimpParam((return_set->converted_array)[j]);
			}
			g_free(return_set->return_array);
			g_free(return_set->converted_array);
			return_set->return_array = NULL;
			return_set->converted_array = NULL;
			g_free(return_set);
			return_set = NULL;
			goto cleanup;
		}
		(return_set->return_array)[i] = (return_set->converted_array)[i]->param;
	}

	/* All done... add the list item to our stack list, set the return pointers, decrement the stack counter */
	append_return_params(return_set);
	(*nreturn_vals) = return_set->num_items;
	(*return_vals) = return_set->return_array;
	decrement_plugin_stack();
	jni_utils_detach_thread();
	return;

cleanup:
	throwable = (*env)->ExceptionOccurred(env);
	if (throwable)
	{
		fprintf(stderr, "JGimp: Caught exception in jgimp.handle_java_plug_in\n");
		(*env)->ExceptionDescribe(env);
		(*env)->ExceptionClear(env);
	}
	error_return_vals[0].type = GIMP_PDB_STATUS;
	error_return_vals[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
	(*nreturn_vals) = 1;
	(*return_vals)  = error_return_vals;

	decrement_plugin_stack();
	jni_utils_detach_thread();
}

void increment_plugin_stack()
{
	if (return_params_stack_mutex == NULL)
	{
#ifndef _WIN32
		return_params_stack_mutex = g_new(pthread_mutex_t, 1);
		if (return_params_stack_mutex == NULL)
		{
			fprintf(stderr, "Error creating new param mutex in jgimp.c\n");
			return;
		}
		pthread_mutex_init(return_params_stack_mutex, NULL);
#else
		return_params_stack_mutex = CreateMutex(NULL, FALSE, NULL);
		if (return_params_stack_mutex == NULL)
		{
			fprintf(stderr, "Error creating new param mutex in jgimp.c\n");
			return;
		}
#endif /* _WIN32 */
	}

	lock_return_params_stack_mutex();

	if ((return_params_stack_count == 0) && (return_params_list != NULL))
	{
		/* delete all old return params */
		unsigned int i = 0;

		for (i = 0; i < g_slist_length(return_params_list); i++)
		{
			struct ParamListItem* return_set = g_slist_nth_data(return_params_list, i);
			int j = 0;

			if (return_set == NULL)
			{
				fprintf(stderr, "JGimp internal error in increment_plugin_stack: return_set in list is NULL\n");
			}
			else
			{
				for (j = 0; j < return_set->num_items; j++)
				{
					free_ConvertedGimpParam((return_set->converted_array)[j]);
				}
				g_free(return_set->converted_array);
				g_free(return_set->return_array);
			}
		}
		g_slist_free(return_params_list);
		return_params_list = NULL;
	}
	return_params_stack_count++;
	unlock_return_params_stack_mutex();
}
void decrement_plugin_stack()
{
	if (return_params_stack_mutex == NULL)
	{
		fprintf(stderr, "Error: attempting to decrement without first incrementing\n");
		return;
	}
	lock_return_params_stack_mutex();
	return_params_stack_count--;
	if (return_params_stack_count < 0)
	{
		return_params_stack_count = 0;
		fprintf(stderr, "Error: decrementing without corresponding increment\n");
	}
	unlock_return_params_stack_mutex();
}
void append_return_params(struct ParamListItem* return_set)
{
	if (return_params_stack_mutex == NULL)
	{
		fprintf(stderr, "Error: attempting to append return params without first incrementing\n");
		return;
	}
	lock_return_params_stack_mutex();
	return_params_list = g_slist_append(return_params_list, return_set);
	unlock_return_params_stack_mutex();
}

static void lock_return_params_stack_mutex()
{
#ifndef _WIN32
	pthread_mutex_lock(return_params_stack_mutex);
#else
	WaitForSingleObject(return_params_stack_mutex, INFINITE);
#endif /* _WIN32 */
}

static void unlock_return_params_stack_mutex()
{
#ifndef _WIN32
	pthread_mutex_unlock(return_params_stack_mutex);
#else
	ReleaseMutex(return_params_stack_mutex);
#endif /* _WIN32 */
}
