/*
   This file serves as an interface between TINKER Fortran code
   and FFE Java objects used for socket based communication
*/

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

#include <windows.h>
#include <sys/stat.h>

#define JVM_DLL "jvm.dll"
#define RELEASE14 "1.4"
#define RELEASE15 "1.5"
#define JRE_KEY     "Software\\JavaSoft\\Java Runtime Environment"

/* Some global variables */
JNIEnv* env = 0;
jobject serverobject = 0;
jclass  serverclass = 0;
jobject systemobject = 0;
jclass  systemclass = 0;
jobject updateobject = 0;
jclass  updateclass = 0;

jboolean InitializeJVM() {
    JavaVM *vm = 0;
    JavaVMInitArgs args;
    JavaVMOption options[1];
    jint numOptions = 1;
    jint r;
    char *classpath;
    char ffe[1024];
    char *def;
    struct stat s;

    // Set the classpath
    classpath = getenv("classpath");
    if (classpath == NULL){
          return JNI_FALSE;
    }
    def = (char *) malloc(strlen(classpath) + 20);
    sprintf(def, "-Djava.class.path=%s", classpath);

    options[0].optionString = def;
    args.version  = JNI_VERSION_1_2;
    args.nOptions = numOptions;
    args.options  = options;
    args.ignoreUnrecognized = JNI_TRUE;

    r = JNI_CreateJavaVM(&vm, (void **) &env, &args);

    return r == JNI_OK;
}

void CREATEJVM(int *flag) {
    if (!InitializeJVM()) {
        *flag = 0;
        return;
    }
    *flag = 1;
    return;
}

void DESTROYJVM(){
	JavaVM *vm;
	(*env)->GetJavaVM(env, &vm);
	(*vm)->DestroyJavaVM(vm);
}

void GETMONITOR(){
	(*env)->MonitorEnter(env, updateobject);
}

void RELEASEMONITOR(){
  	(*env)->MonitorExit(env, updateobject);
}

void CREATESERVER(jint *flag) {
	jmethodID methodID;
        jobject temp;

	// Find the Java ffe.net.FServer class
    	serverclass = (*env)->FindClass(env, "ffe/net/FServer");
        if (serverclass == 0) {
           *flag = 0;
           return;
	}
	methodID = (*env)->GetMethodID(env, serverclass, "<init>",
		"(Lffe/net/TinkerSystem;)V");
	temp =  (*env)->NewObject(env, serverclass, methodID, systemobject);
	methodID = (*env)->GetMethodID(env, serverclass, "start", "()V");
	(*env)->CallVoidMethod(env, temp, methodID, "()V");
    	serverobject =  (*env)->NewGlobalRef(env, temp);
    	(*env)->DeleteLocalRef(env, temp);
        *flag = 1;

	return;
}

void DESTROYSERVER(){
	jmethodID methodID;
        jboolean jffe = JNI_FALSE;

	// Tell the server to close down
	methodID = (*env)->GetMethodID(env, serverclass, "stop", "()V");
	(*env)->CallVoidMethod(env, serverobject, methodID, "()V");

	// Wait while it closes any clients
	methodID = (*env)->GetMethodID(env, serverclass, "isAlive", "()Z");
	while ((*env)->CallBooleanMethod(env, serverobject, methodID, "()Z")) {
        }

        (*env)->DeleteGlobalRef(env, serverobject);
        return;
}

void NEEDUPDATE(int *status){
	jmethodID methodID;
	jboolean ret;

	methodID = (*env)->GetMethodID(env, serverclass, "needUpdate", "()Z");
	ret = (*env)->CallBooleanMethod(env, serverobject, methodID, "()Z");
        if (ret) *status = 1;
	else *status = 0;
        return;
}

void SETUPDATED(){
	jmethodID methodID;

	methodID = (*env)->GetMethodID(env, serverclass, "setUpdated", "()V");
	(*env)->CallVoidMethod(env, serverobject, methodID, "()V");
        return;
}

