/*---------------------------------------------------------------------------
  Originally Microsoft Sample Program MTTY
  http://msdn.microsoft.com/en-us/library/ms810467.aspx
  ReaderAndStatusProc()
  WriteGeneric()
  ---------------------------------------------------------------------------*/

#include <windows.h>
#include <process.h> // for _beginthreadex()
#include <stdio.h>
#include "qpcounter_c.h"


#define PURGE_FLAGS                                                     \
  (PURGE_TXABORT | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_RXCLEAR)
#define EVENTFLAGS_DEFAULT                                      \
  (EV_BREAK | EV_CTS | EV_DSR | EV_ERR | EV_RING | EV_RLSD)
#define AMOUNT_TO_READ          512
#define NUM_READSTAT_HANDLES    4


//DWORD WINAPI ReadThread(LPVOID lpV);
__stdcall unsigned ReadThread(void* lpV);

#define MAX_READ_BUFFER         2048
#define STATUS_CHECK_TIMEOUT    5000

#define RING_MAX (10*1024)
struct _winserial
{
  int fNoReading;
  int fNoEvents;
  int fNoStatus;
  int fConnected;
  int fDisplayTimeouts;
  HANDLE hReaderThread;
  HANDLE ghThreadExitEvent;
  HANDLE ghStatusMessageEvent;
  HANDLE hCommPort;
  DWORD dwStoredFlags;

  // get/put buffers
  // buffers and variables must be changed inside critical section.
  unsigned char r_buf[RING_MAX];
  int r_head, r_tail, r_count;
  CRITICAL_SECTION r_cs;

  // syncronization between read thread and ComGetc()
  HANDLE h_getc_wait;    // event
  int    f_getc_waiting; // flag
  int    f_exit;
};

COMMTIMEOUTS timeoutsorig;

void* qp; // debug, time measurement.


void* ComOpen(char* portname)
{
  unsigned int dwReadStatId;
  struct _winserial *p = (struct _winserial *)malloc(sizeof(struct _winserial));
  qp = qpc_open();
  qpc_set_t0(qp);

  if ( p == NULL ) {
    fprintf(stderr, "Error: memory allocation failed.\n");
    return p;
  }
  p->fNoReading = FALSE;
  p->fNoEvents = FALSE;
  p->fNoStatus = FALSE;
  p->fConnected = FALSE;
  p->fDisplayTimeouts = TRUE;
  p->dwStoredFlags = EVENTFLAGS_DEFAULT;

  p->r_head = p->r_tail = 0;
  p->r_count = 0;
  InitializeCriticalSection(&p->r_cs);
  p->f_getc_waiting = FALSE;
  p->f_exit = FALSE;

  p->ghStatusMessageEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  if (p->ghStatusMessageEvent == NULL) {
    fprintf(stderr, "Error: CreateEvent (Status message event)\n");
    free(p);
    return NULL;
  }

  p->ghThreadExitEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  if (p->ghThreadExitEvent == NULL) {
    fprintf(stderr, "Error: CreateEvent (Thread exit event)\n");
    free(p);
    return NULL;
  }

  // auto reset event (arg2 manual reset=FALSE)
  p->h_getc_wait = CreateEvent(NULL, FALSE, FALSE, NULL);
  if (p->h_getc_wait == NULL) {
    free(p);
    return NULL;
  }

  p->hCommPort = CreateFile(portname, GENERIC_READ | GENERIC_WRITE,
                             0, 0, OPEN_EXISTING,
                             FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0);

  if (p->hCommPort == INVALID_HANDLE_VALUE) {
    fprintf(stderr, "Error: CreateFile");
    free(p);
    return NULL;
  }

  UpdateConnection(p);

  SetCommMask(p->hCommPort, EV_RXCHAR);
  SetupComm(p->hCommPort, 2048, 1024);// read and write buffer size
  PurgeComm(p->hCommPort, PURGE_FLAGS);
  PurgeComm(p->hCommPort, PURGE_FLAGS);

  // start thread
  p->hReaderThread =
    (HANDLE)_beginthreadex(NULL, 0, (unsigned (_stdcall *)(void *))ReadThread,
                           (void*)p, 0, &dwReadStatId);

  if (p->hReaderThread == NULL) {
    fprintf(stderr, "Error: CreateThread(Reader/Status)\n");
  }

  p->fConnected = TRUE;

  return p;
}

