/**********************************************************************
 * Copyright (c) 2008 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * $Id: tptpNativeCall.cpp,v 1.4 2008/02/06 12:41:00 ialelekov Exp $
 * 
 * Contributors: 
 * IBM - Initial API and implementation
 **********************************************************************/

#include "tptpJSSELog.h"


#include "tptpNativeCall.h"

#include "tptpConfig.h"

extern "C" {
	#include "tptp/TPTPJava.h"
}

#include <stdlib.h>
#include <jni.h>

int tptpAttachCurrentThread(JNIEnv** jenv);
int tptpDetachCurrentThread();

#define CLASS_NAME_JAVA "org.eclipse.tptp.platform.agentcontroller.jsse.SSLNativeBinding"
#define CLASS_NAME_NATIVE "org/eclipse/tptp/platform/agentcontroller/jsse/SSLNativeBinding"

static jobject objBinding = NULL;

static char * jsseClassPath = NULL;

static bool JSSE_NATIVE_CALL_DEBUG = false;

static jmethodID mthNativeAccept = NULL;
static jmethodID mthNativeClose = NULL;
static jmethodID mthNativeHandshake = NULL;
static jmethodID mthNativeRead = NULL;
static jmethodID mthNativeWrite = NULL;
static jmethodID mthNativeShutdown = NULL;

static JavaVM *vm = NULL;

int nativeAccept() {
	JNIEnv* jenv_local;
	int rc;

	int result = 0;

	if(JSSE_NATIVE_CALL_DEBUG) printf("nativeAccept in\n");

	rc = tptpAttachCurrentThread(&jenv_local);

	if(!rc && objBinding) {

		if(mthNativeAccept == NULL) {
			jclass clsBinding = jenv_local->FindClass(CLASS_NAME_NATIVE);
			if(clsBinding) {
				mthNativeAccept = jenv_local->GetMethodID(clsBinding, "nativeAccept", "()I");
			}
		}

		if(mthNativeAccept) {
			result = jenv_local->CallIntMethod(objBinding, mthNativeAccept);

			jthrowable jException = jenv_local->ExceptionOccurred();
			if(jException) {
				result = -1;

				if(JSSE_NATIVE_CALL_DEBUG) jenv_local->ExceptionDescribe();
			}

		}
		else {
			
			printf("nativeAccept(): Failed. Cannot resolve Java method: nativeAccept()\n");
			result = -1;
		}

		tptpDetachCurrentThread();
	}
	else {
		printf("nativeAccept(): Failed. JVM not initialized\n");
		result = -1;
	}

	if(JSSE_NATIVE_CALL_DEBUG)  printf("nativeAccept out (%d)\n", result);

	return result;

	
}

void nativeClose(int connectionID) {
	JNIEnv* jenv_local;
	int rc;

	if(JSSE_NATIVE_CALL_DEBUG) printf("nativeClose cid(%d)\n", connectionID);

	rc = tptpAttachCurrentThread(&jenv_local);

	if(!rc && objBinding) {
		if(mthNativeClose == NULL) {
			jclass clsBinding = jenv_local->FindClass(CLASS_NAME_NATIVE);
			if(clsBinding) {
				mthNativeClose = jenv_local->GetMethodID(clsBinding, "nativeClose", "(I)V");
			}
		}

		if(mthNativeClose) {
			jenv_local->CallVoidMethod(objBinding, mthNativeClose, connectionID);

			jthrowable jException = jenv_local->ExceptionOccurred();
			if(jException) {
				if(JSSE_NATIVE_CALL_DEBUG) jenv_local->ExceptionDescribe();
			}
		}
		else {
			printf("nativeClose(): Failed. Cannot resolve Java method: nativeClose()\n");
		}

		tptpDetachCurrentThread();
	}
	else {
		printf("nativeClose(): Failed. JVM not initialized\n");
	}

	if(JSSE_NATIVE_CALL_DEBUG)  printf("nativeClose out\n");
}

