/*-------------------------------------------------------------------------
|   rxtx is a native interface to serial ports in java.
|   Copyright 2002-2004 Michal Hobot MichalHobot@netscape.net
|   Copyright 1997-2004 by Trent Jarvi taj@parcelfarce.linux.theplanet.co.uk
|
|   This library is free software; you can redistribute it and/or
|   modify it under the terms of the GNU Library General Public
|   License as published by the Free Software Foundation; either
|   version 2 of the License, or (at your option) any later version.
|
|   This library is distributed in the hope that it will be useful,
|   but WITHOUT ANY WARRANTY; without even the implied warranty of
|   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
|   Library General Public License for more details.
|
|   You should have received a copy of the GNU Library General Public
|   License along with this library; if not, write to the Free
|   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
--------------------------------------------------------------------------*/
#include "StdAfx.h"
#include <stdio.h>
#include <stdlib.h>
#include "rxtxHelpers.h"

/*----------------------------------------------------------
get_java_int_var

   accept:      env (keyhole to java)
                jobj (java RXTXPort object)
   return:      the jint field from the java object, casted to long
   exceptions:  none
   comments:
----------------------------------------------------------*/
long get_java_int_var( JNIEnv *env, jobject jobj, char *id)
{
	long result = 0;
	jclass jclazz = env->GetObjectClass(jobj);
	jfieldID jfd = env->GetFieldID(jclazz, id, "I");

	if( !jfd ) {
    IF_DEBUG
    (
		  env->ExceptionDescribe();
    )
		env->ExceptionClear();
		env->DeleteLocalRef(jclazz);
		return result;
	}
	result = (long)( env->GetIntField(jobj, jfd) );
	env->DeleteLocalRef(jclazz);
	return result;
}


/*----------------------------------------------------------
get_java_boolean_var

   accept:      env (keyhole to java)
                jobj (java RXTXPort object)
   return:      the jboolean field from the java object, converted to bool
   exceptions:  none
   comments:
----------------------------------------------------------*/
bool get_java_boolean_var( JNIEnv *env, jobject jobj, char *id)
{
	bool result = FALSE;
	jclass jclazz = env->GetObjectClass(jobj);
	jfieldID jfd = env->GetFieldID(jclazz, id, "Z");

	if( !jfd ) {
    IF_DEBUG
    (
		  env->ExceptionDescribe();
    )
		env->ExceptionClear();
		env->DeleteLocalRef(jclazz);
		return result;
	}
	result = (env->GetBooleanField(jobj, jfd)) != JNI_FALSE;
	env->DeleteLocalRef(jclazz);
	return result;
}


/*----------------------------------------------------------
get_java_boolean_var2

   accept:      env (keyhole to java)
                jobj (java RXTXPort object)
                jclazz (class of jobj)
   return:      the jboolean field from the java object, converted to bool
   exceptions:  none
   comments:    can be faster for fetching many variables one by one
----------------------------------------------------------*/
bool get_java_boolean_var2(JNIEnv *env, jobject jobj, jclass jclazz, char *id)
{
	bool result = FALSE;
	jfieldID jfd = env->GetFieldID(jclazz, id, "Z");

	if( !jfd ) {
    IF_DEBUG
    (
		  env->ExceptionDescribe();
    )
		env->ExceptionClear();
		env->DeleteLocalRef(jclazz);
		return result;
	}
	result = (env->GetBooleanField(jobj, jfd)) != JNI_FALSE;
	return result;
}


/*----------------------------------------------------------
throw_java_exception

   accept:      env (keyhole to java)
                *exc (exception class name)
                *foo (function name)
                *msg (error message)
   perform:     Throw a new java exception
   return:      none
   exceptions:  haha!
   comments:
----------------------------------------------------------*/
void throw_java_exception(JNIEnv *env, const char *exc, const char *foo, const char *msg)
{
	char buf[250];
	jclass clazz = env->FindClass(exc);
	if( !clazz ) {
    IF_DEBUG
    (
		  env->ExceptionDescribe();
    )
		env->ExceptionClear();
		return;
	}
	_snprintf(buf, 60, "%s: %s in %s", exc, msg, foo);
	env->ThrowNew(clazz, buf);
	env->DeleteLocalRef(clazz);
}

