#include "com_millistream_www_Mdf.h"
#include "mdf-java.h"

/* varios cached data that we fetch when the Mdf and/or Message class is loaded
 * safe to cache because their values are static throught the lifetime of the class 
*/

jfieldID handle_fid;  /* cached FieldID of "handle" member of Mdf class */
jfieldID message_fid; /* cached FieldID of "handle" member of Message class */

jmethodID al_init_mid; /* cached MethodID of ArrayList constructor */
jmethodID al_add_mid; /* cached MethodID of ArrayList add(Object) method */
jmethodID mh_init_mid; /* cached MethodID for the MesssageHeader constructor */
jmethodID mh_init2_mid; /* cached MethodID for the MesssageHeader constructor with fields ArrayList */
jmethodID mf_init_mid; /* cached MethodID for the MesssageField constructor */

/* called when the Mdf class is loaded so that we can cache the fid of the "handle" member */
JNIEXPORT void JNICALL Java_com_millistream_www_Mdf_initialize (JNIEnv *env, jclass cls)
{
	handle_fid = (*env)->GetFieldID(env, cls, "handle", "J");

	cls = (*env)->FindClass(env, "com/millistream/www/MessageHeader");
	mh_init_mid = (*env)->GetMethodID(env, cls, "<init>", "(SJJS)V");
	mh_init2_mid = (*env)->GetMethodID(env, cls, "<init>", "(SJJSLjava/util/ArrayList;)V");

	cls = (*env)->FindClass(env, "com/millistream/www/MessageField");
	mf_init_mid = (*env)->GetMethodID(env, cls, "<init>", "(ILjava/lang/String;)V");

	cls = (*env)->FindClass(env, "java/util/ArrayList");
	al_init_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
	al_add_mid = (*env)->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_create(JNIEnv *env, jobject job)
{
	mdf_t handle = mdf_create();

	if (handle == 0)
		return JNI_FALSE;

	(*env)->SetLongField(env, job, handle_fid, (jlong) handle);

	return JNI_TRUE;
}

JNIEXPORT void JNICALL Java_com_millistream_www_Mdf_destroy (JNIEnv * env, jobject job)
{
	mdf_t handle = (mdf_t)((*env)->GetLongField(env, job, handle_fid));

	if (handle == 0)
		return;

	struct callback_data *cd;

	/* destroy the per callback cached data */
	if (mdf_get_property (handle, MDF_OPT_DATA_CALLBACK_USERDATA, &cd) == 1 && cd != NULL) {
		(*env)->DeleteGlobalRef (env, cd->obj);
		(*env)->DeleteGlobalRef (env, cd->obj2);
		free (cd);
	}

	if (mdf_get_property (handle, MDF_OPT_STATUS_CALLBACK_USERDATA, &cd) == 1 && cd != NULL) {
		(*env)->DeleteGlobalRef (env, cd->obj);
		free (cd);
	}

	mdf_destroy(handle);
	(*env)->SetLongField(env, job, handle_fid, 0);
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_connect(JNIEnv *env, jobject job, jstring servers)
{
	const char *str = (*env)->GetStringUTFChars(env, servers, NULL);

	if (str == NULL)
		return JNI_FALSE; /* OutOfMemoryError already thrown */

	int res = mdf_connect((mdf_t)((*env)->GetLongField (env, job, handle_fid)), str);

	(*env)->ReleaseStringUTFChars(env, servers, str);

	if (res == 1)
		return JNI_TRUE;

	return JNI_FALSE;
}

JNIEXPORT void JNICALL Java_com_millistream_www_Mdf_disconnect (JNIEnv *env, jobject job)
{
	mdf_disconnect((mdf_t)((*env)->GetLongField (env, job, handle_fid)));
}

JNIEXPORT jint JNICALL Java_com_millistream_www_Mdf_consume(JNIEnv *env, jobject job, jint timeout)
{
	return mdf_consume((mdf_t)((*env)->GetLongField (env, job, handle_fid)), timeout);
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_send(JNIEnv *env, jobject mdf_obj, jobject message_obj)
{
	if (mdf_message_send ((mdf_t)((*env)->GetLongField (env, mdf_obj, handle_fid)), (mdf_message_t)((*env)->GetLongField (env, message_obj, message_fid))) == 1)
		return JNI_TRUE;

	return JNI_FALSE;
}

JNIEXPORT jobject JNICALL Java_com_millistream_www_Mdf_get_1next_1message(JNIEnv *env, jobject job)
{
	mdf_t handle = (mdf_t)((*env)->GetLongField (env, job, handle_fid));

	uint16_t mref;
	uint64_t insref;
	const int result = mdf_get_next_message2 (handle, &mref, &insref);

	if (result == 0)
		return NULL;

	jobject mh = (*env)->NewObject(env, (*env)->FindClass(env, "com/millistream/www/MessageHeader"), mh_init_mid, (jshort)mref, (jlong)mdf_get_mclass (handle), (jlong)insref, (jshort)mdf_get_delay (handle));
	return mh;
}

JNIEXPORT jobject JNICALL Java_com_millistream_www_Mdf_get_1next_1field(JNIEnv *env, jobject job)
{
	uint32_t tag;
	char* value;

	int result = mdf_get_next_field((mdf_t)((*env)->GetLongField (env, job, handle_fid)), &tag, &value);
	if (result == 0)
		return NULL;

	jstring jstr = (*env)->NewStringUTF(env, value);

	jobject mf = (*env)->NewObject(env, (*env)->FindClass(env, "com/millistream/www/MessageField"), mf_init_mid, tag, jstr);
	return mf;
}

JNIEXPORT jobject JNICALL Java_com_millistream_www_Mdf_getMessagesAndFields (JNIEnv *env, jobject job)
{
	jclass mh_class = (*env)->FindClass(env, "com/millistream/www/MessageHeader");
	jclass mf_class = (*env)->FindClass(env, "com/millistream/www/MessageField");
	jclass al_class = (*env)->FindClass(env, "java/util/ArrayList");

	mdf_t handle = (mdf_t)((*env)->GetLongField (env, job, handle_fid));

	/* create ArrayList object for the messages */
	jobject al_messages = (*env)->NewObject(env, al_class, al_init_mid);
	if (al_messages == NULL)
		return NULL;

	uint16_t mref;
	uint64_t insref;

	while (mdf_get_next_message2 (handle, &mref, &insref) != 0) {
		uint32_t tag;
		char* value;

		/* create ArrayList object for the fields */
		jobject al_fields = (*env)->NewObject(env, al_class, al_init_mid);
		if (al_fields == NULL)
			return NULL;

		while (mdf_get_next_field (handle, &tag, &value) != 0) {
			jobject field = (*env)->NewObject(env, mf_class, mf_init_mid, tag, (*env)->NewStringUTF(env, value));
			if (field == NULL)
				return NULL;

			if ((*env)->CallBooleanMethod (env, al_fields, al_add_mid, field) == JNI_FALSE)
				return NULL;
		}

		jobject messageHeader = (*env)->NewObject(env, mh_class, mh_init2_mid, (jshort)mref, (jlong)mdf_get_mclass (handle), (jlong)insref, (jshort)mdf_get_delay (handle), al_fields);
		if (messageHeader == NULL)
			return NULL;

		if ((*env)->CallBooleanMethod (env, al_messages, al_add_mid, messageHeader) == JNI_FALSE)
			return NULL;
	}

	return al_messages;
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_setTimeout(JNIEnv *env, jobject job, jint value)
{
	if (mdf_set_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_CONNECT_TIMEOUT, &value) == 1)
		return JNI_TRUE;

	return JNI_FALSE;
}

JNIEXPORT jint JNICALL Java_com_millistream_www_Mdf_getTimeout(JNIEnv *env, jobject job)
{
	jint value = 0;
	mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_CONNECT_TIMEOUT, &value);
	return value;
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_setHeartbeatInterval(JNIEnv *env, jobject job, jint value)
{
	if (mdf_set_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_HEARTBEAT_INTERVAL, &value) == 1)
		return JNI_TRUE;

	return JNI_FALSE;
}

JNIEXPORT jint JNICALL Java_com_millistream_www_Mdf_getHeartbeatInterval(JNIEnv *env, jobject job)
{
	jint value = 0;
	mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_HEARTBEAT_INTERVAL, &value);
	return value;
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_setHeartbeatMaxMissed(JNIEnv *env, jobject job, jint value)
{
	if (mdf_set_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_HEARTBEAT_MAX_MISSED, &value) == 1)
		return JNI_TRUE;

	return JNI_FALSE;
}

JNIEXPORT jint JNICALL Java_com_millistream_www_Mdf_getHeartbeatMaxMissed(JNIEnv *env, jobject job)
{
	jint value = 0;
	mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_HEARTBEAT_MAX_MISSED, &value);
	return value;
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_setTCPNoDelay(JNIEnv *env, jobject job, jint value)
{
	if (mdf_set_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_TCP_NODELAY, &value) == 1)
		return JNI_TRUE;

	return JNI_FALSE;
}

