#include "kureLib.h"
#include "kureJava.h"  // JNI header file
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>

// global declarations for jni

static jclass javalibraryclass = 0;
static JNIEnv* globalenv = 0;
static jboolean debug = JNI_FALSE;

// usefull methods
//-----------------------------------------------------------------------

jstring charToJString (const char *message)
{
  jstring str = (*globalenv)->NewStringUTF(globalenv, message);
  return str;
}

char* jStringToChar (const jstring message)
{
  const char* pMessage = NULL;
  char* result = NULL;
  pMessage = (*globalenv)->GetStringUTFChars(globalenv, message, 0);

  result = (char*) malloc(strlen(pMessage)+1);
  strcpy(result, pMessage);

  (*globalenv)->ReleaseStringUTFChars(globalenv, message, pMessage);

  return result;
}

jbyteArray convertPointerToByteArray (const void* pointer) { 
 jbyteArray arr = (*globalenv)->NewByteArray(globalenv, sizeof(void*));
 (*globalenv)->SetByteArrayRegion(globalenv, arr,0,sizeof(void*),(jbyte*)&pointer);
 return arr;
}

void* convertByteArrayToPointer (const jbyteArray arr) {
  void* pointer = NULL;
  if (arr != NULL) {
   (*globalenv)->GetByteArrayRegion(globalenv, arr,0,sizeof(void*),(jbyte*)&pointer);
  }
  return pointer;
}

// methods for callback
//-----------------------------------------------------------------------

void command(const jstring str, const jobject parameter)
{
    jmethodID mid = (*globalenv)->GetStaticMethodID(globalenv, javalibraryclass, "command", "(Ljava/lang/String;Ljava/lang/Object;)V");
    if (mid == 0) {
        return;
    }

    (*globalenv)->CallStaticVoidMethod(globalenv, javalibraryclass, mid, str, parameter);  
}

int commandAndAcknowledge(const jstring str, const jobject parameter)
{
    jmethodID mid = (*globalenv)->GetStaticMethodID(globalenv, javalibraryclass, "commandAndAcknowledge", "(Ljava/lang/String;Ljava/lang/Object;)I");
    if (mid == 0) {
        return 0;
    }

    return (*globalenv)->CallStaticIntMethod(globalenv, javalibraryclass, mid, str, parameter);  
}

// Kure callback methods
//-----------------------------------------------------------------------

int Kure_Request (char *message)
{
  return commandAndAcknowledge(charToJString("print"),charToJString(message));
}

void Kure_PrintMessage (char *message)
{
  command(charToJString("print"),charToJString(message));
}

void Kure_PrintError (char *message)
{
  command(charToJString("print"),charToJString(message));
}

// JNI methods
//-----------------------------------------------------------------------

// INIT
JNIEXPORT void JNICALL Java_de_osz_kurejava_Kurejava_init
  (JNIEnv* env, jclass cl) {
  // save jni environment for access in other methods
  globalenv = env;

  // save java library classe for callbacks
  javalibraryclass = (*env)->NewGlobalRef(env, cl);

  // first output
  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("init"));
}

// QUIT
JNIEXPORT void JNICALL Java_de_osz_kurejava_Kurejava_quit
  (JNIEnv* env, jclass cl) {
  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("quit"));
  
  globalenv = NULL;

  (*env)->DeleteGlobalRef(env, javalibraryclass);
}

// SETDEBUG
JNIEXPORT void JNICALL Java_de_osz_kurejava_Kurejava_setDebug
  (JNIEnv* env, jclass cl, jboolean value) {
  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("setDebug"));
  debug = value;
}

// INITRELMANAGER
JNIEXPORT jbyteArray JNICALL Java_de_osz_kurejava_Kurejava_initRelManager
  (JNIEnv* env, jclass cl) {
  RelManager * pRelManager = NULL;
  jbyteArray arr = NULL;

  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("initRelManager"));

  pRelManager = Kure_Init ();
  arr = convertPointerToByteArray(pRelManager);
  // don't free pRelManager because the relmanager must be delete with quitRelManager
 
  return arr;
}

// QUITRELMANAGER
JNIEXPORT void JNICALL Java_de_osz_kurejava_Kurejava_quitRelManager
  (JNIEnv* env, jclass cl, jbyteArray relManager) {
  RelManager* pRelManager = NULL;

  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("quitRelManager"));

  pRelManager = convertByteArrayToPointer(relManager);
  Kure_Quit(pRelManager); // memory allocated by the relmanager is now free
}