// Unicode version:
void throw_java_exceptionW(JNIEnv *env, const char *exc, const wchar_t *foo, const wchar_t *msg)
{
	wchar_t buf[500];
  char *lpcBuf;
  int msgLen;

	jclass clazz = env->FindClass(exc);
	if( !clazz ) {
		env->ExceptionDescribe();
		env->ExceptionClear();
		return;
	}
	msgLen = swprintf(buf, L"%S: %s in %s", exc, msg, foo);
  
  IF_DEBUG
  (
    printj(env, L"!!!!! Throwing %s\n", buf);
    //MessageBox(NULL, buf, L"throw_java_exceptionW"), MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
  )

  lpcBuf = (char *)malloc((msgLen+1)*sizeof(wchar_t)); // Will be ok - mbs not longer than wcs
  lpcBuf[msgLen] = '\0';
  wcstombs(lpcBuf, buf, msgLen);
  env->ThrowNew(clazz, lpcBuf);
  free(lpcBuf);
	env->DeleteLocalRef(clazz);
}


/*----------------------------------------------------------
get_fd

   accept:      env (keyhole to java)
                jobj (java RXTXPort object)
   return:      "fd" field from the java object, as HANDLE
   exceptions:  none
   comments:
----------------------------------------------------------*/
HANDLE get_fd(JNIEnv *env, jobject jobj)
{
  HANDLE fd = (HANDLE)get_java_int_var(env, jobj, "fd");
  return fd!=0?fd:INVALID_HANDLE_VALUE;
}


/*----------------------------------------------------------
get_eis

   accept:      env (keyhole to java)
                jobj (java RXTXPort object)
   return:      "eis" field from the java object, as (EventInfoStruct *)
   exceptions:  none
   comments:
----------------------------------------------------------*/
EventInfoStruct *get_eis(JNIEnv *env, jobject jobj)
{
  return (EventInfoStruct *)get_java_int_var(env, jobj, "eis");
}


/*----------------------------------------------------------
printj

   accept:      like vwprintf()
   return:      number of jchars written or -1
   exceptions:  none
   comments:    prints data using System.out.print()
----------------------------------------------------------*/
int printj(JNIEnv *env, wchar_t *fmt, ...)
{
  wchar_t buf[1024];
  int retval;
  jstring jsBuf;
  jclass clsSystem, clsOut;
  jfieldID jfid;
  jobject objOut;
  jmethodID midPrint;

  va_list ap;
  va_start(ap, fmt);
  retval = _vsnwprintf(buf, 1024, fmt, ap);
  va_end(ap);
  buf[1023] = '\0';
  
  if((clsSystem = env->FindClass("java/lang/System")) == NULL)
  {
		IF_DEBUG
    (
      env->ExceptionDescribe();
    )
		env->ExceptionClear();
    return -1;
  }
  
  if((jfid = env->GetStaticFieldID(clsSystem, "out", "Ljava/io/PrintStream;")) == NULL)
  {
		IF_DEBUG
    (
      env->ExceptionDescribe();
    )
		env->ExceptionClear();
    env->DeleteLocalRef(clsSystem);
    return -1;
  }
  
  objOut = env->GetStaticObjectField(clsSystem, jfid);
	clsOut = env->GetObjectClass(objOut);

  if((midPrint = env->GetMethodID(clsOut, "print", "(Ljava/lang/String;)V")) == NULL)
  {
		IF_DEBUG
    (
      env->ExceptionDescribe();
    )
		env->ExceptionClear();
    env->DeleteLocalRef(clsOut);
    env->DeleteLocalRef(clsSystem);
    return -1;
  }
  
	jsBuf = env->NewString(buf, wcslen(buf));

  env->CallVoidMethod(objOut, midPrint, jsBuf);

 	env->DeleteLocalRef(jsBuf);
  env->DeleteLocalRef(clsOut);
  env->DeleteLocalRef(clsSystem);
  
  return retval;
}