int nativeHandshake(int connectionID) {
	JNIEnv* jenv_local;
	int rc;
	int result = 0;

	if(JSSE_NATIVE_CALL_DEBUG)  printf("nativeHandshake in (cid:%d)\n", connectionID);

	rc = tptpAttachCurrentThread(&jenv_local);

	if(!rc && objBinding) {

		if(mthNativeHandshake == NULL) {
			jclass clsBinding = jenv_local->FindClass(CLASS_NAME_NATIVE);
			if(clsBinding) {
				mthNativeHandshake = jenv_local->GetMethodID(clsBinding, "nativeHandshake", "(I)V");
			}
		}

		if(mthNativeHandshake) {
			jenv_local->CallVoidMethod(objBinding, mthNativeHandshake, connectionID);
			jthrowable jException = jenv_local->ExceptionOccurred();

			if(jException) {
				if(JSSE_NATIVE_CALL_DEBUG) jenv_local->ExceptionDescribe();
				result = -1;
			} 
		}
		else {
			printf("nativeHandshake(): Failed. Cannot resolve Java method: nativeHandshake()\n");
			result = -1;
		}

		tptpDetachCurrentThread();
	}
	else {
		printf("nativeHandshake(): Failed. JVM not initialized\n");
		result = -1;
	}
	if(JSSE_NATIVE_CALL_DEBUG) printf("nativeHandshake out (%d)\n", result);

	return result;
}


#define NINITERR_UNABLE_TO_LISTEN -2
#define NINITERR_NO_ALG -3
#define NINITERR_KEYSTOREEX -4
#define NINITERR_CERTEX -5
#define NINITERR_UNRECOVKEY -6


