/*******************************************************************************
 * Copyright (c) 2008, 2009 Intel Corporation, IBM Corporation.
 * 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: TPTPJava.c,v 1.7 2009/10/19 21:14:02 jwest Exp $ 
 *******************************************************************************/ 

/*
 *	TPTP JVM provider
 *
 *  TODO: error logging, thread synchronization
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "tptp/TPTPConfig.h"
#include "tptp/TPTPJava.h"

#define DEFINE_CLASSPATH "-Djava.class.path="

#ifdef _WIN32
	char pathSeparatorCharacter = '\\';
#else
	char pathSeparatorCharacter = '/';
#endif

typedef jint (JNICALL *CreateJavaVM)(JavaVM**, void**, void*);

JavaVM* jvm = NULL;

void checkPathSeparators(char* path) {
	char psc, *p;
	
#ifdef _WIN32
	psc = '/';
#else
	psc = '\\';
#endif

	p = path;
	while (1) {
		p = strchr(p, psc);
		if (p == NULL) break;
		
		*p = pathSeparatorCharacter;
	}
}

	/* Get the classpath from the system environment */
char* getSystemClasspath() {
	int   classPathSize = 8096;
	char* classpath;
	int result;

	classpath = (char*) malloc(classPathSize);
	while (1) {
		result = getEnvironmentVariable("CLASSPATH", classpath, classPathSize);
		if (result <= classPathSize) break;

		classPathSize = result + 1;
		classpath = realloc(classpath, classPathSize);
	}
	
	checkPathSeparators(classpath);
	
	return classpath;
}

int createJVM(const char* classpath) {
	DLL_REFERENCE jvmLib;
	CreateJavaVM vmCreate; 
	JavaVMInitArgs vmArgs;
	JavaVMOption options[2];
	JNIEnv* jenv = NULL;
	char* cpe=NULL;
	char* cp=NULL;
	int cplen;
	jint result;
	AC_Config_t* cfg;

	cfg = getTPTPConfig();
	if (cfg == NULL) return AC_CONFIG_NOT_FOUND;
	if (cfg->jvmLibPath == NULL) return JVM_LIB_NOT_SET;

	// Load the dynamic library.
	if(!(jvmLib = LOAD_LIBRARY(cfg->jvmLibPath))) return JVM_LOAD_ERROR;

	// Resolve the entry point. 
	if(!(vmCreate = (CreateJavaVM) RESOLVE_ENTRY_POINT(jvmLib, "JNI_CreateJavaVM"))) {
		return JVM_RESOLVE_ERROR;
	}

	vmArgs.nOptions = 0;

#ifdef _WIN32
	// Add the -Xrs option to prevent JVM shutdown upon user logoff 
	options[vmArgs.nOptions++].optionString = "-Xrs";
#endif

	cplen = 0;
	if (classpath != NULL) 
		cplen = strlen(classpath);

	cpe = getSystemClasspath();
	if (cpe != NULL) cplen += strlen(cpe);

	if (cplen > 0) {
		cp = (char*) malloc(cplen + strlen(DEFINE_CLASSPATH) + 2); // including PATH_SEPARATOR
		strcpy(cp, DEFINE_CLASSPATH);
		
		if (classpath != NULL) strcat(cp, classpath);		
		
		if (cpe != NULL) {
			if (classpath != NULL) {
				cplen = strlen(cp);
				*(cp + cplen) = PATH_SEPARATOR;
				*(cp + cplen + 1) = '\0';
			}
			
			strcat(cp, cpe);
		}

		#ifdef MVS
			// z/OS JVM requires the classpath param to be in ASCII, so convert from EBCDIC
			__etoa(cp);
		#endif
		options[vmArgs.nOptions++].optionString = cp;
	}
	
	vmArgs.options = options;
	vmArgs.version = JNI_VERSION_1_2;
	vmArgs.ignoreUnrecognized = JNI_FALSE;

	// Instantiate the JVM, and clean up. 
	result = (*vmCreate)(&jvm, (void**)&jenv, (void*)&vmArgs);

	if(cp != NULL) free(cp);
	if(cpe != NULL) free(cpe);

	return (result == 0) ? 0 : JVM_CREATION_ERROR;
}

