/*
 * tty.c - Win32/NT serial code.
 *
 *   Contributed by Mike Connell (M.Connell@swansea.ac.uk)
 *
 *   $Id: tty.c,v 1.1 1998/08/29 20:41:34 mdanks Exp $
 */

/* serial stuff nicked from win32 API example COMM in VC4.0 */
/* heavily modified, and stripped for simplicity and lightness of being */

#include <windows.h>
#define DEBUG(x) ;

#include "tty.h"

int F_spaceorb_serial_write(T_serial *serial,unsigned char *data,int length) {
  if (WriteCommBlock(serial,data,length))
    return TRUE;

  return FALSE;
}

/* Remove the first 'n' bytes from the buffer */
static void F_spaceorb_serial_compact(T_serial *serial,int n) {
  int residue;
	
  if (n==0) return; /* nothing to do */
  if (n>serial->buffer_used) { /* too much to do */
    serial->buffer_used=0;
    return;
  }

  residue=serial->buffer_used-n;  /* bytes left in buffer */

  /* shunt left over old data up to the front */
  if (residue) 
    strncpy(serial->buffer,&serial->buffer[n],residue); 

  serial->buffer_used=residue;		/* we have less in the buffer now */
}


int F_spaceorb_serial_read(T_serial *serial,unsigned char *data,int length) {
  /* if we request more data than we already have */
  if (length>serial->buffer_used)      
    length=serial->buffer_used;

  /* nothing to do - either requested 0 or we had no data to return */
  if (length==0) 
    return 0;    

  strncpy(data,serial->buffer,length); /* stick it into output buffer */
  F_spaceorb_serial_compact(serial,length); /* We nabbed this many bytes */
  return length;
}

int F_spaceorb_serial_start(T_serial *serial,int port,int speed,int stopbits,int parity,char *name)
{
  serial->buffer_used=0;
  DEBUG(("F_serial_start"));
  serial->waiting_thread_name=malloc(strlen(name)+1);
  strcpy(serial->waiting_thread_name,name);

  if (CreateTTYInfo(serial,port,speed,stopbits,parity)!=TRUE) 
    return FALSE;

  THREADID(serial)=0;
/*	F_serial_close_connection(serial);*/
  if (OpenConnection(serial)!=TRUE) 
    return FALSE;

  return TRUE;
}


LRESULT NEAR CreateTTYInfo(T_serial *serial,int port,int speed,int stopbits,int parity)
{
  COMDEV( serial )        = 0 ;
  CONNECTED( serial )     = FALSE ;
  LOCALECHO( serial )     = FALSE ;
  AUTOWRAP( serial )      = TRUE ;
  PORT( serial )          = port ;  
   
  if (speed==9600)
    BAUDRATE( serial )      = CBR_9600; 
  else if (speed==19200)
    BAUDRATE( serial )      = CBR_19200; 
  else {
    DEBUG(("didn't understand speed\n"))
    return FALSE;
  }

  BYTESIZE( serial )      = 8 ;
  FLOWCTRL( serial )      = 0;
   
  if (!parity)
    PARITY( serial )        = NOPARITY ;
  else {
    DEBUG(("cant use parity"))
    return FALSE;
  }

  if (stopbits!=1) {
    DEBUG(("must use 1 stop bit"))
    return FALSE;
  }
  STOPBITS( serial )      = ONESTOPBIT ;
  XONXOFF( serial )       = FALSE ;
  USECNRECEIVE( serial )  = TRUE ;
  DISPLAYERRORS( serial ) = TRUE ;
  WRITE_OS( serial ).Offset = 0 ;
  WRITE_OS( serial ).OffsetHigh = 0 ;
  READ_OS( serial ).Offset = 0 ;
  READ_OS( serial ).OffsetHigh = 0 ;

  /* create I/O event used for overlapped reads / writes*/
  READ_OS( serial ).hEvent = CreateEvent( NULL,    /* no security*/
                                          TRUE,    /* explicit reset req*/
                                          FALSE,   /* initial event reset*/
                                          NULL ) ; /* no name*/

  if (READ_OS( serial ).hEvent == NULL) {
    LocalFree( serial ) ;
    return ( -1 ) ;
  }
  WRITE_OS( serial ).hEvent = CreateEvent( NULL,    /* no security*/
                                           TRUE,    /* explicit reset req*/
                                           FALSE,   /* initial event reset*/
                                           NULL ) ; /* no name*/

  if (NULL == WRITE_OS( serial ).hEvent) {
    CloseHandle( READ_OS( serial ).hEvent ) ;
    LocalFree( serial ) ;
    return ( -1 ) ;
  }

  return ( (LRESULT) TRUE ) ;
} 