// RELATIONNEW
JNIEXPORT jbyteArray JNICALL Java_de_osz_kurejava_Kurejava_relationNew
  (JNIEnv* env, jclass cl, jbyteArray relManager, jstring relationName,jint height, jint width) {
  RelManager* pRelManager = NULL;
  char* pRelationName = NULL;
  Kure_Rel* pRelation = NULL;
  jbyteArray arr = NULL;

  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("relationNew"));

  pRelManager = convertByteArrayToPointer(relManager);
  pRelationName = jStringToChar(relationName);
  
  pRelation = Kure_RelationNew(pRelManager, pRelationName, height, width);
  arr = convertPointerToByteArray(pRelation);

  free(pRelationName);

  return arr;
}

// RELATIONEXISTS
JNIEXPORT jint JNICALL Java_de_osz_kurejava_Kurejava_relationExists
  (JNIEnv* env, jclass cl, jbyteArray relManager, jstring relationName) {
  RelManager* pRelManager = NULL;
  char* pRelationName = NULL;
  jint result;
  
  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("relationExists"));

  pRelManager = convertByteArrayToPointer(relManager);
  pRelationName = jStringToChar(relationName);

  result = Kure_RelationExists(pRelManager, pRelationName);

  free(pRelationName);

  return result;
}

// RELATIONGET
JNIEXPORT jbyteArray JNICALL Java_de_osz_kurejava_Kurejava_relationGet
  (JNIEnv* env, jclass cl, jbyteArray relManager, jstring relationName) {
  RelManager* pRelManager = NULL;
  char* pRelationName = NULL;
  Kure_Rel* pRelation = NULL;
  jbyteArray arr = NULL;

  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("relationGet"));

  pRelManager = convertByteArrayToPointer(relManager);
  pRelationName = jStringToChar(relationName);
  pRelation = Kure_RelationGet(pRelManager, pRelationName);
  arr = convertPointerToByteArray(pRelation);

  free(pRelationName);

  return arr;
}

// RELATIONDELETE
JNIEXPORT jint JNICALL Java_de_osz_kurejava_Kurejava_relationDelete
  (JNIEnv* env, jclass cl, jbyteArray relManager, jbyteArray relation) {
  RelManager* pRelManager = NULL;
  Kure_Rel* pRelation = NULL;

  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("relationDelete"));

  pRelManager = convertByteArrayToPointer(relManager);
  pRelation = convertByteArrayToPointer(relation);

  return Kure_RelationDelete(pRelManager, pRelation);
}

// RELATIONGETHEIGHT
JNIEXPORT jint JNICALL Java_de_osz_kurejava_Kurejava_relationGetHeight
  (JNIEnv* env, jclass cl, jbyteArray relManager, jbyteArray relation) {
  RelManager* pRelManager = NULL;
  Kure_Rel* pRelation = NULL;

  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("relationGetHeight"));

  pRelManager = convertByteArrayToPointer(relManager);
  pRelation = convertByteArrayToPointer(relation);

  return Kure_RelationGetHeight(pRelManager, pRelation);
}

// RELATIONGETWIDTH
JNIEXPORT jint JNICALL Java_de_osz_kurejava_Kurejava_relationGetWidth
  (JNIEnv* env, jclass cl, jbyteArray relManager, jbyteArray relation) {
  RelManager* pRelManager = NULL;
  Kure_Rel* pRelation = NULL;
  
  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("relationGetWidth"));

  pRelManager = convertByteArrayToPointer(relManager);
  pRelation = convertByteArrayToPointer(relation);

  return Kure_RelationGetWidth(pRelManager, pRelation);
}

// RELATIONGETNUMBEROFENTRIES
JNIEXPORT jint JNICALL Java_de_osz_kurejava_Kurejava_relationGetNumberOfEntries
  (JNIEnv* env, jclass cl, jbyteArray relManager, jbyteArray relation) {
  RelManager* pRelManager = NULL;
  Kure_Rel* pRelation = NULL;

  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("relationGetNumberOfEntries"));

  pRelManager = convertByteArrayToPointer(relManager);
  pRelation = convertByteArrayToPointer(relation);

  return Kure_RelationGetNumberOfEntries(pRelManager, pRelation);
}