JNIEXPORT jint JNICALL Java_com_millistream_www_Mdf_getTCPNoDelay(JNIEnv *env,jobject job)
{
	jint value = 0;
	mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_TCP_NODELAY, &value);
	return value;
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_setNoEncryption(JNIEnv *env, jobject job, jint value)
{
	if (mdf_set_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_NO_ENCRYPTION, &value) == 1)
		return JNI_TRUE;

	return JNI_FALSE;
}

JNIEXPORT jint JNICALL Java_com_millistream_www_Mdf_getNoEncryption(JNIEnv *env, jobject job)
{
	jint value = 0;
	mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_NO_ENCRYPTION, &value);
	return value;
}

JNIEXPORT jint JNICALL Java_com_millistream_www_Mdf_getTimeDifference(JNIEnv *env, jobject job)
{
	jint value = 0;
	mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_TIME_DIFFERENCE, &value);
	return value;
}

JNIEXPORT jlong JNICALL Java_com_millistream_www_Mdf_getTimeDifferenceNS(JNIEnv *env, jobject job)
{
	jlong value = 0;
	mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_TIME_DIFFERENCE_NS, &value);
	return value;
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_setReceivedBytes(JNIEnv *env, jobject job, jlong value)
{
	if (mdf_set_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_RCV_BYTES, &value) == 1)
		return JNI_TRUE;

	return JNI_FALSE;
}