jstring char2jstring(const char *str, int len) {
     jstring result;
     jbyteArray bytes = 0;
     jmethodID methodID;
     jclass class;

     if ((*env)->EnsureLocalCapacity(env, 2) < 0) {
         printf("Out of memory\n");
         exit(-1);
     }
     bytes = (*env)->NewByteArray(env, len);
     if (bytes != NULL) {
         (*env)->SetByteArrayRegion(env, bytes, 0, len,
                                    (jbyte *)str);
	 class = (*env)->FindClass(env, "Ljava/lang/String;");
         methodID = (*env)->GetMethodID(env, class, "<init>", "([B)V");
         result = (*env)->NewObject(env, class, methodID, bytes);
         (*env)->DeleteLocalRef(env, bytes);
         if (result != NULL) return result;
     } /* else fall through */
     printf("Problem creating java string for: %s\nLength: %i", str, len);
     exit(-1);
}

void CREATESYSTEM(jint *atoms, jint *keywords, jint* flag) {
	jmethodID methodID;
        jobject temp;

	systemclass = (*env)->FindClass(env, "ffe/net/TinkerSystem");
        if (systemclass == 0) {
           *flag = 0;
           return;
	}
	methodID = (*env)->GetMethodID(env, systemclass, "<init>", "(II)V");
	temp     = (*env)->NewObject(env, systemclass,
                                     methodID, *atoms, *keywords);
        systemobject  = (*env)->NewGlobalRef(env, temp);
        (*env)->DeleteLocalRef(env, temp);
        *flag = 1;
	return;
}


void CREATEUPDATE(jint *n, jint *type, jint* amoeba, jint* flag){
	jmethodID methodID;
        jobject temp;
        jboolean jbool;

	updateclass = (*env)->FindClass(env, "ffe/net/TinkerUpdate");
        if (updateclass == 0) {
           *flag = 0;
           return;
	}

	methodID = (*env)->GetMethodID(env, updateclass, "<init>", "(IIZ)V");

        if (amoeba == 0) jbool = JNI_FALSE;
        else jbool = JNI_TRUE;

	temp = (*env)->NewObject(env, updateclass, methodID, *n, *type, jbool);
        updateobject  = (*env)->NewGlobalRef(env, temp);
        (*env)->DeleteLocalRef(env, temp);
	methodID = (*env)->GetMethodID(env,serverclass,"loadUpdate",
			  "(Lffe/net/TinkerUpdate;)V");
        (*env)->CallObjectMethod(env, serverobject, methodID, updateobject);
        *flag = 1;
	return;
}

void SETCOORDINATES(jint *n, jdouble *x, jdouble *y, jdouble *z) {
	jfieldID fieldID;
        jobjectArray coords;
        jdoubleArray jx;
        jobject object;
        jclass class;

        object = updateobject;
        class = updateclass;
        if (updateobject == 0) {
             object = systemobject;
             class = systemclass;
        }
	fieldID = (*env)->GetFieldID(env, class, "coordinates", "[[D");
	coords = (*env)->GetObjectField(env, object, fieldID);
        jx = (*env)->GetObjectArrayElement(env, coords, 0);
        (*env)->SetDoubleArrayRegion(env, jx, 0, *n, x);
        jx = (*env)->GetObjectArrayElement(env, coords, 1);
        (*env)->SetDoubleArrayRegion(env, jx, 0, *n, y);
        jx = (*env)->GetObjectArrayElement(env, coords, 2);
        (*env)->SetDoubleArrayRegion(env, jx, 0, *n, z);
}


void SETCONNECTIVITY(jint *n, jint *a, jint *b, jint *c, jint *d) {
	jfieldID fieldID;
        jobjectArray connect;
        jintArray ci;

	fieldID = (*env)->GetFieldID(env, systemclass, "connectivity", "[[I");
	connect = (*env)->GetObjectField(env, systemobject, fieldID);
        ci = (*env)->GetObjectArrayElement(env, connect, 0);
        (*env)->SetIntArrayRegion(env, ci, 0, *n, a);
        ci = (*env)->GetObjectArrayElement(env, connect, 1);
        (*env)->SetIntArrayRegion(env, ci, 0, *n, b);
        ci = (*env)->GetObjectArrayElement(env, connect, 2);
        (*env)->SetIntArrayRegion(env, ci, 0, *n, c);
        ci = (*env)->GetObjectArrayElement(env, connect, 3);
        (*env)->SetIntArrayRegion(env, ci, 0, *n, d);
}