int nativeInit(log_service_t *_logService) {
	JNIEnv* jenv_local = NULL;
	int rc;
	int result = 0;

	if(JSSE_NATIVE_CALL_DEBUG)  printf("nativeInit in\n");

	jsseLogService = _logService;

	vm = tptpCreateJVM(jsseClassPath);

	rc = tptpAttachCurrentThread(&jenv_local);

	if(rc) {
		TPTP_LOG_ERROR_MSG(jsseLogService, "Failed attaching to JVM.");
		return -1;
	}

	jclass clsBinding = jenv_local->FindClass(CLASS_NAME_NATIVE);


	if(clsBinding) {
		jmethodID mthCtor = jenv_local->GetMethodID(clsBinding, "<init>", "()V");
		
		if(mthCtor) {
			objBinding = jenv_local->NewGlobalRef(jenv_local->NewObject(clsBinding, mthCtor));

			if(objBinding) {

				jmethodID mthNativeSetValue = jenv_local->GetMethodID(clsBinding, "nativeSetValue", "(Ljava/lang/String;Ljava/lang/String;)V");

				if(mthNativeSetValue) {
					char* cName;
					char* cValue;
					jstring jName;
					jstring jValue;
					jthrowable jException;

					cValue = getConfigValue(KEYSTORE_FILENAME);
					cName = "org.eclipse.tptp.platform.agentcontroller.jsse.ksFilename";
					jName = jenv_local->NewStringUTF(cName);
					jValue = jenv_local->NewStringUTF(cValue);
					jenv_local->CallVoidMethod(objBinding, mthNativeSetValue, jName, jValue);
					jException = jenv_local->ExceptionOccurred();
					if(jException) {
						jenv_local->ExceptionDescribe();
						result = -1;
					}
					
					cValue = getConfigValue(KEYSTORE_PASSWORD);
					cName = "org.eclipse.tptp.platform.agentcontroller.jsse.ksPassword";

					jName = jenv_local->NewStringUTF(cName);
					jValue = jenv_local->NewStringUTF(cValue);
					jenv_local->CallVoidMethod(objBinding, mthNativeSetValue, jName, jValue);
					jException = jenv_local->ExceptionOccurred();
					if(jException) {
						jenv_local->ExceptionDescribe();
						result = -1;
					}

					cValue = getConfigValue(KEYSTORE_SERVER_PORT);

					cName = "org.eclipse.tptp.platform.agentcontroller.jsse.serverPort";
					jName = jenv_local->NewStringUTF(cName);
					jValue = jenv_local->NewStringUTF(cValue);
										
					jenv_local->CallVoidMethod(objBinding, mthNativeSetValue, jName, jValue);

					jException = jenv_local->ExceptionOccurred();
					if(jException) {
						jenv_local->ExceptionDescribe();
						result = -1;
					}

					jmethodID mthNativeInit = jenv_local->GetMethodID(clsBinding, "nativeInit", "()I");
					

					if(mthNativeInit) {
						jint nativeIntReturn = jenv_local->CallIntMethod(objBinding, mthNativeInit);

						// If we have encountered no errors thus far, AND nativeIntReturn contains an error....
						if(result >= 0 && nativeIntReturn < 0) {
							
							// Handle error codes
							if(NINITERR_UNABLE_TO_LISTEN == nativeIntReturn) TPTP_LOG_ERROR_MSG(jsseLogService, "Unable to listen on secure port.");
							
							if(NINITERR_NO_ALG == nativeIntReturn) TPTP_LOG_ERROR_MSG(jsseLogService, "NoSuchAlgorithmException thrown by JSSE module.");
							if(NINITERR_KEYSTOREEX == nativeIntReturn) TPTP_LOG_ERROR_MSG(jsseLogService, "KeyStoreException thrown by JSSE module.");
							if(NINITERR_CERTEX == nativeIntReturn)  TPTP_LOG_ERROR_MSG(jsseLogService, "CertificateException thrown by JSSE module.");
							if(NINITERR_UNRECOVKEY == nativeIntReturn) TPTP_LOG_ERROR_MSG(jsseLogService, "UnrecoverableKeyException thrown by JSSE module.");

							result = -1;
						}

						jException = jenv_local->ExceptionOccurred();
						if(jException) {
							if(JSSE_NATIVE_CALL_DEBUG) jenv_local->ExceptionDescribe();
							result = -1;
						}
					}
					else {
						printf("nativeInit(): Failed. Cannot resolve Java method: nativeInit()\n");
						result = -1;
					}
				}
				else {
					printf("nativeInit(): Failed. Cannot resolve Java method: nativeSetValue()\n");
					result = -1;
				}
			}
			else {
				printf("nativeInit(): Failed. Cannot allocate Java object: %s\n", CLASS_NAME_JAVA);
				result = -1;
			}
		}
		else {
			printf("nativeInit(): Failed. Cannot resolve constructor for Java class: %s\n", CLASS_NAME_JAVA);
			result = -1;
		}
	}
	else {
		printf("nativeInit(): Failed. Cannot find Java class: %s\n", CLASS_NAME_JAVA);
		TPTP_LOG_ERROR_MSG1(jsseLogService, "nativeInit(): Failed. Cannot find Java class: %s", CLASS_NAME_JAVA);
		result = -1;
	}
	
	tptpDetachCurrentThread();

	if(JSSE_NATIVE_CALL_DEBUG) printf("nativeInit out (%d)\n", result);

	return result;
}

int nativeRead(int connectionID, char* buffer, int length) {
	JNIEnv* jenv_local;
	int rc;
	int result = 0;

	if(JSSE_NATIVE_CALL_DEBUG) printf("nativeRead in (cid:%d) (length:%d)\n", connectionID, length);

	rc = tptpAttachCurrentThread(&jenv_local);

	if(!rc && objBinding) {

		if(mthNativeRead == NULL) {
			jclass clsBinding = jenv_local->FindClass(CLASS_NAME_NATIVE);
			if(clsBinding) {
				mthNativeRead = jenv_local->GetMethodID(clsBinding, "nativeRead", "(I[BI)I");
			}
		}

		if(mthNativeRead) {

			jbyteArray jbuffer = jenv_local->NewByteArray(length);
			
			jint byteRead = jenv_local->CallIntMethod(objBinding, mthNativeRead, connectionID, jbuffer, length);
			
			jthrowable jException = jenv_local->ExceptionOccurred();
			
			if(jException) {
				if(JSSE_NATIVE_CALL_DEBUG) jenv_local->ExceptionDescribe();
				result = -1;
			} else {
				jenv_local->GetByteArrayRegion(jbuffer, 0 /*offset*/, length, (jbyte*)buffer);
				result = byteRead;
				if(JSSE_NATIVE_CALL_DEBUG) printf("Bytes Read in NativeRead: %d\n", byteRead);
			}
			
			jenv_local->DeleteLocalRef(jbuffer);
		}
		else {
			printf("nativeRead(): Failed. Cannot resolve Java method: nativeRead()\n");
			result = -1;
		}

		tptpDetachCurrentThread();
	}
	else {
		printf("nativeRead(): Failed. JVM not initialized\n");
		result = -1;
	}

	if(JSSE_NATIVE_CALL_DEBUG) printf("nativeRead out (%d)\n", result);

	return result;
}