DWORD WaitForThreads(void* p0, DWORD dwTimeout)
{
  struct _winserial *p=(struct _winserial *)p0;
  HANDLE hThreads[2];
  DWORD  dwRes;

  hThreads[0] = p->hReaderThread;

  // set thread exit event here
  SetEvent(p->ghThreadExitEvent);
  p->f_exit = TRUE;

  dwRes = WaitForMultipleObjects(1, hThreads, TRUE, dwTimeout);
  switch(dwRes) {
  case WAIT_OBJECT_0:
  case WAIT_OBJECT_0 + 1:
    dwRes = WAIT_OBJECT_0;
    break;

  case WAIT_TIMEOUT:
    if (WaitForSingleObject(p->hReaderThread, 0) == WAIT_TIMEOUT) {
      fprintf(stderr, "Error: Reader/Status Thread didn't exit.\n");
    }
    break;

  default:
    fprintf(stderr, "WaitForMultipleObjects\n");
    break;
  }

  // reset thread exit event here
  ResetEvent(p->ghThreadExitEvent);

  return dwRes;
}


int ComClose(void* p0)
{
  struct _winserial *p=(struct _winserial *)p0;

  if (!p->fConnected)
    return FALSE;

  p->fConnected = FALSE;
  SetEvent(p->h_getc_wait);

  // wait for the threads for a small period
  if (WaitForThreads(p, 20000) != WAIT_OBJECT_0) {
    /*
      if threads haven't exited, then they will
      interfere with a new connection.  I must abort
      the entire program.
    */

    fprintf(stderr, "Error closing port.");
    free(p);
    ExitProcess(0);
  }

  // Purge reads/writes, input buffer and output buffer
  if (!PurgeComm(p->hCommPort, PURGE_FLAGS)) {
    fprintf(stderr, "Error: PurgeComm\n");
  }

  // lower DTR
  if (!EscapeCommFunction(p->hCommPort, CLRDTR)) {
    fprintf(stderr, "Error: EscapeCommFunction(CLRDTR)\n");
  }

  SetCommMask(p->hCommPort, 0);

  CloseHandle(p->hCommPort);
  CloseHandle(p->hReaderThread);

  DeleteCriticalSection(&p->r_cs);

  free(p);

  return TRUE;
}


void UpdateStatus(void* p0, char * szText)
{
  struct _winserial *p=(struct _winserial *)p0;
  SetEvent(p->ghStatusMessageEvent);
}


void CheckModemStatus(void* p0, BOOL bUpdateNow )
{
  struct _winserial *p=(struct _winserial *)p0;

  // dwOldStatus needs to be static so that it is maintained
  // between function calls by the same thread.
  // It also needs to be __declspec(thread) so that it is
  // initialized when a new thread is created.

  __declspec(thread) static DWORD dwOldStatus = 0;

  DWORD dwNewModemStatus;

  if (!GetCommModemStatus(p->hCommPort, &dwNewModemStatus))
    fprintf(stderr, "GetCommModemStatus\n");

  // Report status if bUpdateNow is true or status has changed
  if (bUpdateNow || (dwNewModemStatus != dwOldStatus)) {
    fprintf(stderr, "Status: modem: %x\n", dwNewModemStatus);
    dwOldStatus = dwNewModemStatus;
  }

  return;
}


void CheckComStat(void* p0, BOOL bUpdateNow)
{
  struct _winserial *p=(struct _winserial *)p0;
  COMSTAT ComStatNew;
  DWORD dwErrors;

  __declspec(thread) static COMSTAT ComStatOld = {0};
  __declspec(thread) static DWORD dwErrorsOld = 0;

  BOOL bReport = bUpdateNow;

  if (!ClearCommError(p->hCommPort, &dwErrors, &ComStatNew))
    fprintf(stderr, "ClearCommError\n");

  if (dwErrors != dwErrorsOld) {
    bReport = TRUE;
    dwErrorsOld = dwErrors;
  }

  if (memcmp(&ComStatOld, &ComStatNew, sizeof(COMSTAT))) {
    bReport = TRUE;
    ComStatOld = ComStatNew;
  }

  ComStatOld = ComStatNew;

  return;
}