/*
void SETVDW(jint *n, jdouble *vdw) {
	jfieldID fieldID;
        jdoubleArray jvdw;

	fieldID = (*env)->GetFieldID(env, systemclass, "vdw", "[D");
	jvdw = (*env)->GetObjectField(env, systemobject, fieldID);
        (*env)->SetDoubleArrayRegion(env, jvdw, 0, *n, *vdw);
}*/

void SETCHARGE(jint *n, jdouble *charge) {
	jfieldID fieldID;
        jdoubleArray jcharge;

	fieldID = (*env)->GetFieldID(env, systemclass, "charge", "[D");
	jcharge = (*env)->GetObjectField(env, systemobject, fieldID);
        (*env)->SetDoubleArrayRegion(env, jcharge, 0, *n, charge);
}

void SETMASS(jint *n, jdouble *mass) {
	jfieldID fieldID;
        jdoubleArray jmass;

	fieldID = (*env)->GetFieldID(env, systemclass, "mass", "[D");
	jmass = (*env)->GetObjectField(env, systemobject, fieldID);
        (*env)->SetDoubleArrayRegion(env, jmass, 0, *n, mass);
}

void SETATOMIC(jint *n, jint *atomic) {
	jfieldID fieldID;
        jintArray jatomic;

	fieldID = (*env)->GetFieldID(env, systemclass, "atomic", "[I");
	jatomic = (*env)->GetObjectField(env, systemobject, fieldID);
        (*env)->SetIntArrayRegion(env, jatomic, 0, *n, atomic);
}

void SETATOMTYPES(jint *n, jint *t) {
	jfieldID fieldID;
        jintArray types;

	fieldID = (*env)->GetFieldID(env, systemclass, "types", "[I");
	types = (*env)->GetObjectField(env, systemobject, fieldID);
        (*env)->SetIntArrayRegion(env, types, 0, *n, t);
}

void SETNAME(jint *num, char *s, jint len) {
	jfieldID fieldID;
        jstring string;
        jobjectArray name;
        jint index;

	index = (*num) - 1;
        if (index < 0) {
		printf("Negative index into name array\n");
                exit(-1);
	}
 	string = char2jstring(s, len);
	fieldID = (*env)->GetFieldID(env, systemclass, "name",
 				"[Ljava/lang/String;");
	name = (*env)->GetObjectField(env, systemobject, fieldID);
        (*env)->SetObjectArrayElement(env, name, index, string);
}

void SETSTORY(jint *num, char *s, jint len) {
	jfieldID fieldID;
        jstring string;
        jobjectArray story;
        jint index;

	index = (*num) - 1;
        if (index < 0) {
		printf("Negative index into story array\n");
	 	exit(-1);
	}
 	string = char2jstring(s, len);
	fieldID = (*env)->GetFieldID(env, systemclass, "story",
 				"[Ljava/lang/String;");
	story = (*env)->GetObjectField(env, systemobject, fieldID);
        (*env)->SetObjectArrayElement(env, story, index, string);
}

void SETKEYWORD(jint *num, char *keyword, jint len) {
	jfieldID fieldID;
        jstring string;
        jobjectArray keywords;
        jint index;

	index = (*num) - 1;
        if (index < 0) {
		printf("Negative index into keyword array\n");
		exit(0);
	}

 	string = char2jstring(keyword, len);
	fieldID = (*env)->GetFieldID(env, systemclass, "keywords",
 				"[Ljava/lang/String;");
	keywords = (*env)->GetObjectField(env, systemobject, fieldID);
        (*env)->SetObjectArrayElement(env, keywords, index, string);
}

void SETFORCEFIELD(char *forcefield, jint len) {
	jfieldID fieldID;
        jstring string;

 	string = char2jstring(forcefield, len);
	fieldID = (*env)->GetFieldID(env, systemclass, "forcefield",
				    "Ljava/lang/String;");
	(*env)->SetObjectField(env, systemobject, fieldID, string);
}

void SETTIME(jdouble *time) {
	jfieldID fieldID;

	fieldID = (*env)->GetFieldID(env, updateclass, "time", "D");
	(*env)->SetDoubleField(env, updateobject, fieldID, *time);
}

