#include <stdio.h>

//JNI definition (and jni.h)
#include "comediJNI_Comedi.h"

//Sensicam library includes
#include <comedilib.h>
#include <unistd.h>
#include <memory.h>

//define DEBUG

#ifdef DEBUG
#define DBG(x) printf((x));fflush(stdout)
#define DBG2(x,y) printf((x),(y));fflush(stdout)
#else
#define DBG(x)
#define DBG2(x,y)
#endif

/*
 * Class:     comediJNI_Comedi
 * Method:    comedi_open
 * Signature: (Ljava/lang/String;)J
 */
JNIEXPORT jlong JNICALL Java_comediJNI_Comedi_open(JNIEnv *env, jclass cls, jstring fileName){ DBG("A1 ");
	
	comedi_t *it;
	char *fileNameCSTR = (char *)(*env)->GetStringUTFChars(env, fileName, NULL);
	it = comedi_open(fileNameCSTR);

	return (long)it;
}

/*
 * Class:     comediJNI_Comedi
 * Method:    close
 * Signature: (J)V
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_close
  (JNIEnv *env, jclass cls, jlong it){
	return comedi_close((comedi_t *)it);
}


/*
 * Class:     comediJNI_Comedi
 * Method:    strerror
 * Signature: (I)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_comediJNI_Comedi_strerror(JNIEnv *env, jclass cls, jint errnum){ DBG("B1 ");
  	
	const char *errorCSTR = comedi_strerror(errnum);

	return (*env)->NewStringUTF(env, errorCSTR);
}


/*
 * Class:     comediJNI_Comedi
 * Method:    errorno
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_errorno(JNIEnv *env, jclass cls){ DBG("C1 ");
	return comedi_errno();
}

/*
 * Class:     comediJNI_Comedi
 * Method:    data_read
 * Signature: (JIIII)[I
 */
JNIEXPORT jintArray JNICALL Java_comediJNI_Comedi_data_1read(JNIEnv *env, jclass cls, jlong it, jint subd, jint chan, jint range, jint aref){ printf("D1 ");
	lsampl_t data;

	int ret = comedi_data_read((comedi_t *)it, subd, chan, range, aref, &data);

	jintArray result;
	result = (*env)->NewIntArray(env, 2);
	if (result == NULL) { fprintf(stderr, "data_read(): Alloc ret array failed"); return NULL; }
	
	int retArr[2];
	retArr[0] = ret;
	retArr[1] = data;

	(*env)->SetIntArrayRegion(env, result, 0, 2, retArr);
	return result;
}


/*
 * Class:     comediJNI_Comedi
 * Method:    set_global_oor_behavior
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_set_1global_1oor_1behavior
  (JNIEnv *env, jclass cls, jint behavior){ DBG("E1 ");

	return comedi_set_global_oor_behavior(behavior);
}

/*
 * Class:     comediJNI_Comedi
 * Method:    get_range
 * Signature: (JIIILcomediJNI/ComediDef/Range;)V
 */
JNIEXPORT void JNICALL Java_comediJNI_Comedi_get_1range
  (JNIEnv *env, jclass cls, jlong it, jint subdevice, jint chan, jint range, jobject range_obj){ DBG("F1 ");

	comedi_range *range_struct = comedi_get_range((comedi_t *)it, subdevice, chan, range);

	jclass range_class = (*env)->GetObjectClass(env, range_obj);

	(*env)->SetDoubleField(env, range_obj, (*env)->GetFieldID(env, range_class, "min", "D"), range_struct->min);
	(*env)->SetDoubleField(env, range_obj, (*env)->GetFieldID(env, range_class, "max", "D"), range_struct->max);
	(*env)->SetIntField(env, range_obj, (*env)->GetFieldID(env, range_class, "unit", "I"), range_struct->unit);
	
}