/*----------------------------------------------------------
CommEventThread

   accept:      communication structure ptr
   return:      0 - normal finish, other - error
   exceptions:  none
   comments:    runs as separate thread
----------------------------------------------------------*/
DWORD __stdcall CommEventThread(LPVOID lpEventInfo)
{ 
  DWORD dwErr;
  EventInfoStruct *EventInfo = (EventInfoStruct *)lpEventInfo;
  HANDLE hPort = EventInfo->fd;
  
  // Specify a set of events to be monitored for the port.
  if(!SetCommMask(hPort, EventInfo->ef))
  {
    dwErr = GetLastError();
    IF_DEBUG
    (
      LPCWSTR lpMsgBuf;
      CreateErrorMsg(dwErr, lpMsgBuf);
      MessageBoxW(NULL, lpMsgBuf, L"!!! CommEventThread - SetCommMask() error", MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
      ReleaseErrorMsg(lpMsgBuf);
    )
    return dwErr;
  }

  // Thread is ready to work
  EventInfo->eventThreadReady = true;

  do
  {
    // Wait for an event to occur for the port.
    if(!WaitCommEvent(hPort, &(EventInfo->event), NULL))
    {
      dwErr = GetLastError();
      if(dwErr == ERROR_INVALID_PARAMETER) 
      { // Mask is empty - let's wait for a moment and continue
        MessageBeep(MB_ICONQUESTION);
        Sleep(200);
        continue;
      }

      if(dwErr == ERROR_INVALID_HANDLE)
      { // Port was closed
        //MessageBox(NULL, L"--- CommEventThread - Port closed", L"ERROR_INVALID_HANDLE", MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
        free(EventInfo);
        return 0;
      }
      
      IF_DEBUG
      (
        LPCWSTR lpMsgBuf;
        WCHAR MsgBuf2[1000];
        CreateErrorMsg(dwErr, lpMsgBuf);
        wsprintfW(MsgBuf2, L"%ld %s, CommMask==%ld", dwErr, lpMsgBuf, EventInfo->ef);
        MessageBoxW(NULL, MsgBuf2, L"!!! CommEventThread - WaitCommEvent() error", MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
        ReleaseErrorMsg(lpMsgBuf);
      )
      return dwErr;
    }

    IF_DEBUG
    (
      MessageBeep(MB_ICONEXCLAMATION);
      //MessageBoxW(NULL, L"--- RXTXPort.CommEventThread() - CommEvent", L"CommEventThread", MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
    )
    
    // Re-specify the set of events to be monitored for the port.
    if(!SetCommMask(hPort, EventInfo->ef))
    {
      dwErr = GetLastError();
      IF_DEBUG
      (
        LPCWSTR lpMsgBuf;
        CreateErrorMsg(dwErr, lpMsgBuf);
        MessageBoxW(NULL, lpMsgBuf, L"!!! CommEventThread - SetCommMask() error", MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
        ReleaseErrorMsg(lpMsgBuf);
      )
      return dwErr;
    }

    if(!SetEvent(EventInfo->eventHandle))
    {
      dwErr = GetLastError();
      if(dwErr == ERROR_INVALID_HANDLE) // Event was closed
      {
        //MessageBoxW(NULL, L"--- CommEventThread - Event closed", L"ERROR_INVALID_HANDLE", MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
        free(EventInfo);
        return 0;
      }

      IF_DEBUG
      (
        LPCWSTR lpMsgBuf;
        CreateErrorMsg(dwErr, lpMsgBuf);
        MessageBoxW(NULL, lpMsgBuf, L"!!! CommEventThread - SetEvent() error", MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
        ReleaseErrorMsg(lpMsgBuf);
      )
      return dwErr;
    }

    // Wait for receiving and clearing event
    while(EventInfo->event != 0)
      Sleep(0);

  } while(TRUE);
}


/*----------------------------------------------------------
setEventFlags

   accept:      EventFlag table
   return:      none
   exceptions:  none
   comments:    sets EventFlag table according to Java 
                MonitorThread fields
----------------------------------------------------------*/
/*void setEventFlags(JNIEnv *env, jobject jobjPort, bool ef[])
{
	jfieldID jfid;
  jclass cls;
  jobject jobj;
  jclass clsPort = env->GetObjectClass(jobjPort);

  if((jfid = env->GetFieldID(clsPort, "monThread", "Lgnu/io/RXTXPort$MonitorThread;")) == NULL)
  {
		IF_DEBUG
    (
      env->ExceptionDescribe();
    )
		env->ExceptionClear();
    env->DeleteLocalRef(clsPort);
    return;
  }
  jobj = env->GetObjectField(jobjPort, jfid);
	cls = env->GetObjectClass(jobj);

  ef[SPE_DATA_AVAILABLE] = get_java_boolean_var2(env, jobj, cls, "Data");
  ef[SPE_OUTPUT_BUFFER_EMPTY] = get_java_boolean_var2(env, jobj, cls, "Output");
  ef[SPE_CTS] = get_java_boolean_var2(env, jobj, cls, "CTS");
  ef[SPE_DSR] = get_java_boolean_var2(env, jobj, cls, "DSR");
  ef[SPE_RI] = get_java_boolean_var2(env, jobj, cls, "RI");
  ef[SPE_CD] = get_java_boolean_var2(env, jobj, cls, "CD");
  ef[SPE_OE] = get_java_boolean_var2(env, jobj, cls, "OE");
  ef[SPE_PE] = get_java_boolean_var2(env, jobj, cls, "PE");
  ef[SPE_FE] = get_java_boolean_var2(env, jobj, cls, "FE");
  ef[SPE_BI] = get_java_boolean_var2(env, jobj, cls, "BI");

  env->DeleteLocalRef(clsPort);
  env->DeleteLocalRef(cls);
  env->DeleteLocalRef(jobj);
}
*/

/*----------------------------------------------------------
InitialiseEventInfoStruct

   accept:      Port handle
   return:      0 - OK, other - error number
   exceptions:  none
   comments:    Structure for communication with thread
----------------------------------------------------------*/
int InitialiseEventInfoStruct(HANDLE hPort, EventInfoStruct **EventInfoPtr)
{
  DWORD dwErr;
  WCHAR wsEventName[MAX_PATH];
  
  (*EventInfoPtr) = (EventInfoStruct *)malloc(sizeof(EventInfoStruct));
  if(*EventInfoPtr == NULL)
    return ERROR_NOT_ENOUGH_MEMORY;

  memset(*EventInfoPtr, 0, sizeof(EventInfoStruct));
  (*EventInfoPtr)->fd = hPort;
  wsprintfW(wsEventName, L"rxtxPort.SerEvt%lx", hPort);
  (*EventInfoPtr)->eventHandle = CreateEventW(NULL, FALSE, FALSE, wsEventName);
  dwErr = GetLastError();
  if((*EventInfoPtr)->eventHandle == NULL || dwErr == ERROR_ALREADY_EXISTS)
  {
    IF_DEBUG
    (
      LPCWSTR lpMsgBuf;
      CreateErrorMsg(dwErr, lpMsgBuf);
      MessageBoxW(NULL, lpMsgBuf, L"!!! InitialiseEventInfoStruct - CreateEvent() error", MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
      ReleaseErrorMsg(lpMsgBuf);
    )
    free(*EventInfoPtr);
    return dwErr;
  }

  // Set initial event flags: It should be not empty, but unused here and relatively rare event
  (*EventInfoPtr)->ef = EV_POWER; //In WinNT you can use EV_EVENT2 instead
  return 0;
}


/*----------------------------------------------------------
SendEvents

   accept:      
   return:      
   exceptions:  none
   comments:    Sends events needed
----------------------------------------------------------*/
int SendEvents(JNIEnv *env, jobject jobj, DWORD dwEvent, EventInfoStruct *EventInfo, jmethodID jmSendEvent)
{
  DWORD dwErrors;
  COMSTAT Stat;

  IF_DEBUG
  (
    printj(env, L"--- SendEvents(): event %#lx\n", dwEvent);
  )

  if(!ClearCommError(EventInfo->fd, &dwErrors, &Stat))
  {
    IF_DEBUG
    (
      LPCWSTR lpMsgBuf;
      CreateErrorMsg(GetLastError(), lpMsgBuf);
      printj(env, L"!!! SendEvents - ClearCommError() error: %s\n", lpMsgBuf);
      ReleaseErrorMsg(lpMsgBuf);
    )
    return -1;
  }

  // Ignore not subscribed events
  dwEvent &= EventInfo->ef;

  if(dwEvent & EV_RXCHAR)
  {
    IF_DEBUG
    (
      printj(env, L"--- SendEvents(): EV_RXCHAR\n");
    )
    if(env->CallBooleanMethod(jobj, jmSendEvent, SPE_DATA_AVAILABLE, JNI_TRUE) == JNI_TRUE)
      return SPE_DATA_AVAILABLE;
  }

  if(dwEvent & EV_TXEMPTY)
  {
    IF_DEBUG
    (
      printj(env, L"--- SendEvents(): EV_TXEMPTY\n");
    )
    if(env->CallBooleanMethod(jobj, jmSendEvent, SPE_OUTPUT_BUFFER_EMPTY, JNI_TRUE) == JNI_TRUE)
      return SPE_OUTPUT_BUFFER_EMPTY;
  }

  if(dwEvent & EV_CTS)
  {
    IF_DEBUG
    (
      printj(env, L"--- SendEvents(): EV_CTS\n");
    )
    if(env->CallBooleanMethod(jobj, jmSendEvent, SPE_CTS, Stat.fCtsHold?JNI_TRUE:JNI_TRUE) == JNI_TRUE)
      return SPE_CTS;
  }

  if(dwEvent & EV_DSR)
  {
    IF_DEBUG
    (
      printj(env, L"--- SendEvents(): EV_DSR\n");
    )
    if(env->CallBooleanMethod(jobj, jmSendEvent, SPE_DSR, Stat.fDsrHold?JNI_TRUE:JNI_TRUE) == JNI_TRUE)
      return SPE_DSR;
  }

  if(dwEvent & EV_RING)
  {
    IF_DEBUG
    (
      printj(env, L"--- SendEvents(): EV_RING\n");
    )
    if(env->CallBooleanMethod(jobj, jmSendEvent, SPE_RI, JNI_TRUE) == JNI_TRUE)
      return SPE_RI;
  }
  
  if(dwEvent & EV_RLSD)
  {
    IF_DEBUG
    (
      printj(env, L"--- SendEvents(): EV_RLSD\n");
    )
    if(env->CallBooleanMethod(jobj, jmSendEvent, SPE_CD, Stat.fRlsdHold?JNI_TRUE:JNI_TRUE) == JNI_TRUE)
      return SPE_CD;
  }

  if(dwEvent & EV_ERR)
  {
    if(dwErrors & CE_OVERRUN)
    {
      IF_DEBUG
      (
        printj(env, L"--- SendEvents(): CE_OVERRUN\n");
      )
      if(env->CallBooleanMethod(jobj, jmSendEvent, SPE_OE, JNI_TRUE) == JNI_TRUE)
        return SPE_OE;
    }

    if(dwErrors & CE_RXPARITY)
    {
      IF_DEBUG
      (
        printj(env, L"--- SendEvents(): CE_RXPARITY\n");
      )
      if(env->CallBooleanMethod(jobj, jmSendEvent, SPE_PE, JNI_TRUE) == JNI_TRUE)
        return SPE_PE;
    }

    if(dwErrors & CE_FRAME)
    {
      IF_DEBUG
      (
        printj(env, L"--- SendEvents(): CE_FRAME\n");
      )
      if(env->CallBooleanMethod(jobj, jmSendEvent, SPE_FE, JNI_TRUE) == JNI_TRUE)
        return SPE_FE;
    }
  }

  if(dwEvent & EV_BREAK)
  {
    IF_DEBUG
    (
      printj(env, L"--- SendEvents(): EV_BREAK\n");
    )
    if(env->CallBooleanMethod(jobj, jmSendEvent, SPE_BI, JNI_TRUE) == JNI_TRUE)
      return SPE_BI;
  }
  
  return 0;
}