JNIEXPORT jlong JNICALL Java_com_millistream_www_Mdf_getReceivedBytes(JNIEnv *env, jobject job)
{
	jlong value = 0;
	mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_RCV_BYTES, &value);
	return value;
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_setSentBytes(JNIEnv *env, jobject job, jlong value)
{
	if (mdf_set_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_SENT_BYTES, &value) == 1)
		return JNI_TRUE;

	return JNI_FALSE;
}

JNIEXPORT jlong JNICALL Java_com_millistream_www_Mdf_getSentBytes(JNIEnv *env,jobject job)
{
	jlong value = 0;
	mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_SENT_BYTES, &value);
	return value;
}

JNIEXPORT jint JNICALL Java_com_millistream_www_Mdf_getError(JNIEnv *env,jobject job)
{
	jint value = 0;
	mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_ERROR, &value);
	return value;
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_setBindAddress (JNIEnv *env, jobject job, jstring address)
{
	if (address == NULL) {
		if (mdf_set_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_BIND_ADDRESS, NULL) == 1)
			return JNI_TRUE;

		return JNI_FALSE;
	}

	const char *str = (*env)->GetStringUTFChars(env, address, NULL);

	if (str == NULL)
		return JNI_FALSE; /* OutOfMemoryError already thrown */

	if (mdf_set_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_BIND_ADDRESS, (void *)str) == 1) {
		(*env)->ReleaseStringUTFChars(env, address, str);
		return JNI_TRUE;
	}

	(*env)->ReleaseStringUTFChars(env, address, str);

	return JNI_FALSE;
}