/*
 * Class:     comediJNI_Comedi
 * Method:    get_maxdata
 * Signature: (JII)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_get_1maxdata
  (JNIEnv *env, jclass cls, jlong it, jint subdevice, jint chan){ DBG("G1 ");

	lsampl_t ret = comedi_get_maxdata((comedi_t *)it, subdevice, chan);

	return ret;
}

void cmd_in(JNIEnv *env, jobject cmd_obj, comedi_cmd *cmd_struct){ DBG("H1 ");
	jclass cmd_class = (*env)->GetObjectClass(env, cmd_obj);

	memset(cmd_struct, 0, sizeof(comedi_cmd));

	cmd_struct->subdev = (*env)->GetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "subdev", "I"));

	cmd_struct->flags = (*env)->GetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "flags", "I"));
	cmd_struct->start_src = (*env)->GetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "start_src", "I"));
	cmd_struct->start_arg = (*env)->GetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "start_arg", "I"));
	cmd_struct->scan_begin_src = (*env)->GetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "scan_begin_src", "I"));
	cmd_struct->scan_begin_arg = (*env)->GetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "scan_begin_arg", "I"));
	cmd_struct->convert_src = (*env)->GetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "convert_src", "I"));
	cmd_struct->convert_arg = (*env)->GetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "convert_arg", "I"));

	cmd_struct->scan_end_src = (*env)->GetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "scan_end_src", "I"));
	cmd_struct->scan_end_arg = (*env)->GetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "scan_end_arg", "I"));
	cmd_struct->stop_src = (*env)->GetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "stop_src", "I"));
	cmd_struct->stop_arg = (*env)->GetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "stop_arg", "I"));
	cmd_struct->chanlist_len = (*env)->GetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "chanlist_len", "I"));
	cmd_struct->data_len = (*env)->GetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "data_len", "I"));

	jfieldID chanlist_id = (*env)->GetFieldID(env, cmd_class, "chanlist", "[I");
	jobject chanlist_arr_obj = (*env)->GetObjectField(env, cmd_obj, chanlist_id);
DBG2("H7 %p ", chanlist_arr_obj);
	if(chanlist_arr_obj == NULL){
		cmd_struct->chanlist = NULL;
	}else{
	  	jintArray *chanlist_arr = (jintArray*)(&chanlist_arr_obj);
DBG2("H8 %p ", chanlist_arr);
		cmd_struct->chanlist = (unsigned int *) (*env)->GetIntArrayElements(env, *chanlist_arr, NULL);
	}
DBG2("H9 %p", cmd_struct->chanlist);

	jfieldID data_id = (*env)->GetFieldID(env, cmd_class, "data", "[S");
	jobject data_arr_obj = (*env)->GetObjectField(env, cmd_obj, data_id);
	if(data_arr_obj == NULL){
		cmd_struct->data = NULL;
	}else{
  		jshortArray *data_arr = (jshortArray*)(&data_arr_obj);
		cmd_struct->data = (unsigned short *) (*env)->GetShortArrayElements(env, *data_arr, NULL);
	}
DBG("H10 ");

}


void cmd_out(JNIEnv *env, jobject cmd_obj, comedi_cmd *cmd_struct){ DBG("I1 ");
	jclass cmd_class = (*env)->GetObjectClass(env, cmd_obj);

	(*env)->SetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "subdev", "I"), cmd_struct->subdev);
	(*env)->SetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "flags", "I"), cmd_struct->flags);
	(*env)->SetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "start_src", "I"), cmd_struct->start_src);
	(*env)->SetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "start_arg", "I"), cmd_struct->start_arg);
	(*env)->SetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "scan_begin_src", "I"), cmd_struct->scan_begin_src);
	(*env)->SetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "scan_begin_arg", "I"), cmd_struct->scan_begin_arg);
	(*env)->SetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "convert_src", "I"), cmd_struct->convert_src);
	(*env)->SetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "convert_arg", "I"), cmd_struct->convert_arg);
	(*env)->SetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "scan_end_src", "I"), cmd_struct->scan_end_src);
	(*env)->SetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "scan_end_arg", "I"), cmd_struct->scan_end_arg);
	(*env)->SetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "stop_src", "I"), cmd_struct->stop_src);
	(*env)->SetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "stop_arg", "I"), cmd_struct->stop_arg);
	(*env)->SetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "chanlist_len", "I"), cmd_struct->chanlist_len);
	(*env)->SetIntField(env, cmd_obj, (*env)->GetFieldID(env, cmd_class, "data_len", "I"), cmd_struct->data_len);

	//(*env)->SetIntArrayRegion(env, result, 0, 2, retArr);

	jfieldID chanlist_id = (*env)->GetFieldID(env, cmd_class, "chanlist", "[I");
	jobject chanlist_arr_obj = (*env)->GetObjectField(env, cmd_obj, chanlist_id);
	if(chanlist_arr_obj == NULL){
		if(cmd_struct->chanlist != NULL){
			printf("chanlist was null from java and is now filled!!\n");
		}
	}else{
 	 	jintArray *chanlist_arr = (jintArray*)(&chanlist_arr_obj);	
		(*env)->ReleaseIntArrayElements(env, *chanlist_arr, (int *)cmd_struct->chanlist, 0);
	}

	jfieldID data_id = (*env)->GetFieldID(env, cmd_class, "data", "[S");
	jobject data_arr_obj = (*env)->GetObjectField(env, cmd_obj, data_id);
	if(data_arr_obj == NULL){
		if(cmd_struct->data != NULL){
			printf("data was null from java and is now filled!!\n");
		}
	}else{
	  	jshortArray *data_arr = (jshortArray*)(&data_arr_obj);
		(*env)->ReleaseShortArrayElements(env, *data_arr, (short *)cmd_struct->data, 0);
	}
}

/*
 * Class:     comediJNI_Comedi
 * Method:    command_test
 * Signature: (JLcomediJNI/ComediDef/Cmd;)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_command_1test
  (JNIEnv *env, jclass cls, jlong it, jobject cmd_obj){ DBG("J1 ");
	comedi_cmd cmd_struct;
	cmd_in(env, cmd_obj, &cmd_struct);

	int ret = comedi_command_test((comedi_t *)it, &cmd_struct);

	cmd_out(env, cmd_obj, &cmd_struct);

	return ret;
}

/*
 * Class:     comediJNI_Comedi
 * Method:    command
 * Signature: (JLcomediJNI/ComediDef/Cmd;)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_command
  (JNIEnv *env, jclass cls, jlong it, jobject cmd_obj){ DBG("K1 ");
	comedi_cmd cmd_struct;
	cmd_in(env, cmd_obj, &cmd_struct);

	#ifdef DEBUG
	int j;
	for(j=0; j < sizeof(comedi_cmd); j++){
		if((j % 16) == 0) printf("\n%04X: ", j);
		printf("%02X ", ((unsigned char *)&cmd_struct)[j]);

	}
	for(j=0; j < (cmd_struct.chanlist_len * sizeof(unsigned int)); j++){
		if((j % 16) == 0) printf("\n%04X: ", j);
		printf("%02X ", ((unsigned char *)cmd_struct.chanlist)[j]);

	}
	#endif
		
	int ret = comedi_command((comedi_t *)it, &cmd_struct);

	cmd_out(env, cmd_obj, &cmd_struct);

	return ret;
}


JNIEXPORT jint JNICALL Java_comediJNI_Comedi_cancel
  (JNIEnv *env, jclass cls, jlong it, jint subdevice) {
	return comedi_cancel((comedi_t *)it, subdevice);
}

/*
 * Class:     comediJNI_Comedi
 * Method:    get_subdevice_flags
 * Signature: (JI)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_get_1subdevice_1flags
  (JNIEnv *env, jclass cls, jlong it, jint subdevice){ DBG("L1 ");

	return comedi_get_subdevice_flags((comedi_t *)it, subdevice);

}

/*
 * Class:     comediJNI_Comedi
 * Method:    fileno
 * Signature: (J)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_fileno
  (JNIEnv *env, jclass cls, jlong it){ DBG("M1 ");

	return comedi_fileno((comedi_t *)it);
}

/*
 * Class:     comediJNI_Comedi
 * Method:    get_cmd_generic_timed
 * Signature: (JILcomediJNI/ComediDef/Cmd;II)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_get_1cmd_1generic_1timed
  (JNIEnv *env, jclass cls, jlong it, jint subdevice, jobject cmd_obj, jint chanlist_len, jint scan_period_ns){ DBG("N1 ");
	comedi_cmd cmd_struct;
	cmd_in(env, cmd_obj, &cmd_struct);

	int ret = comedi_get_cmd_generic_timed((comedi_t *)it, subdevice, &cmd_struct, chanlist_len, scan_period_ns);

	cmd_out(env, cmd_obj, &cmd_struct);

	return ret;
}

/*
 * Class:     comediJNI_Comedi
 * Method:    to_phys
 * Signature: (ILcomediJNI/ComediDef/Range;I)D
 */