void ComDebugOut(char* lpBuf, int dwRead)
{
  int i;
  fprintf(stderr, "debug out--------\n");
  for (i=0; i<dwRead; i++) putchar( lpBuf[i] );
  fprintf(stderr, "debug out--------end\n");
}

void ComReadCopy(void* p0, char* lpBuf, int dwRead)
{
  struct _winserial *p=(struct _winserial *)p0;
  int bufcnt;
  int lpBuf_index = 0;

    while ( 0<dwRead ) {
      EnterCriticalSection(&p->r_cs);

    if ( p->r_head < p->r_tail ) bufcnt = p->r_tail - p->r_head +1;
    else                         bufcnt = RING_MAX - p->r_head;
    if ( dwRead <= bufcnt ) bufcnt = dwRead;

    // copy
    memcpy( &p->r_buf[p->r_head], &lpBuf[lpBuf_index], bufcnt );

    // update
    p->r_head += bufcnt;
    if (RING_MAX <= p->r_head) {
      if (RING_MAX != p->r_head) {
        fprintf(stderr, "Error: algorithm error\n");
      }
      p->r_head = 0;
    }
    p->r_count += bufcnt;
    lpBuf_index += bufcnt;
    dwRead -= bufcnt;  // amount of the rest

    LeaveCriticalSection(&p->r_cs);

    if ( p->f_getc_waiting ) {
      p->f_getc_waiting = FALSE;
      SetEvent(p->h_getc_wait);
    }

    while ( RING_MAX <= p->r_count && dwRead) {
      Sleep(1);
    }
  }
}


