/*
  SerialInterface.cpp 
  
  The Win32 native methods for communicating with Lego NXT bricks.
  
  Compile as a C library, not as a C++ library.
*/

#ifndef WINVER				// Allow use of features specific to Windows XP or later.
#define WINVER 0x0501		// Change this to the appropriate value to target other versions of Windows.
#endif

#ifndef _WIN32_WINNT		// Allow use of features specific to Windows XP or later.                   
#define _WIN32_WINNT 0x0501	// Change this to the appropriate value to target other versions of Windows.
#endif						

#ifndef _WIN32_WINDOWS		// Allow use of features specific to Windows 98 or later.
#define _WIN32_WINDOWS 0x0410 // Change this to the appropriate value to target Windows Me or later.
#endif

#ifndef _WIN32_IE			// Allow use of features specific to IE 6.0 or later.
#define _WIN32_IE 0x0600	// Change this to the appropriate value to target other versions of IE.
#endif

#define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers
// Windows Header Files:
#include <windows.h>
//#include <stdio.h>
//#include "stdafx.h"

#include <jni.h>

/*
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
    return TRUE;
}
*/

/*

  

 */
static char errmsg[512];
static void set_error_message() {
  DWORD errorcode = GetLastError();
  FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 
                0,
                errorcode,
                0,
                errmsg, 
                511,
                NULL);
}

JNIEXPORT jstring JNICALL Java_sivantoledo_nxt_SerialInterface_nlasterror
  (JNIEnv *env, jclass C)
{
  return (*env)->NewStringUTF(env, errmsg);
}


JNIEXPORT jint JNICALL Java_sivantoledo_nxt_SerialInterface_ninvalid
  (JNIEnv *env, jclass C)
{
  union {
    HANDLE h;
    jint   jih;
  } handle_union;

  handle_union.h = INVALID_HANDLE_VALUE;
  return handle_union.jih;
}
JNIEXPORT jint JNICALL Java_sivantoledo_nxt_SerialInterface_nhandlesize
  (JNIEnv *env, jclass C)
{
  return (jint) sizeof(HANDLE);
}
JNIEXPORT jint JNICALL Java_sivantoledo_nxt_SerialInterface_ngetaborthandle
  (JNIEnv *env, jclass C)
{
  union {
    HANDLE h;
    jint   jih;
  } handle_union;

 	handle_union.h = CreateEvent(NULL, /* security attributes */ 
                               TRUE, /* manual reset */
                               FALSE, /* intialize to nonsignaled */
                               NULL   /* no name */
                              );

  return handle_union.jih;
}

JNIEXPORT void JNICALL Java_sivantoledo_nxt_SerialInterface_nabort
  (JNIEnv *env, jclass C, jint handle)
{
  union {
    HANDLE h;
    jint   jih;
  } handle_union;

  handle_union.jih = handle;

  SetEvent(handle_union.h);
}

JNIEXPORT jint JNICALL Java_sivantoledo_nxt_SerialInterface_nopen
  (JNIEnv *env, jclass C, jstring name)
{

  const char *sname = (*env)->GetStringUTFChars(env, name, 0); 

  union {
    HANDLE h;
    jint   jih;
  } handle_union;

  COMMTIMEOUTS comTimeOut;                   

 	handle_union.h
    = CreateFile(sname,  // Specify port device: default "COM1"
	               GENERIC_READ | GENERIC_WRITE,       // Specify mode that open device.
                 0,                                  // the devide isn't shared.
                 NULL,                               // the object gets a default security.
                 OPEN_EXISTING,                      // Specify which action to take on file. 
                 FILE_FLAG_OVERLAPPED,               // default.
                 NULL);                              // default.
  if (handle_union.h == INVALID_HANDLE_VALUE) set_error_message();

  //GetCommTimeouts(h,&comTimeOut);
  //printf("read interval timeout   = %d\n",comTimeOut.ReadIntervalTimeout);
  //printf("read multiplier timeout = %d\n",comTimeOut.ReadTotalTimeoutMultiplier);
  //printf("read constant timeout   = %d\n",comTimeOut.ReadTotalTimeoutConstant);

  comTimeOut.ReadIntervalTimeout         = 3;
  comTimeOut.ReadTotalTimeoutMultiplier  = 3;
  comTimeOut.ReadTotalTimeoutConstant    = 60000;
  comTimeOut.WriteTotalTimeoutMultiplier = 3;
  comTimeOut.WriteTotalTimeoutConstant   = 60000;

  SetCommTimeouts(handle_union.h,&comTimeOut);

  (*env)->ReleaseStringUTFChars(env, name, sname);

	return handle_union.jih;
}