JNIEXPORT jdouble JNICALL Java_comediJNI_Comedi_to_1phys
  (JNIEnv *env, jclass cls, jint data, jobject range_obj, jint maxdata){ DBG("O1 ");
	comedi_range range_struct;

	jclass range_class = (*env)->GetObjectClass(env, range_obj);
	range_struct.min = (*env)->GetDoubleField(env, range_obj, (*env)->GetFieldID(env, range_class, "min", "D"));
	range_struct.max = (*env)->GetDoubleField(env, range_obj, (*env)->GetFieldID(env, range_class, "max", "D"));
	range_struct.unit = (*env)->GetIntField(env, range_obj, (*env)->GetFieldID(env, range_class, "unit", "I"));
	
	return comedi_to_phys(data, &range_struct, maxdata);
}

/*
 * Class:     comediJNI_Comedi
 * Method:    read
 * Signature: (J[BI)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_read
  (JNIEnv *env, jclass cls, jlong it, jbyteArray data_arr, jint dataSize){ DBG("P1 "); 

	unsigned char *data = (unsigned char *) (*env)->GetByteArrayElements(env, data_arr, NULL);

	//that's the standard C read
	int ret = read(comedi_fileno((comedi_t *)it), data, dataSize);
	#ifdef DEBUG
	printf("read(dataSize=%i) = %i\n", dataSize, ret); fflush(stdout);
	#endif

	(*env)->ReleaseByteArrayElements(env, data_arr, (jbyte *)data, 0);
	
	return ret;
}



/* (Uses JNI foo to access FileDescriptor's private .fd member)
 *
 * Class:     comediJNI_Comedi
 * Method:    getFileDesc
 * Signature: (J)Ljava/io/FileDescriptor;
 */