BOOL NEAR OpenConnection(T_serial *serial) {            
  char       szPort[ 15 ];
  BOOL       fRetVal ;

  HANDLE        hCommWatchThread ;
  DWORD         dwThreadID ;
  COMMTIMEOUTS  CommTimeOuts ;

  wsprintf( szPort, "%s%d", "COM", PORT( serial ) ) ;

  /* open COMM device*/
  if ((COMDEV( serial ) =
    CreateFile( szPort, GENERIC_READ | GENERIC_WRITE,
	  /*MIKE was      0,                    // exclusive access*/
	  FILE_SHARE_READ | FILE_SHARE_WRITE,
                  NULL,                 /* no security attrs*/
                  OPEN_EXISTING,
                  FILE_ATTRIBUTE_NORMAL | 
                  FILE_FLAG_OVERLAPPED, /* overlapped I/O*/
                  NULL )) == (HANDLE) -1 )
	   /*MIKE return ( FALSE ) ;*/
	  return ( TRUE ) ;
  else {
    /* get any early notifications*/
    SetCommMask( COMDEV( serial ), EV_RXCHAR ) ;
    /* setup device buffers*/
    SetupComm( COMDEV( serial ), 32,32  ) ; /* worked with 16,16*/
    /*MIKE orig:	SetupComm( COMDEV( serial ), 4096,4096 ) ;*/
    /* purge any information in the buffer*/
    PurgeComm( COMDEV( serial ), PURGE_TXABORT | PURGE_RXABORT |
                                 PURGE_TXCLEAR | PURGE_RXCLEAR ) ;

    /* set up for overlapped I/O*/
    CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF ;
    CommTimeOuts.ReadTotalTimeoutMultiplier = 0 ;
    CommTimeOuts.ReadTotalTimeoutConstant = 1000 ;
    /*CommTimeOuts.ReadTotalTimeoutConstant = 100 ;*/
    CommTimeOuts.WriteTotalTimeoutMultiplier = 0 ;
    CommTimeOuts.WriteTotalTimeoutConstant = 1000 ;
    /*CommTimeOuts.WriteTotalTimeoutConstant = 100 ;*/
    SetCommTimeouts( COMDEV( serial ), &CommTimeOuts ) ;
  }

  fRetVal = SetupConnection(serial) ;

  if (fRetVal) {
    CONNECTED( serial ) = TRUE ;

    /* Create a secondary thread*/
    /* to watch for an event.*/
    if (NULL == (hCommWatchThread =
                      CreateThread( (LPSECURITY_ATTRIBUTES) NULL,
                                    0, 
                                    (LPTHREAD_START_ROUTINE) CommWatchProc,
                                    (LPVOID) serial,
                                    0, &dwThreadID )))
    {
         CONNECTED( serial ) = FALSE ;
         CloseHandle( COMDEV( serial ) ) ;
         fRetVal = FALSE ;
    }
    else {
      THREADID( serial ) = dwThreadID ;
      HTHREAD( serial ) = hCommWatchThread ;

      /* assert DTR */
      EscapeCommFunction( COMDEV( serial ), SETDTR ) ;
    }
  }
  else {
    CONNECTED( serial ) = FALSE ;
    CloseHandle( COMDEV( serial ) ) ;
  }

  return ( fRetVal ) ;
} /* end of OpenConnection()*/