JNIEXPORT jint JNICALL Java_sivantoledo_nxt_SerialInterface_nclose
  (JNIEnv *env, jclass C, jint handle, jint abort_handle)
{

  union {
    HANDLE h;
    jint   jih;
  } handle_union;

  union {
    HANDLE h;
    jint   jih;
  } abort_union;

  handle_union.jih = handle;

  abort_union.jih = abort_handle;

  //CancelIoEx(handle_union.h, NULL); // cancel pending reads!
  CloseHandle(handle_union.h);
  CloseHandle(abort_union.h);

  return 0;
}

  JNIEXPORT jint JNICALL Java_sivantoledo_nxt_SerialInterface_nreceive_orig
  (JNIEnv *env, jclass C, jint handle, jbyteArray outbuffer)
{
  union {
    HANDLE h;
    jint   jih;
  } handle_union;

  unsigned char buffer[256];

  DWORD bytes_received;
  DWORD total = 0;
  DWORD packet_length;

  OVERLAPPED ol;

  memset(&ol,0,sizeof(OVERLAPPED));
  ol.Offset = 0;
  ol.OffsetHigh = 0;
  ol.hEvent = 0;
  ol.hEvent = CreateEvent(NULL, /* security attributes */ 
                          TRUE, /* manual reset */
                          FALSE, /* intialize to nonsignaled */
                          NULL   /* no name */
                         );

  handle_union.jih = handle;

  /* read the two-byte header containing the length of the message */

  while (total < 2) {
    //printf("Calling ReadFile, total so far = %d\n",total);
    //printf(">>> calling ReadFile\n");
    if (ReadFile(handle_union.h, buffer+total, 2-total, &bytes_received, NULL) == 0) {
      //printf(">>> ReadFile error\n");
      set_error_message();
      //printf("ReadFile error");
      return -1;
    } else if (bytes_received == 0) {
      // possibly a time out
      //printf(">>> timeout !?!\n");
      return 0;
    } else {
      total += bytes_received;
    }
  }

  packet_length = buffer[0] + 256*buffer[1]; /* LSB, MSB */
  //printf("packet length = %d\n",packet_length);

  while (total < 2+packet_length) {
    //printf("Calling ReadFile, total so far = %d\n",total);
    if (ReadFile(handle_union.h, buffer + total, (2+packet_length)-total, &bytes_received,NULL) == 0) {
      //printf("ReadFile error");
      set_error_message();
      return -1;
    } else if (bytes_received == 0) {
      // possibly a time out
      //printf(">>> timeout !?!\n");
      return 0;
    } else {
      total += bytes_received;
    }
  }

  //printf("done reading the packet, analyzing it now\n");

  (*env)->SetByteArrayRegion(env, outbuffer, 0, packet_length, (jbyte*) (buffer+2));

  return packet_length;
  //return (*env)->GetArrayLength(env, buffer);
	//return 42;
}

