/*
  Native entries for the HPrimitive class

  @author Jeff Schoen
  @author J. Czechowski IV
*/

#include <stdlib.h> /* needed for malloc to work correctly on 64-bit systems */

#include "cdefs.h"
#include "native.h"
#include "context.h"
#include "commandable.h"
#include "xmcompat.h"
#include "nxm_xbc_lib_HPrimitive.h"

/* These methods are defined in xmlibc.c */
void    libHostInit   (JNIEnv *env, jobject xm);
void   *initContext   (JNIEnv *env, jobject obj, jobject nm);
int     initCommand   (JNIEnv *env, jobject obj, jobject nm, jint debuglevel);
void    m$setbreak_   (int *value);
int     j2fstr        (JNIEnv *env, jobject jstr, char *fstr, int fstrlen);
jarray  getStackTrace (jlong st);


/**************************************************************************************************/
/* METHODS FOR DYNAMIC LOADING FROM A SHARED OBJECT                                               */
/**************************************************************************************************/
typedef void (*funcPointer_t)();
typedef struct {
  void          *handle;   /* Only used by loadRoutine and freeRoutine */
  void          *symbol;   /* Only used by loadRoutine and freeRoutine */
  funcPointer_t  func;     /* Callable function. */
} routine_t;


#if _LINX
#include <dlfcn.h>
#ifndef RTLD_LAZY             /* This should already be defined but if not */
#define RTLD_LAZY 0x00001     /* dlopen MODE: Lazy function call binding.  */
#endif

static routine_t *loadRoutine (const char *path, const char *subroutine) {
  routine_t  *routine;
  char        fsubroutine[80];
  int         dl_flags;

  /* Setup the routine structure. */
  routine = (routine_t *) malloc(sizeof(routine_t));
  if (routine == NULL) {
    fprintf(stderr, "HPrimitive.c.loadRoutine(): Unable to allocate memory for routine_t !");
    return NULL;
  }
  memset(routine, 0, sizeof(routine_t)); /* Null everything out */

  /* Get handle to the shared object */
  dl_flags = RTLD_LAZY;
  routine->handle = dlopen(path,dl_flags);
  if (routine->handle == NULL) {
    fprintf(stderr, "HPrimitive.c.loadRoutine(): Error in dlopen(..): %s\n",dlerror());
    free(routine);
    return NULL;
  }

  /* Get the pointer to the symbol */
  routine->symbol = dlsym(routine->handle, subroutine);
  if (routine->symbol == NULL) { /* Try again but with an '_' appended (typical of Fortran) */
    dlerror(); /* Clear the error. */
    snprintf(fsubroutine, 80, "%s_", subroutine);
    routine->symbol = dlsym(routine->handle, fsubroutine);
  }
  if (routine->symbol == NULL) {
    fprintf(stderr, "HPrimitive.c.loadRoutine(): Error in dlsym(..): %s\n",dlerror());
    free(routine);
    return NULL;
  }

  /* Set the function pointer */
  routine->func = (funcPointer_t)routine->symbol;

  return routine;
}
static routine_t *freeRoutine (routine_t *routine) {
  int err;
  if (routine != NULL) {
    if (routine->handle != NULL) {
      err = dlclose(routine->handle);
      if (err) fprintf(stderr, "HPrimitive.c.freeRoutine(): Error in dlclose(..): %s\n",dlerror());
    }
    memset(routine, 0, sizeof(routine_t)); /* Null everything out */
  }
  return NULL;
}
#else
static routine_t *loadRoutine (const char *path, const char *subroutine) {
  fprintf(stderr, "ERROR in XBC: HPrimitive.c.loadRoutine(..) not implemented for this platform.");
  return NULL;
}
static routine_t *freeRoutine (routine_t *routine) {
  fprintf(stderr, "ERROR in XBC: HPrimitive.c.freeRoutine(..) not implemented for this platform.");
  return NULL;
}
#endif



/**************************************************************************************************/
/* NATIVE METHODS CALLED BY HPRIMITIVE.JAVA                                                       */
/**************************************************************************************************/
JNIEXPORT void JNICALL Java_nxm_xbc_lib_HPrimitive_libHostInit (JNIEnv *env, jclass hp, jobject xm) {
  if (sizeof(M_PTR_T) != sizeof(size_t)) {
    int exp = (sizeof(M_PTR_T) == 4)? 32 : 64;
    int act = (sizeof(size_t)  == 4)? 32 : 64;

    fprintf(stderr, "ERROR: ***********************************************\n");
    fprintf(stderr, "ERROR: **                   ERROR                   **\n");
    fprintf(stderr, "ERROR: ** XBC has been compiled for a %d-bit system **\n", exp);
    fprintf(stderr, "ERROR: ** but is being run on a %d-bit system.      **\n", act);
    fprintf(stderr, "ERROR: ***********************************************\n");
  }
  libHostInit(env,xm);
}

/** init() loads HOST Primitive shared object (library) and subroutine to be invoke in exec() */
JNIEXPORT jint JNICALL Java_nxm_xbc_lib_HPrimitive_init (JNIEnv *env, jobject obj) {
  jstring jpath = GetStringObject(obj,"libpath");    /* e.g. "myprim_lin.so" */
  jstring jsub  = GetStringObject(obj,"subroutine"); /* e.g. "mh$myprim" (no ending "_") */
  const char *path = GetString(jpath);
  const char *sub  = GetString(jsub);
  routine_t *routine = loadRoutine(path,sub);
  ReleaseString(jpath,path);
  ReleaseString(jsub,sub);
  if (routine==NULL) return ABORT;
  SetPointer(obj,"handle",routine);
  return INIT;
}