// RELATIONGETBIT
JNIEXPORT jint JNICALL Java_de_osz_kurejava_Kurejava_relationGetBit
  (JNIEnv* env, jclass cl, jbyteArray relManager, jbyteArray relation,jint row, jint column) {
  RelManager* pRelManager = NULL;
  Kure_Rel* pRelation = NULL;

  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("relationGetBit"));

  pRelManager = convertByteArrayToPointer(relManager);
  pRelation = convertByteArrayToPointer(relation);

  return Kure_RelationGetBit(pRelManager, pRelation, row, column);
}

// RELATIONSETBIT
JNIEXPORT jint JNICALL Java_de_osz_kurejava_Kurejava_relationSetBit
  (JNIEnv* env, jclass cl, jbyteArray relManager, jbyteArray relation,jint row, jint column) {
  RelManager* pRelManager = NULL;
  Kure_Rel* pRelation = NULL;
  
  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("relationSetBit"));

  pRelManager = convertByteArrayToPointer(relManager);
  pRelation = convertByteArrayToPointer(relation);

  return Kure_RelationSetBit(pRelManager, pRelation, row, column);
}

// RELATIONCLEARBIT
JNIEXPORT jint JNICALL Java_de_osz_kurejava_Kurejava_relationClearBit
  (JNIEnv* env, jclass cl, jbyteArray relManager, jbyteArray relation,jint row, jint column) {
  RelManager* pRelManager = NULL;
  Kure_Rel* pRelation = NULL;

  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("relationClearBit"));

  pRelManager = convertByteArrayToPointer(relManager);
  pRelation = convertByteArrayToPointer(relation);

  return Kure_RelationClearBit(pRelManager, pRelation, row, column);
}

// FUNCTIONNEW
JNIEXPORT jint JNICALL Java_de_osz_kurejava_Kurejava_functionNew
  (JNIEnv* env, jclass cl, jbyteArray relManager, jstring function) {
  RelManager* pRelManager = NULL;
  char* pFunction = NULL;
  jint result;

  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("functionNew"));

  pRelManager = convertByteArrayToPointer(relManager);
  pFunction = jStringToChar(function);

  result = Kure_FunctionNew(pRelManager, pFunction);

  free(pFunction);

  return result;
}

// PROGRAMNEW
JNIEXPORT jint JNICALL Java_de_osz_kurejava_Kurejava_programNew
  (JNIEnv* env, jclass cl, jbyteArray relManager, jstring program) {
  RelManager* pRelManager = NULL;
  char* pProgram = NULL;
  jint result;

  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("programNew"));

  pRelManager = convertByteArrayToPointer(relManager);
  pProgram = jStringToChar(program);

  result = Kure_ProgramNew(pRelManager, pProgram);
  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("programNew"));
  
  free(pProgram);

  return result;
}

// PROGRAMFILEREAD
JNIEXPORT jint JNICALL Java_de_osz_kurejava_Kurejava_programFileRead
  (JNIEnv* env, jclass cl, jbyteArray relManager, jstring filename) {
  RelManager* pRelManager = NULL;
  char* pFilename = NULL;
  jint result;
  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("programFileRead"));

  pRelManager = convertByteArrayToPointer(relManager);
  pFilename = jStringToChar(filename);

  result = Kure_ProgramFileRead(pRelManager, pFilename);

  free(pFilename);

  return result;
}

// EVALUATETERM
JNIEXPORT jint JNICALL Java_de_osz_kurejava_Kurejava_evaluateTerm
  (JNIEnv* env, jclass cl, jbyteArray relManager, jstring term, jstring resultRelationName) {
  RelManager* pRelManager = NULL;
  char* pTerm = NULL;
  char* pResultRelationName = NULL;
  jint result;

  if (debug == JNI_TRUE) command(charToJString("print"),charToJString("evaluateTerm"));

  pRelManager = convertByteArrayToPointer(relManager);
  pTerm = jStringToChar(term);
  pResultRelationName = jStringToChar(resultRelationName);

  result = Kure_EvaluateTerm(pRelManager, pTerm, pResultRelationName);

  free(pTerm);
  free(pResultRelationName);

  return result;
}