JNIEXPORT jstring JNICALL Java_com_millistream_www_Mdf_getBindAddress(JNIEnv *env, jobject job)
{
	char *value;
	if (mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_BIND_ADDRESS, &value) == 0)
		return NULL;

	return (*env)->NewStringUTF(env, value);
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_setCiphers (JNIEnv *env, jobject job, jstring ciphers)
{
	if (ciphers == NULL) {
		if (mdf_set_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_CRYPT_CIPHERS, NULL) == 1)
			return JNI_TRUE;

		return JNI_FALSE;
	}

	const char *str = (*env)->GetStringUTFChars(env, ciphers, NULL);

	if (str == NULL)
		return JNI_FALSE; /* OutOfMemoryError already thrown */

	if (mdf_set_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_CRYPT_CIPHERS, (void *)str) == 1) {
		(*env)->ReleaseStringUTFChars(env, ciphers, str);
		return JNI_TRUE;
	}

	(*env)->ReleaseStringUTFChars(env, ciphers, str);

	return JNI_FALSE;
}

JNIEXPORT jstring JNICALL Java_com_millistream_www_Mdf_getCiphers(JNIEnv *env, jobject job)
{
	char *value;
	if (mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_CRYPT_CIPHERS, &value) == 0)
		return NULL;

	return (*env)->NewStringUTF(env, value);
}

JNIEXPORT jstring JNICALL Java_com_millistream_www_Mdf_getCipher(JNIEnv *env, jobject job)
{
	char *value;
	if (mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_CRYPT_CIPHER, &value) == 0)
		return NULL;

	return (*env)->NewStringUTF(env, value);
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_setDigests (JNIEnv *env, jobject job, jstring digests)
{
	if (digests == NULL) {
		if (mdf_set_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_CRYPT_CIPHERS, NULL) == 1)
			return JNI_TRUE;

		return JNI_FALSE;
	}

	const char *str = (*env)->GetStringUTFChars(env, digests, NULL);

	if (str == NULL)
		return JNI_FALSE; /* OutOfMemoryError already thrown */

	if (mdf_set_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_CRYPT_DIGESTS, (void *)str) == 1) {
		(*env)->ReleaseStringUTFChars(env, digests, str);
		return JNI_TRUE;
	}

	(*env)->ReleaseStringUTFChars(env, digests, str);

	return JNI_FALSE;
}

JNIEXPORT jstring JNICALL Java_com_millistream_www_Mdf_getDigests(JNIEnv *env, jobject job)
{
	char *value;
	if (mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_CRYPT_DIGESTS, &value) == 0)
		return NULL;

	return (*env)->NewStringUTF(env, value);
}

JNIEXPORT jstring JNICALL Java_com_millistream_www_Mdf_getDigest(JNIEnv *env, jobject job)
{
	char *value;
	if (mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_CRYPT_DIGEST, &value) == 0)
		return NULL;

	return (*env)->NewStringUTF(env, value);
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_setHandleDelay(JNIEnv *env, jobject job, jint value)
{
	if (mdf_set_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_HANDLE_DELAY, &value) == 1)
		return JNI_TRUE;

	return JNI_FALSE;
}

JNIEXPORT jint JNICALL Java_com_millistream_www_Mdf_getHandleDelay(JNIEnv *env,jobject job)
{
	jint value = 0;
	mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_HANDLE_DELAY, &value);
	return value;
}

JNIEXPORT jstring JNICALL Java_com_millistream_www_Mdf_getConnectedHostName(JNIEnv *env, jobject job)
{
	char *value;
	if (mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_CONNECTED_HOST, &value) == 0)
		return NULL;

	return (*env)->NewStringUTF(env, value);
}