JNIEXPORT jint JNICALL Java_sivantoledo_nxt_SerialInterface_nreceive
  (JNIEnv *env, jclass C, jint handle, jbyteArray outbuffer, jint abort_handle)
{
  union {
    HANDLE h;
    jint   jih;
  } handle_union;

  union {
    HANDLE h;
    jint   jih;
  } abort_union;
  HANDLE waits[2];
  DWORD which;

  unsigned char buffer[256];

  DWORD bytes_received;
  DWORD total = 0;
  DWORD packet_length;

  OVERLAPPED ol;

  DWORD rc;

  memset(&ol,0,sizeof(OVERLAPPED));
  ol.Offset = 0;
  ol.OffsetHigh = 0;
  ol.hEvent = 0;
  ol.hEvent = CreateEvent(NULL, /* security attributes */ 
                          TRUE, /* manual reset */
                          FALSE, /* intialize to nonsignaled */
                          NULL   /* no name */
                         );

  handle_union.jih = handle;

  abort_union.jih = abort_handle;

  waits[1] = ol.hEvent;
  waits[0] = abort_union.h;

  /* read the two-byte header containing the length of the message */

  while (total < 2) {
    //printf("Calling ReadFile, total so far = %d\n",total);
    rc = ReadFile(handle_union.h, buffer + total, (2)-total, NULL,&ol);
    if (!rc && GetLastError() != ERROR_IO_PENDING) {
      set_error_message();
      CloseHandle(ol.hEvent);
      return -1;
    }
    which = WaitForMultipleObjects(2, waits, FALSE, INFINITE);
    if (which == WAIT_OBJECT_0) {
      CancelIo(handle_union.h);
      CloseHandle(ol.hEvent);
      return -1;
    }
    rc = GetOverlappedResult(handle_union.h,&ol,&bytes_received,TRUE /* wait */);
    if (!rc) {
      set_error_message();
      CloseHandle(ol.hEvent);
      return -1;
    }
    total += bytes_received;
  }

  packet_length = buffer[0] + 256*buffer[1]; /* LSB, MSB */
  //printf("packet length = %d\n",packet_length);

  while (total < 2+packet_length) {
    //printf("Calling ReadFile, total so far = %d\n",total);
    rc = ReadFile(handle_union.h, buffer + total, (2+packet_length)-total, NULL,&ol);
    if (!rc && GetLastError() != ERROR_IO_PENDING) {
      set_error_message();
      CloseHandle(ol.hEvent);
      return -1;
    }
    which = WaitForMultipleObjects(2, waits, FALSE, INFINITE);
    if (which == WAIT_OBJECT_0) {
      CancelIo(handle_union.h);
      CloseHandle(ol.hEvent);
      return -1;
    }
    rc = GetOverlappedResult(handle_union.h,&ol,&bytes_received,TRUE /* wait */);
    if (!rc) {
      set_error_message();
      CloseHandle(ol.hEvent);
      return -1;
    }
    total += bytes_received;
  }

  //printf("done reading the packet, analyzing it now\n");

  (*env)->SetByteArrayRegion(env, outbuffer, 0, packet_length, (jbyte*) (buffer+2));

  CloseHandle(ol.hEvent);

  return packet_length;
  //return (*env)->GetArrayLength(env, buffer);
	//return 42;
}

JNIEXPORT jint JNICALL Java_sivantoledo_nxt_SerialInterface_nsend
  (JNIEnv *env, jclass C, jint handle, jbyteArray inbuffer, jint length, jint abort_handle)
{
  int i;
  union {
    HANDLE h;
    jint   jih;
  } handle_union;

  union {
    HANDLE h;
    jint   jih;
  } abort_union;
  HANDLE waits[2];
  DWORD which;

  unsigned char buffer[256];

  DWORD bytes_sent;
  DWORD total = 0;

  OVERLAPPED ol;

  DWORD rc;

  memset(&ol,0,sizeof(OVERLAPPED));
  ol.Offset = 0;
  ol.OffsetHigh = 0;
  ol.hEvent = 0;
  ol.hEvent = CreateEvent(NULL, /* security attributes */ 
                          TRUE, /* manual reset */
                          FALSE, /* intialize to nonsignaled */
                          NULL   /* no name */
                         );

  handle_union.jih = handle;

  abort_union.jih = abort_handle;

  waits[1] = ol.hEvent;
  waits[0] = abort_union.h;

  buffer[0] = (unsigned char) (length % 256);
  buffer[1] = (unsigned char) (length / 256);

  (*env)->GetByteArrayRegion(env, inbuffer, 0, length, (jbyte*) (buffer+2));

  for (i=0; i<2+length; i++)
    //fprintf(stderr,">>> %02x\n",buffer[i]);

  while (total < (DWORD) 2+length) {
    //printf("Calling ReadFile, total so far = %d\n",total);
    rc = WriteFile(handle_union.h, buffer + total, (2+length)-total, NULL,&ol);
    if (!rc && GetLastError() != ERROR_IO_PENDING) {
      set_error_message();
      CloseHandle(ol.hEvent);
      return -1;
    }
    //WaitForSingleObject(abort_union.h,INFINITE);
    //return -9;
    which = WaitForMultipleObjects(2, waits, FALSE, INFINITE);
    if (which == WAIT_OBJECT_0) {
      //return -177;
      CancelIo(handle_union.h);
      CloseHandle(ol.hEvent);
      return -1;
    }
    rc = GetOverlappedResult(handle_union.h,&ol,&bytes_sent,TRUE /* wait */);
    if (!rc) {
      set_error_message();
      CloseHandle(ol.hEvent);
      return -1;
    }
    total += bytes_sent;
  }

  CloseHandle(ol.hEvent);
  return 0;
}