void SETENERGY(jdouble *e) {
	jfieldID fieldID;

	fieldID = (*env)->GetFieldID(env, updateclass, "energy", "D");
	(*env)->SetDoubleField(env, updateobject, fieldID, *e);
}

void SETSTEP(jint *step) {
	jfieldID fieldID;

	fieldID = (*env)->GetFieldID(env, updateclass, "step", "I");
	(*env)->SetIntField(env, updateobject, fieldID, *step);
}

void SETTYPE(jint *type) {
	jfieldID fieldID;

	fieldID = (*env)->GetFieldID(env, updateclass, "type", "I");
	(*env)->SetIntField(env, updateobject, fieldID, *type);
}

void SETFILE(char *name, jint len) {
	jfieldID fieldID;
        jstring string;

 	string = char2jstring(name, len);
	fieldID = (*env)->GetFieldID(env, systemclass, "file",
				    "Ljava/lang/String;");
	(*env)->SetObjectField(env, systemobject, fieldID, string);
}

void SETVELOCITY(jint *n, jdouble *x, jdouble *y, jdouble *z){
	jfieldID fieldID;
        jobjectArray ind;
        jdoubleArray jx;

	fieldID = (*env)->GetFieldID(env, updateclass, "velocity", "[[D");
	ind = (*env)->GetObjectField(env, updateobject, fieldID);
        jx = (*env)->GetObjectArrayElement(env, ind, 0);
        (*env)->SetDoubleArrayRegion(env, jx, 0, *n, x);
        jx = (*env)->GetObjectArrayElement(env, ind, 1);
        (*env)->SetDoubleArrayRegion(env, jx, 0, *n, y);
        jx = (*env)->GetObjectArrayElement(env, ind, 2);
        (*env)->SetDoubleArrayRegion(env, jx, 0, *n, z);
}

void SETACCELERATION(jint *n, jdouble *x, jdouble *y, jdouble *z){
	jfieldID fieldID;
        jobjectArray ind;
        jdoubleArray jx;

	fieldID = (*env)->GetFieldID(env, updateclass, "acceleration", "[[D");
	ind = (*env)->GetObjectField(env, updateobject, fieldID);
        jx = (*env)->GetObjectArrayElement(env, ind, 0);
        (*env)->SetDoubleArrayRegion(env, jx, 0, *n, x);
        jx = (*env)->GetObjectArrayElement(env, ind, 1);
        (*env)->SetDoubleArrayRegion(env, jx, 0, *n, y);
        jx = (*env)->GetObjectArrayElement(env, ind, 2);
        (*env)->SetDoubleArrayRegion(env, jx, 0, *n, z);
}

void SETINDUCED(jint *n, jdouble *x, jdouble *y, jdouble *z){
	jfieldID fieldID;
        jobjectArray ind;
        jdoubleArray jx;

	fieldID = (*env)->GetFieldID(env, updateclass, "induced", "[[D");
	ind = (*env)->GetObjectField(env, updateobject, fieldID);
        jx = (*env)->GetObjectArrayElement(env, ind, 0);
        (*env)->SetDoubleArrayRegion(env, jx, 0, *n, x);
        jx = (*env)->GetObjectArrayElement(env, ind, 1);
        (*env)->SetDoubleArrayRegion(env, jx, 0, *n, y);
        jx = (*env)->GetObjectArrayElement(env, ind, 2);
        (*env)->SetDoubleArrayRegion(env, jx, 0, *n, z);
}

void SETGRADIENTS(jint *n, jdouble *x, jdouble *y, jdouble *z){
	jfieldID fieldID;
        jobjectArray ind;
        jdoubleArray jx;

	fieldID = (*env)->GetFieldID(env, updateclass, "gradients", "[[D");
	ind = (*env)->GetObjectField(env, updateobject, fieldID);
        jx = (*env)->GetObjectArrayElement(env, ind, 0);
        (*env)->SetDoubleArrayRegion(env, jx, 0, *n, x);
        jx = (*env)->GetObjectArrayElement(env, ind, 1);
        (*env)->SetDoubleArrayRegion(env, jx, 0, *n, y);
        jx = (*env)->GetObjectArrayElement(env, ind, 2);
        (*env)->SetDoubleArrayRegion(env, jx, 0, *n, z);
}