static jstring nativeToJavaString(JNIEnv *env, const char *s) {
	jclass clsString; 
    jmethodID mthInit;
    jbyteArray ba = NULL;
    jstring jstr;
    int slen;

    clsString = (*env)->FindClass(env, "java/lang/String");
    if (clsString == NULL) return NULL;

    mthInit = (*env)->GetMethodID(env, clsString, "<init>", "([B)V");
    if (mthInit == NULL) return NULL;

    slen = strlen(s);
    ba = (*env)->NewByteArray(env, slen);
    if (ba == NULL) return NULL;
    
    (*env)->SetByteArrayRegion(env, ba, 0, slen, (jbyte *)s);
	jstr = (*env)->NewObject(env, clsString, mthInit, ba);
	(*env)->DeleteLocalRef(env, ba);
	
	return jstr;
}

static char *JavaToNativeString(JNIEnv *env, jstring jstr) {
    jclass clsString;
    jmethodID mthGetBytes;
    jbyteArray ba = NULL;
    jthrowable ex;
    jint slen;
    char *s = NULL;
      
    clsString = (*env)->FindClass(env, "java/lang/String");
    if (clsString == NULL) return NULL;
    
    mthGetBytes = (*env)->GetMethodID(env, clsString, "getBytes", "()[B");
    if (mthGetBytes == NULL) return NULL;
       
    ba = (*env)->CallObjectMethod(env, jstr, mthGetBytes);
    ex = (*env)->ExceptionOccurred(env);
    if (ex != NULL) {
        (*env)->DeleteLocalRef(env, ex);
        return NULL;
    }
    
    slen = (*env)->GetArrayLength(env, ba);
    s = (char *) malloc(slen + 1);
	
	(*env)->GetByteArrayRegion(env, ba, 0, slen, (jbyte *)s);
	*(s+slen) = 0;

    (*env)->DeleteLocalRef(env, ba);
    
    return s;
}

void setClasspath(JNIEnv* jenv, const char* classpath) {
	jclass clsSystem = NULL;
	jmethodID mthGetProp = NULL;
	jmethodID mthSetProp = NULL;
	jstring prop, jcp;
    jthrowable exc;
	char *cp0, *cp;
	
	if (classpath == NULL) return;
	if (strlen(classpath) <= 0) return;
	
	clsSystem = (*jenv)->FindClass(jenv, "java/lang/System");
	if (clsSystem == NULL) return;
	
	mthGetProp = (*jenv)->GetStaticMethodID(jenv, clsSystem, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
	if (mthGetProp == NULL) return;
	
	mthSetProp = (*jenv)->GetStaticMethodID(jenv, clsSystem, "setProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
	if (mthSetProp == NULL) return;

	prop = nativeToJavaString(jenv, "java.class.path");
	if (prop == NULL) return;
		
	jcp = (jstring) (*jenv)->CallStaticObjectMethod(jenv, clsSystem, mthGetProp, prop);
	if (jcp == NULL) return;

	cp0 = JavaToNativeString(jenv, jcp);
	if (cp0 == NULL) {
		cp = (char*) classpath;
	}
	else {
		int len = strlen(cp0);
		cp = (char*) malloc(len + strlen(classpath) + 2); // including path separator
		strcpy(cp, cp0);
		*(cp + len) = PATH_SEPARATOR;
		*(cp + len + 1) = '\0';
		strcat(cp, classpath);
	}

	jcp = nativeToJavaString(jenv, cp);
	if (jcp != NULL) {
		(*jenv)->CallStaticObjectMethod(jenv, clsSystem, mthSetProp, prop, jcp);
		
    	exc = (*jenv)->ExceptionOccurred(jenv);
    	if (exc) {
        	(*jenv)->DeleteLocalRef(jenv, exc);
    	}
	}

	if (cp0 != NULL) {
		free(cp);
		free(cp0);
	}	
}

JavaVM* tptpCreateJVM(const char* classpath) {
	if (jvm == NULL) 
		createJVM(classpath);
	else {
		JNIEnv* jenv;

		(*jvm)->AttachCurrentThread(jvm, (void**)&jenv, NULL);
		setClasspath(jenv, classpath);
	}
	
	return jvm;
}