/** exec() is executed inside the Primitive's thread */
JNIEXPORT jint JNICALL Java_nxm_xbc_lib_HPrimitive_exec (JNIEnv *env, jobject obj, jobject nmjackets) {
  routine_t *routine = (routine_t*)GetPointer(obj,"handle"); // this was set in init()
  jint      debug    = GetStaticInt(obj,"debug");
  void      *context = initContext(env,obj,nmjackets);

  /* Since NeXtMidas 2.7.2, initCommand() is called to execute common Primitive startup code
   * before m$chkin (Fortran) or m_checkin (C++) are called by the Primitive's mainroutine below */
  int result = initCommand(env,obj,nmjackets,debug); /* similar to X-Midas's M$COMMAND_ENTRY() and M$INIT_PRIMTIVE() */
  if (result != 0) { /* non-zero means error, abort command. */
    fprintf(stderr, "ERROR: HPrimitive.exec(): initCommand(..) returned error! Aborting command.\n");
    return ABORT;
  } else {
    if (debug>0) printf("DEBUG: HPrimitive.exec() initCommand result = %d\n", result);
  }

  SetPointer(obj,"context",context);
  (*env)->PushLocalFrame(env,1024);
  (*routine->func)();
  (*env)->PopLocalFrame(env,NULL);
  return NORMAL;
}

JNIEXPORT jint JNICALL Java_nxm_xbc_lib_HPrimitive_free (JNIEnv *env, jobject obj) {
  routine_t *routine = (routine_t*)GetPointer(obj,"handle");
  routine = freeRoutine(routine);
  SetPointer(obj,"handle",routine);
  return NORMAL;
}

JNIEXPORT jint JNICALL Java_nxm_xbc_lib_HPrimitive_findControl (JNIEnv *env, jobject obj, jobject key) {
  char cname[16]; jint i,j,ln;
  Context *ctx = (Context*)GetPointer(obj,"context");
  ln = j2fstr (env,key,cname,16);
  for (i=0; i<ctx->ncontrols; i++) {
    for (j=0; j<ln; j++) if (cname[j]!=ctx->controls[i].name[j]) j=99;
    if (j==ln) return i;
  }
  return -1;
}

JNIEXPORT jint JNICALL Java_nxm_xbc_lib_HPrimitive_getControlType (JNIEnv *env, jobject obj, jint cid) {
  char *carray; jint i,j,ln;
  Context *ctx = (Context*)GetPointer(obj,"context");
  if (cid<0 || cid>=ctx->ncontrols) return -1;
  return ctx->controls[cid].type;
}

JNIEXPORT jint JNICALL Java_nxm_xbc_lib_HPrimitive_getControlName (JNIEnv *env, jobject obj, jint cid, jobject array) {
  char *carray; jint i,j,ln;
  Context *ctx = (Context*)GetPointer(obj,"context");
  if (cid<0) return ctx->ncontrols;
  if (cid>=ctx->ncontrols) return -1;
  carray = GetArray(array);
  for (i=0; i<16; i++) carray[i] = ctx->controls[cid].name[i];
  for (ln=16; ln>0 && carray[ln-1]<=' '; ln--);
  ReleaseArray(array,carray);
  return ln;
}

JNIEXPORT jint JNICALL Java_nxm_xbc_lib_HPrimitive_getControlParameters (JNIEnv *env, jobject obj, jint cid, jobject array) {
  char *carray; jint i,j,ln;
  Context *ctx = (Context*)GetPointer(obj,"context");
  if (cid<0 || cid>=ctx->ncontrols) return -1;
  carray = GetArray(array);
  for (i=0; i<ctx->controls[cid].llist; i++) carray[i] = ctx->controls[cid].list[i];
  ReleaseArray(array,carray);
  return ctx->controls[cid].llist;
}

JNIEXPORT jdouble JNICALL Java_nxm_xbc_lib_HPrimitive_getControl (JNIEnv *env, jobject obj, jint cid) {
  Context *ctx = (Context*)GetPointer(obj,"context");
  if (ctx->controls[cid].type <= 2)
    return (double)ctx->controls[cid].lval;
  else
    return (double)ctx->controls[cid].dval;
}

JNIEXPORT void JNICALL Java_nxm_xbc_lib_HPrimitive_setControl (JNIEnv *env, jobject obj, jint cid, jdouble value) {
  Context *ctx = (Context*)GetPointer(obj,"context");
  if (ctx->controls[cid].type <= 2)
    ctx->controls[cid].lval = (int_4)value;
  else
    ctx->controls[cid].dval = value;
}

JNIEXPORT void JNICALL Java_nxm_xbc_lib_HPrimitive_setInterrupt (JNIEnv *env, jobject obj, jint value) {
  Context *ctx = (Context*)GetPointer(obj,"context");
  if (ctx != NULL && ctx->mc !=NULL) {
    ctx->mc->break_ = value;
  }
}

JNIEXPORT jarray JNICALL Java_nxm_xbc_lib_HPrimitive_getStackTrace (JNIEnv *env, jclass clazz, jlong st) {
  return getStackTrace(st);
}