__stdcall unsigned ReadThread(void* p0)
{
  struct _winserial *p=(struct _winserial *)p0;
  OVERLAPPED osReader = {0};  // overlapped structure for read operations
  HANDLE     hArray[NUM_READSTAT_HANDLES];
  DWORD      dwStoredFlags = 0xFFFFFFFF;      // local copy of event flags
  DWORD      dwCommEvent;     // result from WaitCommEvent
  DWORD      dwOvRes;         // result from GetOverlappedResult
  DWORD      dwRead;          // bytes actually read
  DWORD      dwRes;           // result from WaitForSingleObject
  BOOL       fWaitingOnRead = FALSE;
  BOOL       fWaitingOnStat = FALSE;
  BOOL       fThreadDone = FALSE;
  char       lpBuf[AMOUNT_TO_READ];

  //!*hh create two overlapped structures, one for read events
  // and another for status events
  osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  if (osReader.hEvent == NULL)
    fprintf(stderr, "CreateEvent (Reader Event)\n");

  // We want to detect the following events:
  //   Read events (from ReadFile)
  //   Status events (from WaitCommEvent)
  //   Status message events (from our UpdateStatus)
  //   Thread exit evetns (from our shutdown functions)
  hArray[0] = osReader.hEvent;
  hArray[1] = p->ghThreadExitEvent;

  // initial check, forces updates
  while ( !fThreadDone ) {
    if ( p->f_exit ) {
      fThreadDone = TRUE;
    }

    // If no reading is allowed, then set flag to
    // make it look like a read is already outstanding.
    if (p->fNoReading) {
      fWaitingOnRead = TRUE;
    }

    // if no read is outstanding, then issue another one
    if (!fWaitingOnRead) {
      COMSTAT ComStatNew;
      DWORD dwErrors;

      //qpc_set_t1(qp);
      if (!ReadFile(p->hCommPort, lpBuf, AMOUNT_TO_READ, &dwRead, &osReader)) {
        int err;
        if ((err=GetLastError()) != ERROR_IO_PENDING) {   // read not delayed?
          dwRead = 0;
        } else { // IO PENDING
          dwRes =
            WaitForMultipleObjects(2, hArray, FALSE, STATUS_CHECK_TIMEOUT);
          if ( dwRes == WAIT_OBJECT_0 ) {
            // wait reading
            int ret;
            ret = GetOverlappedResult(p->hCommPort, &osReader, &dwRead, FALSE);
          } else if ( dwRes == (WAIT_OBJECT_0+1) ) {
            fThreadDone = TRUE;
            dwRead = 0;
          } else {
            dwRead = 0;
          }
        }
      } //ReadFile()

      if (dwRead) {
        ComReadCopy(p, lpBuf, dwRead);
      }

      ClearCommError(p->hCommPort, &dwErrors, &ComStatNew);
    } //fWaitingOnRead

    continue;

    // wait for pending operations to complete
    if ( fWaitingOnRead ) {
      fprintf(stderr, "Debug: Waitformultipleobject start\n");

      dwRes = WaitForMultipleObjects(2, hArray, FALSE, STATUS_CHECK_TIMEOUT);

      fprintf(stderr, "Debug: Waitformultipleobject res=%d OBJ0=%d\n", dwRes, WAIT_OBJECT_0);

      switch(dwRes) {
        // read completed
      case WAIT_OBJECT_0:
        fprintf(stderr, "GetOverlappedResult on reader\n");
        if (!GetOverlappedResult(p->hCommPort, &osReader, &dwRead, FALSE)) {
          if (GetLastError() == ERROR_OPERATION_ABORTED) {
            fprintf(stderr, "GetOverlappedResult aborted\n");
            UpdateStatus(p, "Read aborted\r\n");
          } else
            fprintf(stderr, "GetOverlappedResult (in Reader)\n");
        } else {      // read completed successfully
          fprintf(stderr, "GetOverlappedResult ok dwRead=%d\n", dwRead);
          if ((dwRead != MAX_READ_BUFFER) && p->fDisplayTimeouts)
            UpdateStatus(p, "Read timed out overlapped.\r\n");

          if (dwRead) {
            fprintf(stderr, "Debug: ComReadCopy2 dwRead=%d\n", dwRead);
            ComReadCopy(p, lpBuf, dwRead);
          }
        }

        fWaitingOnRead = FALSE;
        break;

      case WAIT_OBJECT_0 + 1:
        fThreadDone = TRUE;
        break;

      default:
        fprintf(stderr, "WaitForMultipleObjects(Reader & Status handles) code %d %d\n", dwRes, GetLastError());
        break;
      }
    }
  }

  // close event handles
  CloseHandle(osReader.hEvent);

  return 1;
}

void ComWrite(void* p0, char * lpBuf, DWORD dwToWrite)
{
  struct _winserial *p=(struct _winserial *)p0;
  OVERLAPPED osWrite = {0};
  HANDLE hArray[2];
  DWORD dwWritten;
  DWORD dwRes;

  // create this writes overlapped structure hEvent
  osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  if (osWrite.hEvent == NULL)
    fprintf(stderr, "CreateEvent (overlapped write hEvent)\n");

  hArray[0] = osWrite.hEvent;
  hArray[1] = p->ghThreadExitEvent;

  // issue write
  if (!WriteFile(p->hCommPort, lpBuf, dwToWrite, &dwWritten, &osWrite)) {
    if (GetLastError() == ERROR_IO_PENDING) {
      // write is delayed
      dwRes = WaitForMultipleObjects(2, hArray, FALSE, INFINITE);
      switch(dwRes) {

      case WAIT_OBJECT_0:
        // write event set
        SetLastError(ERROR_SUCCESS);
        if (!GetOverlappedResult(p->hCommPort, &osWrite, &dwWritten, FALSE)) {
          if (GetLastError() == ERROR_OPERATION_ABORTED)
            UpdateStatus(p, "Write aborted\r\n");
          else
            fprintf(stderr, "GetOverlappedResult(in Writer)\n");
        }

        if (dwWritten != dwToWrite) {
          if ((GetLastError() == ERROR_SUCCESS) && p->fDisplayTimeouts)
            UpdateStatus(p, "Write timed out. (overlapped)\r\n");
          else
            fprintf(stderr, "Error writing data to port (overlapped)\n");
        }
        break;

      case WAIT_OBJECT_0 + 1:
        // thread exit event set
        break;

      case WAIT_TIMEOUT:
        UpdateStatus(p, "Wait Timeout in WriterGeneric.\r\n");
        break;

      case WAIT_FAILED:
      default:    fprintf(stderr, "WaitForMultipleObjects (WriterGeneric)\n");
        break;
      }
    } else {
      // writefile failed, but it isn't delayed
      fprintf(stderr, "WriteFile (in Writer)\n");
    }
  } else {
    // writefile returned immediately
    if (dwWritten != dwToWrite)
      UpdateStatus(p, "Write timed out. (immediate)\r\n");
  }

  CloseHandle(osWrite.hEvent);

  return;
}