JNIEXPORT jobject JNICALL Java_comediJNI_Comedi_getFileDesc
  (JNIEnv *env, jclass cls, jlong it){

	int fd = comedi_fileno((comedi_t *)it);

	if(fd < 0){
		fprintf(stderr, "ERROR ComediJNI.getFileDesc(): comedi_fileno() returned %i\n", fd);
		return NULL;
	}

	// construct a new FileDescriptor
	jclass class_fdesc = (*env)->FindClass(env, "java/io/FileDescriptor");
	jmethodID const_fdesc = (*env)->GetMethodID(env, class_fdesc, "<init>", "()V");
	if (const_fdesc == NULL){ fprintf(stderr, "JNI.GetMethodID('java/io/FileDescriptor') failed\n"); return NULL; }
	jobject ret = (*env)->NewObject(env, class_fdesc, const_fdesc);

	// poke the "fd" field with the file descriptor
	jfieldID field_fd = (*env)->GetFieldID(env, class_fdesc, "fd", "I");
	if (field_fd == NULL){ fprintf(stderr, "JNI.GetFieldID('FileDescriptor.fd') failed\n"); return NULL; }
	(*env)->SetIntField(env, ret, field_fd, fd);

	return ret;

}

/*
 * Class:     comediJNI_Comedi
 * Method:    internal_trigger
 * Signature: (JII)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_internal_1trigger
  (JNIEnv *env, jclass cls, jlong it, jint subdev, jint trignum){

	return comedi_internal_trigger((comedi_t *)it, subdev, trignum);
}

/*
 * Class:     comediJNI_Comedi
 * Method:    find_subdevice_by_type
 * Signature: (JII)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_find_1subdevice_1by_1type
  (JNIEnv *env, jclass cls, jlong it, jint type, jint subdev){

	return comedi_find_subdevice_by_type((comedi_t *)it, type, subdev);
}

/*
 * Class:     comediJNI_Comedi
 * Method:    dio_config
 * Signature: (JIII)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_dio_1config
  (JNIEnv *env, jclass cls, jlong it, jint subd, jint chan, jint dir) {

	return comedi_dio_config((comedi_t *)it, subd, chan, dir);
}


/*
 * Class:     comediJNI_Comedi
 * Method:    get_subdevice_type
 * Signature: (JI)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_get_1subdevice_1type
  (JNIEnv *env, jclass cls, jlong it, jint subdevice){

	return comedi_get_subdevice_type((comedi_t *)it, subdevice);
}


/*
 * Class:     comediJNI_Comedi
 * Method:    reset
 * Signature: (JI)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_reset
  (JNIEnv *env, jclass cls, jlong it, jint subdevice) {

	return comedi_reset((comedi_t *)it, subdevice);
}


/*
 * Class:     comediJNI_Comedi
 * Method:    arm
 * Signature: (JII)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_arm
  (JNIEnv *env, jclass cls, jlong it, jint subdevice, jint source) {

	return comedi_arm((comedi_t *)it, subdevice, source);
}


/*
 * Class:     comediJNI_Comedi
 * Method:    data_write
 * Signature: (JIIIII)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_data_1write
  (JNIEnv *env, jclass cls, jlong it, jint subd, jint chan, jint range, jint aref, jint data) {

	return comedi_data_write((comedi_t *)it, subd, chan,  range, aref, data);
}

/*
 * Class:     comediJNI_Comedi
 * Method:    set_counter_mode
 * Signature: (JIII)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_set_1counter_1mode
  (JNIEnv *env, jclass cls, jlong it, jint subdevice, jint channel, jint mode_bits) {

	return comedi_set_counter_mode((comedi_t *)it, subdevice, channel, mode_bits);
}

/*
 * Class:     comediJNI_Comedi
 * Method:    set_gate_source
 * Signature: (JIIII)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_set_1gate_1source
  (JNIEnv *env, jclass cls, jlong it, jint subdevice, jint channel, jint gate_index, jint gate_source) {

	return comedi_set_gate_source((comedi_t *)it, subdevice, channel, gate_index, gate_source);
}

/*
 * Class:     comediJNI_Comedi
 * Method:    set_clock_source
 * Signature: (JIIII)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_set_1clock_1source
  (JNIEnv *env, jclass cls, jlong it, jint subdevice, jint channel, jint clock, jint period_ns) {

	return comedi_set_clock_source((comedi_t *)it, subdevice, channel, clock, period_ns);
}


/*
 * Class:     comediJNI_Comedi
 * Method:    get_buffer_size
 * Signature: (JI)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_get_1buffer_1size
  (JNIEnv *env, jclass cls, jlong it, jint subdevice) {

	return comedi_get_buffer_size((comedi_t *)it, subdevice);
}


/*
 * Class:     comediJNI_Comedi
 * Method:    get_buffer_contents
 * Signature: (JI)I
 */
JNIEXPORT jint JNICALL Java_comediJNI_Comedi_get_1buffer_1contents
 (JNIEnv *env, jclass cls, jlong it, jint subdevice) {

	return comedi_get_buffer_contents((comedi_t *)it, subdevice);
}