BOOL NEAR SetupConnection(T_serial *serial) {
  BOOL       fRetVal ;
  BYTE       bSet ;
  DCB        dcb ;

  dcb.DCBlength = sizeof( DCB ) ;
  GetCommState( COMDEV( serial ), &dcb ) ;
  dcb.BaudRate = BAUDRATE( serial ) ;
  dcb.ByteSize = BYTESIZE( serial ) ;
  dcb.Parity = PARITY( serial ) ;
  dcb.StopBits = STOPBITS( serial ) ;

  /* setup hardware flow control*/
  bSet = (BYTE) ((FLOWCTRL( serial ) & FC_DTRDSR) != 0) ;
  dcb.fOutxDsrFlow = bSet ;
  if (bSet)
    dcb.fDtrControl = DTR_CONTROL_HANDSHAKE ;
  else
    dcb.fDtrControl = DTR_CONTROL_ENABLE ;

  bSet = (BYTE) ((FLOWCTRL( serial ) & FC_RTSCTS) != 0) ;
  dcb.fOutxCtsFlow = bSet ;

  if (bSet)
    dcb.fRtsControl = RTS_CONTROL_HANDSHAKE ;
  else
    dcb.fRtsControl = RTS_CONTROL_ENABLE ;

  /* setup software flow control*/
  bSet = (BYTE) ((FLOWCTRL( serial ) & FC_XONXOFF) != 0) ;
  dcb.fInX = dcb.fOutX = bSet ;
  dcb.XonChar = ASCII_XON ;
  dcb.XoffChar = ASCII_XOFF ;
  dcb.XonLim = 100 ;
  dcb.XoffLim = 100 ;

  /* other various settings*/
  dcb.fBinary = TRUE ;
  dcb.fParity = TRUE ;
  fRetVal = SetCommState( COMDEV( serial ), &dcb ) ;

  if (fRetVal!=TRUE)
    DEBUG(("Failed to SetCommSate"))

  return ( fRetVal ) ;
} /* end of SetupConnection()*/




BOOL NEAR F_spaceorb_serial_close_connection(T_serial *serial)
{
   CONNECTED( serial ) = FALSE ;
   /* disable event notification and wait for thread to halt*/
   SetCommMask( COMDEV( serial ), 0 ) ;

   /* block until thread has been halted */
   while(THREADID(serial) != 0);

   /* drop DTR*/
   EscapeCommFunction( COMDEV( serial ), CLRDTR ) ;

   /* purge any outstanding reads/writes and close device handle*/
   PurgeComm( COMDEV( serial ), PURGE_TXABORT | PURGE_RXABORT |
                                   PURGE_TXCLEAR | PURGE_RXCLEAR ) ;

   CloseHandle( COMDEV( serial ) ) ;

   return ( TRUE ) ;
} /* end of CloseConnection()*/

int NEAR ReadCommBlock(T_serial *serial,LPSTR lpszBlock, int nMaxLength )
{
  BOOL    fReadStat ;
  COMSTAT ComStat ;
  DWORD   dwErrorFlags;
  DWORD   dwLength;
  DWORD   dwError;

  /* only try to read number of bytes in queue */
  ClearCommError( COMDEV( serial ), &dwErrorFlags, &ComStat ) ;
  dwLength = min( (DWORD) nMaxLength, ComStat.cbInQue ) ;

  if (dwLength > 0) {
    fReadStat = ReadFile( COMDEV( serial ), lpszBlock,
                          dwLength, &dwLength, &READ_OS( serial ) ) ;

    if (!fReadStat) {
      if (GetLastError() == ERROR_IO_PENDING) {
        OutputDebugString("\n\rIO Pending");

	/* We have to wait for read to complete. */
	/* This function will timeout according to the*/
	/* CommTimeOuts.ReadTotalTimeoutConstant variable*/
	/* Every time it times out, check for port errors*/
	while(!GetOverlappedResult( COMDEV( serial ), 
		&READ_OS( serial ), &dwLength, TRUE ))
	{
          dwError = GetLastError();

          /* normal result if not finished*/
          if(dwError == ERROR_IO_INCOMPLETE) {
            continue;
          }
          else {
            DEBUG(("error in ReadCommBlock"))
            ClearCommError( COMDEV( serial ), &dwErrorFlags, &ComStat ) ;
            break;
          }
	}
      }
      else {
        DEBUG(("error in ReadCommBlock"))

        /* some other error occurred */
        dwLength = 0 ;
	ClearCommError( COMDEV( serial ), &dwErrorFlags, &ComStat ) ;
	/* printf("error in ReadComm from Readfile");*/
      }
    }
  }
   
  return ( dwLength ) ;
} /* end of ReadCommBlock()*/