int nativeShutdown() {
	JNIEnv* jenv_local;
	int rc;
	int result = 0;

	if(JSSE_NATIVE_CALL_DEBUG) printf("nativeShutdown in\n");
	
	rc = tptpAttachCurrentThread(&jenv_local);
	
	if(!rc) {
		
	}
	
	if(!rc && objBinding) {

		if(mthNativeShutdown == NULL) {
			jclass clsBinding = jenv_local->FindClass(CLASS_NAME_NATIVE);
			if(clsBinding) {
				mthNativeShutdown = jenv_local->GetMethodID(clsBinding, "nativeShutdown", "()V");
			}
		}

		if(mthNativeShutdown) {
			jenv_local->CallVoidMethod(objBinding, mthNativeShutdown);
			jthrowable jException = jenv_local->ExceptionOccurred();
			if(jException) {
				if(JSSE_NATIVE_CALL_DEBUG) jenv_local->ExceptionDescribe();
				result = -1;
			}
		}
		else {
			printf("mthNativeShutdown(): Failed. Cannot resolve Java method: nativeShutdown()\n");
			result = -1;
		}

		tptpDetachCurrentThread();
	}
	else {
		printf("nativeShutdown(): Failed. JVM not initialized\n");
		result = -1;
	}

	if(JSSE_NATIVE_CALL_DEBUG) printf("nativeShutdown out (%d)\n", result);

	return result;
}

void nativeWrite(int connectionID, char* buffer, int length) {
	JNIEnv* jenv_local;
	int rc;
	if(JSSE_NATIVE_CALL_DEBUG) printf("nativeWrite in (cid:%d)\n", connectionID);

	rc = tptpAttachCurrentThread(&jenv_local);

	if(!rc && objBinding) {
		
		if(mthNativeWrite == NULL) {
			jclass clsBinding = jenv_local->FindClass(CLASS_NAME_NATIVE);
			if(clsBinding) {
				mthNativeWrite = jenv_local->GetMethodID(clsBinding, "nativeWrite", "(I[BI)V");
			}
		}
		
		if(mthNativeWrite) {
			jbyteArray jbuffer = jenv_local->NewByteArray(length);
			jenv_local->SetByteArrayRegion(jbuffer, 0 /* offset*/, length, (jbyte*)buffer);
			jenv_local->CallVoidMethod(objBinding, mthNativeWrite, connectionID, jbuffer, length);
			jthrowable jException = jenv_local->ExceptionOccurred();
			if(jException) {
				if(JSSE_NATIVE_CALL_DEBUG) jenv_local->ExceptionDescribe();
			}
			jenv_local->DeleteLocalRef(jbuffer);
		}
		else {
			printf("nativeRead(): Failed. Cannot resolve Java method: nativeWrite()\n");
		}

		tptpDetachCurrentThread();
	}
	else {
		printf("nativeWrite(): Failed. JVM not initialized\n");
	}
	
	if(JSSE_NATIVE_CALL_DEBUG) printf("nativeWrite out\n");
}

int tptpAttachCurrentThread(JNIEnv** jenv) {
	jint result;
	result = vm->AttachCurrentThread((void**)jenv, NULL);
	return result;
}

int tptpDetachCurrentThread() {
	jint result;
	
	result = vm->DetachCurrentThread();

	return result;
}