#define BAUDRATE( x )       (x.dwBaudRate)
#define PARITY( x )         (x.bParity)
#define BYTESIZE( x )       (x.bByteSize)
#define STOPBITS( x )       (x.bStopBits)

BOOL UpdateConnection(void* p0)
{
  struct _winserial *p = (struct _winserial *)p0;
  DCB dcb = { 0 };
  dcb.DCBlength = sizeof(dcb);

  // get current DCB settings
  GetCommState(p->hCommPort, &dcb);

  // update DCB rate, byte size, parity, and stop bits size
  dcb.BaudRate = CBR_19200;
  dcb.fBinary           = TRUE;
  dcb.fParity           = FALSE;
  dcb.fOutxCtsFlow      = FALSE;
  dcb.fOutxDsrFlow      = FALSE;
  dcb.fDtrControl       = DTR_CONTROL_DISABLE;
  dcb.fDsrSensitivity   = FALSE;
  dcb.fTXContinueOnXoff = FALSE;
  dcb.fOutX             = FALSE;
  dcb.fInX              = FALSE;
  dcb.fErrorChar        = FALSE;
  dcb.fNull             = FALSE;
  dcb.fRtsControl       = RTS_CONTROL_DISABLE;
  dcb.fAbortOnError     = FALSE;
  dcb.fDummy2           = 0;
  dcb.XonLim            = 0;
  dcb.XoffLim           = 0;
  dcb.ByteSize          = 8;
  dcb.Parity            = NOPARITY;
  dcb.StopBits          = ONESTOPBIT;
  dcb.XonChar           = 0;
  dcb.XoffChar          = 0;
  dcb.ErrorChar         = 0;
  dcb.EofChar           = 0;
  dcb.EvtChar           = 0;
  dcb.wReserved1        = 0;

  // set new state
  if (!SetCommState(p->hCommPort, &dcb))
    fprintf(stderr, "SetCommState");

  // set new timeouts
  return TRUE;
}



// still incomplete syncronization...
int ComGetc(void* p0)
{
  struct _winserial *p=(struct _winserial *)p0;
  int c;

  if ( ! p->fConnected ) return -1;

  for (;;) {
    if ( p->r_count <= 0 ) {
      // wait event here

      p->f_getc_waiting = TRUE;
      WaitForSingleObject(p->h_getc_wait, 5000);
      if ( ! p->fConnected ) return -1;
    } else break;
  }

  EnterCriticalSection(&p->r_cs);
  c = p->r_buf[ p->r_tail ];
  (p->r_tail)++;
  if ( RING_MAX <= p->r_tail ) p->r_tail = 0;
  --(p->r_count);
  LeaveCriticalSection(&p->r_cs);

  return c;
}


int ComGets(void* p0, char* str, int size)
{
  struct _winserial *p=(struct _winserial *)p0;
  int i;
  for (i=0; i<size-1; i++) {
    int c = ComGetc(p);
    if ( i==0 && c < 0 ) {
      str[0] = '\0';
      return;
    }
    str[i] = c;
    if ( str[i] == '\n' ) {
      i++;
      break;
    }
  }
  str[i] = '\0';
  if ( str[i-1] == '\n' ) return 1; //OK

  return 0; //NG
}


int ComPuts(void* p0, char* str)
{
  ComWrite(p0, str, strlen(str));
  return 1; //OK
}