JNIEXPORT jstring JNICALL Java_com_millistream_www_Mdf_getConnectedHostIP(JNIEnv *env, jobject job)
{
	char *value;
	if (mdf_get_property((mdf_t)((*env)->GetLongField (env, job, handle_fid)), MDF_OPT_CONNECTED_IP, &value) == 0)
		return NULL;

	return (*env)->NewStringUTF(env, value);
}

static void consume_callback (void *userdata, mdf_t handle)
{
	struct callback_data *cd = userdata;
	(*cd->env)->CallVoidMethod (cd->env, cd->obj, cd->mid, cd->obj2);
}

static void status_callback (void *userdata, MDF_CONN_STATUS status, const char *host, const char *ip)
{
	struct callback_data *cd = userdata;

	jclass statusCode_class = (*cd->env)->FindClass(cd->env, "com/millistream/www/Callback$StatusCode");
	if (statusCode_class == NULL)
		return;

	jfieldID statusCode_fid;
	switch (status) {
		case MDF_STATUS_LOOKUP       : statusCode_fid = (*cd->env)->GetStaticFieldID(cd->env, statusCode_class, "LOOKUP", "Lcom/millistream/www/Callback$StatusCode;"); break;
		case MDF_STATUS_CONNECTING   : statusCode_fid = (*cd->env)->GetStaticFieldID(cd->env, statusCode_class, "CONNECTING", "Lcom/millistream/www/Callback$StatusCode;"); break;
		case MDF_STATUS_CONNECTED    : statusCode_fid = (*cd->env)->GetStaticFieldID(cd->env, statusCode_class, "CONNECTED", "Lcom/millistream/www/Callback$StatusCode;"); break;
		case MDF_STATUS_DISCONNECTED : statusCode_fid = (*cd->env)->GetStaticFieldID(cd->env, statusCode_class, "DISCONNECTED", "Lcom/millistream/www/Callback$StatusCode;"); break;
		case MDF_STATUS_READYTOLOGON : statusCode_fid = (*cd->env)->GetStaticFieldID(cd->env, statusCode_class, "READYTOLOGON", "Lcom/millistream/www/Callback$StatusCode;"); break;
		case MDF_STATUS_SND_HB_REQ : statusCode_fid = (*cd->env)->GetStaticFieldID(cd->env, statusCode_class, "SND_HB_REQ", "Lcom/millistream/www/Callback$StatusCode;"); break;
		case MDF_STATUS_RCV_HB_REQ : statusCode_fid = (*cd->env)->GetStaticFieldID(cd->env, statusCode_class, "RCV_HB_REQ", "Lcom/millistream/www/Callback$StatusCode;"); break;
		case MDF_STATUS_RCV_HB_RES : statusCode_fid = (*cd->env)->GetStaticFieldID(cd->env, statusCode_class, "RCV_HB_RES", "Lcom/millistream/www/Callback$StatusCode;"); break;
		default: return;
	}

	jobject statusCode = (*cd->env)->GetStaticObjectField (cd->env, statusCode_class, statusCode_fid);
	if (statusCode == NULL)
		return;

	jstring jstr_host = (*cd->env)->NewStringUTF (cd->env, host);
	jstring jstr_ip = (*cd->env)->NewStringUTF (cd->env, ip);

	(*cd->env)->CallVoidMethod (cd->env, cd->obj, cd->mid, statusCode, jstr_host, jstr_ip);
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_setConsumeDataCallback (JNIEnv *env, jobject job, jobject job2)
{
	mdf_t handle = (mdf_t)((*env)->GetLongField (env, job, handle_fid));

	struct callback_data *cd;

	if (mdf_get_property (handle, MDF_OPT_DATA_CALLBACK_USERDATA, &cd) == 0)
		return JNI_FALSE;

	if (cd == NULL) {
		cd = malloc (sizeof *cd);
		if (cd == NULL)
			return JNI_FALSE;
	} else {
		/* first we remove the current status callback function in case we
		 * encounter any errors later */
		mdf_set_property (handle, MDF_OPT_DATA_CALLBACK_USERDATA, NULL);
		mdf_set_property (handle, MDF_OPT_DATA_CALLBACK_FUNCTION, NULL);
		(*env)->DeleteGlobalRef (env, cd->obj);
		(*env)->DeleteGlobalRef (env, cd->obj2);
	}

	cd->env = env;
	cd->obj = (*env)->NewGlobalRef (env, job2); /* job2 cannot be used after this! */
	cd->obj2 = (*env)->NewGlobalRef (env, job); /* job cannot be used after this! */

	if (cd->obj == NULL || cd->obj2 == NULL) {
		if (cd->obj != NULL)
			(*env)->DeleteGlobalRef (env, cd->obj);

		if (cd->obj2 != NULL)
			(*env)->DeleteGlobalRef (env, cd->obj2);

		free (cd);

		/* where we called in order to unset the callback? */
		if (cd->obj == NULL)
			return JNI_TRUE;

		return JNI_FALSE;
	}

	jclass cls2 = (*env)->GetObjectClass (env, cd->obj);
	if (cls2 == NULL) {
		(*env)->DeleteGlobalRef (env, cd->obj);
		(*env)->DeleteGlobalRef (env, cd->obj2);
		free (cd);
		return JNI_FALSE;
	}

	cd->mid = (*env)->GetMethodID (env, cls2, "consumeData", "(Lcom/millistream/www/Mdf;)V");
	if (cd->mid == NULL) {
		(*env)->DeleteGlobalRef (env, cd->obj);
		(*env)->DeleteGlobalRef (env, cd->obj2);
		free (cd);
		return JNI_FALSE;
	}

	mdf_set_property (handle, MDF_OPT_DATA_CALLBACK_USERDATA, cd);
	mdf_set_property (handle, MDF_OPT_DATA_CALLBACK_FUNCTION, consume_callback);

	return JNI_TRUE;
}

JNIEXPORT jboolean JNICALL Java_com_millistream_www_Mdf_setStatusChangeCallback (JNIEnv *env, jobject job, jobject job2)
{
	mdf_t handle = (mdf_t)((*env)->GetLongField (env, job, handle_fid));

	struct callback_data *cd;

	if (mdf_get_property (handle, MDF_OPT_STATUS_CALLBACK_USERDATA, &cd) == 0)
		return JNI_FALSE;

	if (cd == NULL) {
		cd = malloc (sizeof *cd);
		if (cd == NULL)
			return JNI_FALSE;
	} else {
		/* first we remove the current status callback function in case we
		 * encounter any errors later */
		mdf_set_property (handle, MDF_OPT_STATUS_CALLBACK_USERDATA, NULL);
		mdf_set_property (handle, MDF_OPT_STATUS_CALLBACK_FUNCTION, NULL);
		(*env)->DeleteGlobalRef (env, cd->obj);
	}

	cd->env = env;
	cd->obj = (*env)->NewGlobalRef (env, job2); /* job2 cannot be used after this! */

	if (cd->obj == NULL) {
		free (cd);

		/* we where called in order to unset the callback */
		return JNI_TRUE;
	}

	jclass cls2 = (*env)->GetObjectClass (env, cd->obj);
	if (cls2 == NULL) {
		(*env)->DeleteGlobalRef (env, cd->obj);
		free (cd);
		return JNI_FALSE;
	}

	cd->mid = (*env)->GetMethodID (env, cls2, "statusChange", "(Lcom/millistream/www/Callback$StatusCode;Ljava/lang/String;Ljava/lang/String;)V");
	if (cd->mid == NULL) {
		(*env)->DeleteGlobalRef (env, cd->obj);
		free (cd);
		return JNI_FALSE;
	}

	mdf_set_property (handle, MDF_OPT_STATUS_CALLBACK_USERDATA, cd);
	mdf_set_property (handle, MDF_OPT_STATUS_CALLBACK_FUNCTION, status_callback);

	return JNI_TRUE;
}