JNIEXPORT jint JNICALL Java_sivantoledo_nxt_SerialInterface_nsend_orig
  (JNIEnv *env, jclass C, jint handle, jbyteArray inbuffer, jint length)
{
  int i;
  union {
    HANDLE h;
    jint   jih;
  } handle_union;

  unsigned char buffer[256];

  DWORD bytes_sent;
  DWORD total = 0;

  handle_union.jih = handle;

  buffer[0] = (unsigned char) (length % 256);
  buffer[1] = (unsigned char) (length / 256);

  (*env)->GetByteArrayRegion(env, inbuffer, 0, length, (jbyte*) (buffer+2));

  for (i=0; i<2+length; i++)
    //fprintf(stderr,">>> %02x\n",buffer[i]);

  while (total < (DWORD) 2+length) {
    //printf("Calling ReadFile, total so far = %d\n",total);
    if (WriteFile(handle_union.h, buffer + total, (2+length)-total, &bytes_sent, NULL) == 0) {
      //printf("ReadFile error");
      return -1;
    } else {
      total += bytes_sent;
    }
  }

  return 0;
  //return length + (*env)->GetArrayLength(env, buffer);
	//return 42;
}

/*********************************************************************************/
/* Sending input to the Windows event stream                                     */
/*********************************************************************************/

JNIEXPORT void JNICALL Java_sivantoledo_nxt_SerialInterface_ninjectkey
  (JNIEnv *env, jclass C, jstring name)
{
  INPUT in;
  const char *sname = (*env)->GetStringUTFChars(env, name, 0);

  if (strcmp(sname,"RIGHT")==0) {
    in.type = INPUT_KEYBOARD; // INPUT_KEYBOARD
  	in.ki.wVk = VK_RIGHT;
	  in.ki.wScan = 0;
	  in.ki.dwFlags = 0;
	  in.ki.time = 0; // system supplies time stamp
	  in.ki.dwExtraInfo = 0;
	  SendInput(1,&in,sizeof(in));
  }

  if (strcmp(sname,"LEFT")==0) {
    in.type = INPUT_KEYBOARD; // INPUT_KEYBOARD
  	in.ki.wVk = VK_LEFT;
	  in.ki.wScan = 0;
	  in.ki.dwFlags = 0;
	  in.ki.time = 0; // system supplies time stamp
	  in.ki.dwExtraInfo = 0;
	  SendInput(1,&in,sizeof(in));
  }

  (*env)->ReleaseStringUTFChars(env, name, sname);

	return;
}

/************************************************************/
/* enumerating serial ports                                 */
/************************************************************/

JNIEXPORT jstring JNICALL Java_sivantoledo_nxt_SerialInterface_nenumerateserialports
  (JNIEnv *env, jclass C)
{
  DWORD i;
  HKEY  layouts,existing_layout;
  char  existing_key[256];
  int found = 0;
  FILETIME write_time;
  char buffer[256];
  DWORD buffer_length_size;

  char out[1024];

  LONG rc;

  out[0] = 0;

  if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
            		   "Hardware\\DeviceMap\\SerialComm",	
		               0, // DWORD ulOptions; reserved and must be 0
		               KEY_READ, //REGSAM samDesired,
		               &layouts //PHKEY phkResult
		               ) != ERROR_SUCCESS) 
    return (*env)->NewStringUTF(env, out);
  
  rc = ERROR_SUCCESS;
  for (i = 0; rc == ERROR_SUCCESS; i++) {
    char name[256];
    DWORD name_len = 256;
    DWORD type;
    char data[256];
    DWORD data_len = 256;
    buffer_length_size = 256;
    rc = RegEnumValue(layouts,
                      i,
                      name,
                      &name_len,
                      0,
                      &type,
                      data,
                      &data_len);

    //return (*env)->NewStringUTF(env, buffer);
    if (rc == ERROR_SUCCESS) {
      strcat_s(out,1024,name);
      strcat_s(out,1024,"=");
      strcat_s(out,1024,data);
      strcat_s(out,1024,";");
      //return 768;
      //return (*env)->NewStringUTF(env, data);
    } 
  }

  return (*env)->NewStringUTF(env, out);
  //return 0;
}