BOOL NEAR WriteCommBlock(T_serial *serial,LPSTR lpByte , DWORD dwBytesToWrite) {
  BOOL        fWriteStat ;
  DWORD       dwBytesWritten ;
  /*NPTTYINFO   npTTYInfo ;*/
  DWORD       dwErrorFlags;
  DWORD   	dwError;
  COMSTAT     ComStat;
  char        szError[ 10 ] ;

  fWriteStat = WriteFile( COMDEV( serial ), lpByte, dwBytesToWrite,
	                       &dwBytesWritten, &WRITE_OS( serial ) ) ;

  if (!fWriteStat) {
    if(GetLastError() == ERROR_IO_PENDING) {
      while(!GetOverlappedResult( COMDEV( serial ),
        &WRITE_OS( serial ), &dwBytesWritten, TRUE ))
      {
        dwError = GetLastError();


        /* normal result if not finished */
        if(dwError == ERROR_IO_INCOMPLETE) {
          continue;
        }
	else {
          DEBUG(("error in WriteCommBlock"))

          /* an error occurred, try to recover*/
          wsprintf( szError, "<CE-%u>", dwError ) ;
          ClearCommError( COMDEV( serial ), &dwErrorFlags, &ComStat ) ;
          break;
        }
      }
    }
    else {						 	
      /* some other error occurred*/
      DEBUG(("error in WriteCommBlock"))
      ClearCommError( COMDEV( serial ), &dwErrorFlags, &ComStat ) ;
      return ( FALSE );
    }
  }

  return ( TRUE ) ;
} /* end of WriteCommBlock()*/


/* this secodary thread watches for events */
DWORD FAR PASCAL CommWatchProc( LPSTR lpData ) {
  DWORD       dwEvtMask ;
  T_serial *serial = (T_serial *) lpData ;
  OVERLAPPED  os ;
  int        len ;
  BYTE       data[ MAXBLOCK + 1] ;

  memset( &os, 0, sizeof( OVERLAPPED ) ) ;

  /* create I/O event used for overlapped read*/
  os.hEvent = CreateEvent( NULL,    /* no security*/
                           TRUE,    /* explicit reset req*/
                           FALSE,   /* initial event reset*/
                           NULL ) ; /* no name*/

  if (os.hEvent == NULL) {
    DEBUG(("failed to CommWathcProg"))
    return ( FALSE ) ;
  }
  if (!SetCommMask( COMDEV( serial ), EV_RXCHAR )) {
    DEBUG(("filaed to settcommmask"))
    return ( FALSE ) ;
  }

  while ( CONNECTED( serial ) ) {
    dwEvtMask = 0 ;
    WaitCommEvent( COMDEV( serial ), &dwEvtMask, NULL );
    if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR) {
      do {
        /* copy the data into T_serial structure */
        if (len = ReadCommBlock(serial,(LPSTR) data, MAXBLOCK )) {
    
          /* enough space? */
	  if (len+serial->buffer_used<SERIAL_MAX_BUFFER_POS) { 
            /* follow existing data with new data */
            strncpy(&serial->buffer[serial->buffer_used],data,len); 
            serial->buffer_used+=len;
	  } 

          /* We could call a function here */
        }
      } while ( len > 0 ) ;
    }
  }

  /* get rid of event handle*/
  CloseHandle( os.hEvent ) ;

  /* clear information in structure (kind of a "we're done flag")*/
  THREADID( serial ) = 0 ;
  HTHREAD( serial ) = NULL ;

  return( TRUE ) ;
} /* end of CommWatchProc()*/

