//=========================================================================
// Summary  : */
// Filename : SerialDevice.cc
// Author   : */
// Project  : */
// Revision : 1
// Created  : 2000/08/19
// Modified : 2000/08/19
//=========================================================================
// Description :
//=========================================================================

/*
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>

#include <sys/dev.h>
#include <sys/select.h>

#include "Syslog.h"
#include "Time.h"
*/
#include "WinSerialDevice.h"
#include "remote/Modem.h"


class Syslog
{
public:
  static void write(const char*){};

};



SerialDevice::SerialDevice(const char *ttyName)
{
    char errorBuf[256];
   
    errorBuf[0] = 0x00;

	_baud = 0;
    _commsDebugMode = DebugOff;		// Debugging is off by default
    _pcName = new char[strlen(ttyName)+1];
    strcpy(_pcName, ttyName);

    // Init the port handle.
    _hComm = CreateFile( ttyName,  
                    GENERIC_READ | GENERIC_WRITE, 
                    0, 
                    0, 
                    OPEN_EXISTING,
                    0, //                    FILE_FLAG_OVERLAPPED,
                    
                    0);

    if (_hComm == INVALID_HANDLE_VALUE)
    {
      // error opening port; abort
      return;
    }

    // Should flush the port, but I don't know how.
//    tcflush(_fd, TCIOFLUSH);		// Flush input and output queues

}


SerialDevice::~SerialDevice()
{
  // Should close the _hComm, but I do not know how.
  delete [] _pcName;

  CloseHandle( _hComm);
}


void SerialDevice::reset()
{
}

int SerialDevice::nonBlocking()
{
 Assert(false);
/*
    int flags = 0;
    int status;

    if (_fd == ERROR)
	return EBADF;

    if ( (flags = fcntl(_fd, F_GETFL )) == ERROR)
    {
      Syslog::write ("F_GETFL failed\n");
      return ERROR;
    }

    flags |= O_NONBLOCK;	
    status = fcntl(_fd, F_SETFL, flags);

    if (status == ERROR)
      Syslog::write ("F_SETFL failed\n");

    else if (_commsDebugMode == DebugOn)
      Syslog::write ("Device set to non-blocking mode\n");
*/
    return false;	
} 

int SerialDevice::blocking()
{
  return true;
  /*
    int flags = 0;
    int status;

    if (_fd == ERROR)
	return EBADF;


    if ( (flags = fcntl( _fd, F_GETFL)) == ERROR)
    {
      Syslog::write ("_F_GETFL failed\n");
      return ERROR;
    }

    flags &= ~O_NONBLOCK;	
    status = fcntl(_fd, F_SETFL, flags);
    
    if (status == ERROR)
      Syslog::write ("_F_SETFL failed\n");

    else if (_commsDebugMode == DebugOn)
      {
      Syslog::write ("Device set to blocking mode\n");
      printf ("blocking\n");
      }
    return status;	
    */
} 

int SerialDevice::raw()
{
  return true;
  /*
     int status;
     struct termios termios_p;

     if (tcgetattr( _fd, &termios_p) == ERROR)
       {
	 Syslog::write ("F_GETFL failed\n");
      return ERROR;
       }

    termios_p.c_cc[VMIN]  =  1;
    termios_p.c_cc[VTIME] =  0;

    termios_p.c_iflag = 0;
    termios_p.c_oflag &= ~OPOST;
    termios_p.c_lflag &= ~(ISIG | ICANON | ECHO );
    termios_p.c_oflag &= ~( CSIZE | PARENB );
    termios_p.c_oflag |= CS8;

    if ( (status = tcsetattr( _fd, TCSANOW, &termios_p)) == ERROR)
      Syslog::write ("F_GETFL failed\n");


    return status;
    */
}

int SerialDevice::nonRaw()
{
  Assert(false);
  return false;

  /*
     int status;
     struct termios termios_p;

     if (tcgetattr( _fd, &termios_p) == ERROR)
       {
	 Syslog::write ("F_GETFL failed\n");
      return ERROR;
       }

    termios_p.c_lflag |= (ICANON|ECHO|ISIG|ECHOE|ECHOK|ECHONL);

    if ( (status = tcsetattr( _fd, TCSADRAIN, &termios_p)) == ERROR)
      Syslog::write ("F_GETFL failed\n");

    return status;
    */
}

int SerialDevice::setLineFormat(speed_t baud, int data, int stop, const char *parity)
{
  _baud = baud;

  DCB dcb;

  FillMemory(&dcb, sizeof(dcb), 0);
  dcb.DCBlength = sizeof(dcb);

  char buffer[128];
//  sprintf(buffer, "baud=%d parity=%c data=%d stop=%d",
//          baud, *parity, data, stop);
  sprintf(buffer, "%d,%c,%d,%d",
          baud, *parity, data, stop);


   if (!BuildCommDCB(buffer, &dcb)) 
   {  
      // Couldn't build the DCB. Usually a problem
      // with the communications specification string.
     Assert(false);
     return false;
   }
   else
   {
      // DCB is ready for use.
 
     // Set new state.
     if (!SetCommState(_hComm, &dcb))
     {
       // Error in SetCommState. Possibly a problem with the communications 
       return 0;
     }
     else
       return true;
   }

/*
  //  Syslog::write("speed: %d\n",baud);
  int status;

    if (_fd == ERROR)
	return (EBADF);
 
    struct termios termios_p;
 
    if (tcgetattr( _fd, &termios_p) == ERROR)
    {
      Syslog::write ("F_GETFL failed\n");
      return ERROR;
    }
  
    cfsetispeed(   &termios_p, baud );
  
    cfsetospeed(   &termios_p, baud );
  
    csetStopBits(  &termios_p, stop);
  
    csetDataBits(  &termios_p, data);
  
    csetParityBits(&termios_p, parity);
    

                                        // Leave end-of-line unprocessed 
    termios_p.c_iflag &= ~IGNCR;		// Ignore CR on input
    termios_p.c_iflag &= ~ICRNL;	// Don't map CR to NL
    termios_p.c_iflag &= ~INLCR;	// Don't map NL to CR

    termios_p.c_lflag &= ~ICANON;	// Disable Line Oriented Mode 
    termios_p.c_lflag &= ~ECHO;		// Disable input to output echo

    termios_p.c_lflag |= IEXTEN;	// Enable QNX Extensions to POSIX
    termios_p.c_cflag |= CLOCAL;	// Ignore modem status lines
    termios_p.c_cflag |= CREAD;		// Enable Receiver

    termios_p.c_cflag &= ~IHFLOW;	// Ignore modem status lines
    termios_p.c_cflag &= ~OHFLOW;	// Ignore modem status lines

    if ( (status = tcsetattr( _fd, TCSANOW, &termios_p)) == ERROR)
      Syslog::write ("F_GETFL failed\n");
    
    Syslog::write("SerialDevice: done...\n");
    return status;
    */
}


const char *SerialDevice::devName()
{
    return _pcName;
}


int SerialDevice::nRecvdBytes()
{ 
  if (_hComm == INVALID_HANDLE_VALUE)
    return -1;


  COMSTAT comstat;
  DWORD   temp;
  if (ClearCommError(_hComm,
                  &temp,
                  &comstat))
  {
    return comstat.cbInQue;
  }
  else
  {
    int i = GetLastError();

    return 0;
  }
} 

int SerialDevice::sendBreak()
{ 
  return sendBreak(300);
}

int SerialDevice::sendBreak(int milliseconds)
{ 
  Assert(false);
  return false;

  if (_commsDebugMode == DebugOn)
    Syslog::write("Sending break\n");

  // Send break with a user specified duration 
//  return tcsendbreak(_fd, milliseconds);
}

void SerialDevice::commsDebugMode(CommsDebugMode mode)
{
  // Toggle debug mode on/off
  _commsDebugMode = mode;
}

int SerialDevice::csetStopBits(struct termios *termios_p, int stopBits )
{
  Assert(false);
  return false;

  /*
  // old version had structure backwards
  // replaced == 1 with ==2
  // JAR and AM 12/09/99
  
  if (stopBits == 2)
    termios_p->c_cflag |= CSTOPB;
  else
    termios_p->c_cflag &= ~CSTOPB;

  return OK;
  */
} 

int SerialDevice::csetDataBits(struct termios *termios_p, int dataBits )
{
  Assert(false);
  return false;

  /*
  int cflag = termios_p->c_cflag;
  
  // old version was not inverting CSIZE and consequently
  // not setting databits correctly!
  // JAR and AM 12/09/99

  if (dataBits == 5)	
    cflag = (cflag & ~CSIZE) | CS5;
  
  else if (dataBits == 6)	
    cflag = (cflag & ~CSIZE) | CS6;

  else if (dataBits == 7)	
    cflag = (cflag & ~CSIZE) | CS7;

  else if (dataBits == 8)	
    cflag = (cflag & ~CSIZE) | CS8;

  else
    return ERROR;

  termios_p->c_cflag = cflag;
  
  return OK;
  */
} 

int SerialDevice::csetParityBits(struct termios *termios_p, const char* parity)
{
    Assert(false);
  return false;

  /*
    int cflag = termios_p->c_cflag;

    if (stricmp(parity, "NONE") == 0)
	cflag &= ~PARENB;
	
    else if (stricmp(parity, "EVEN") == 0) 
        cflag |= PARENB, cflag &= ~PARODD; 

    else if (stricmp(parity, "ODD") == 0)
	cflag |= PARENB, cflag |= PARODD; 

    else
	return ERROR;

    termios_p->c_cflag = cflag;
    return OK;
    */
} 

int SerialDevice::read(char *buf, int maxBytes, int timeout)
{
//  int nBytes;
//  struct timeval timeout_tv;
//  fd_set readFds;

  if (_commsDebugMode == DebugOn)
    Syslog::write ("reading\n");

  Assert(timeout > 0);

  COMMTIMEOUTS timeouts;
  if (!GetCommTimeouts(_hComm, &timeouts))
  {
    return -1;
  }

  // Set the total read timeout.
  timeouts.ReadTotalTimeoutConstant = timeout;

  // No other read timeouts apply.
  timeouts.ReadIntervalTimeout = 0;
  timeouts.ReadTotalTimeoutMultiplier = 0;

  if (!SetCommTimeouts(_hComm, &timeouts))
  {
    return -1;
  }


  DWORD dwRead;

  // Issue read operation.
  if (!ReadFile(_hComm, buf, maxBytes, &dwRead, NULL)) 
  {
    return -1;
  }
  else 
  {    
    // Read completed successfully.
    if (dwRead < maxBytes)
      buf[dwRead] = '\0';		// Null terminate string

    return dwRead;
  }
}

/*-----------------------------------------------------------------------*/
int SerialDevice::readNChars(char *buf, int count, int timeout)
{
  // start DAP insert 5/16/2001
  // Wait until all the bytes are at the device
  int ms = 10;
  long ii = 1;
  int fudgeFactor = 20;
  Assert(_baud != 0);
  double bytesPerCount = (double)_baud/8.*(double)ms/1000.;
  while (nRecvdBytes() < count) {
	  Sleep(ms);
	  // If this is taking too long, stop waiting
	  if (bytesPerCount*ii++ >= fudgeFactor*count) break;
  }
  // end DAP insert 5/16/2001

  return read(buf, count, timeout);
  /*
  int nBytes = 0;

  int flags;

  if (_commsDebugMode == DebugOn)
    Syslog::write ("serialdevice -- reading\n");

  if( timeout < 100 ) {
//       if( timeout < 50 )
	    return 0;
//       timeout = 100;
  } 

  if( (nBytes = ::dev_read( _fd, buf, count,
			    count, timeout/100, timeout/100, 0, 0 )) 
      == ERROR ) {
       if (_commsDebugMode == DebugOn) {
	    char errBuf[128];
	    int error = errno;
	    strcpy( errBuf, "Read failed: " );
	    switch( error ) {
	    case EAGAIN:
		 strcat( errBuf, "eagain");
                 break;
	    case EBADF:
		 strcat( errBuf, "ebadf");
                 break;
	    case EINTR:
		 strcat( errBuf, "eintr");
                 break;
	    case EIO:
		 strcat( errBuf, "eio");
                 break;
	    case ENOSYS:
		 strcat( errBuf, "enosys");
                 break;
	    default:
		 sprintf( errBuf, "error %d", error );
	    }
	    Syslog::write("%s", errBuf );
       } else {
	    Syslog::write ("read failed" );
       }

       return ERROR;
  }
  
  buf[nBytes] = '\0';           // Null terminate string
  
  if ( _commsDebugMode == DebugOn )
       Syslog::write("read %d chars from dev %s\n", nBytes, ttyname(_hComm));
  
  return nBytes;
  */
}

int SerialDevice::write(const char *string, int nBytes)
{
  DWORD dw = -1;

  BOOL bWrote = WriteFile(_hComm,
                          string,
                          nBytes,
                          &dw,
                          0);

  if (bWrote)
  {
    return dw;
  }
  else
  {
    volatile int i = GetLastError();   
    return 0;
  }
}

int SerialDevice::writeRepeat( const char *string, int nBytes, int reps )
{
     int count = 0;
     for( int i=0; i<reps; i++ )
	  count += write( string, nBytes );
     return count;
}

void SerialDevice::clearPort(void)
{
	PurgeComm(_hComm,PURGE_TXCLEAR | PURGE_RXCLEAR);
//  Assert(false);
}


int SerialDevice::flushReceiveBuf()
{
	/*
	int nBytes, tries = 3;
	char buf[256];
	
	nBytes = read( buf, 256, 10 );
	while (nBytes > 0 )
	{
		nBytes = read( buf, 256, 10 );
	}
	return OK;
	*/
	Assert(false);
	return -1;
}


int SerialDevice::confirm(char *term, int timeout)
{
  Assert(false);
  return 0;
#if 0
  int status = ERROR;

  try
    {
      status = readUntil(term, timeout);
    }
  catch (TimedOut e)
    {
      Syslog::write("SerialDevice::confirm() - %s", e.msg);
    }
  catch (BadSystemCall e)
    {
      Syslog::write("SerialDevice::confirm() - %s", e.msg);
    }
  return status;
#endif
}


void SerialDevice::printTermios()
{
  Assert(false);
  return;
#if 0
    struct termios termios_p;
 
    tcgetattr( _hComm/*_fd*/, &termios_p);

    printf ("Signal interrupt on break %s\n", 
	    ((termios_p.c_iflag & BRKINT) ? "Enabled" : "Disabled"));

    if (termios_p.c_iflag & ICRNL)
      printf ("CR mapped to NL on input\n");

    if (termios_p.c_iflag & IGNBRK)
      printf ("Break ignored\n");

    if (termios_p.c_iflag & IGNCR)
      printf ("CR ignored\n");

    if (termios_p.c_iflag & IGNPAR)
      printf ("Ignore Parity Errors\n");

    if (termios_p.c_iflag & INLCR)
      printf ("NL mapped to CR on input");

    if (termios_p.c_iflag & INPCK)
      printf ("Parity checking enabled on input");

    if (termios_p.c_iflag & ISTRIP)
      printf ("Top bit stripping Enabled\n");

    if (termios_p.c_iflag & IXOFF)
      printf ("Input Software Flow Control Xon/Xoff Enabled\n");

    if (termios_p.c_iflag & IXON)
      printf ("Output Software Flow Control Xon/Xoff Enabled\n");

    if (termios_p.c_iflag & PARMRK)
      printf ("Mark parity errors on input data\n");


    if (termios_p.c_oflag & OPOST)
      printf ("Enable output processing\n");


    printf ("Modem status lines %s \n",
	    ((termios_p.c_cflag & CLOCAL) ? "ignored" : "used"));

    printf ("Receiver %s \n",
	    ((termios_p.c_cflag & CREAD) ? "enabled" : "disabled"));


    printf ("Input baud rate: %d\n",  termios_p.c_ispeed);
    printf ("Output baud rate: %d\n", termios_p.c_ospeed);

    switch (termios_p.c_cflag & CSIZE)
    {
      case CS5:
    	printf ("Data bits: 5\n");
	break;

      case CS6:
    	printf ("Data bits: 6\n");
	break;
	
      case CS7:
    	printf ("Data bits: 7\n");
	break;

      case CS8:
    	printf ("Data bits: 8\n");
	break;
    } /* switch */

    printf ("Stop bits: %d\n", (termios_p.c_cflag & CSTOPB ? 2 : 1));

    if (termios_p.c_cflag & PARENB)
      printf ("Parity: %s\n", (termios_p.c_cflag & PARODD ? "Odd" : "Even"));
    else
      printf ("Parity: Disabled\n");

    if (termios_p.c_cflag & HUPCL)
      printf ("Hangup on last close enabled\n");


    if (termios_p.c_lflag & IEXTEN)
    {	
      if (termios_p.c_cflag & PARSTK)
	printf ("Stick parity\n");

      if (termios_p.c_cflag & IHFLOW)
	printf ("Hardware input flow control enabled\n");

      if (termios_p.c_cflag & OHFLOW)
	printf ("Hardware output flow control enabled\n");
    } /* if */

    if (termios_p.c_lflag & ECHO)
      printf ("Echo enabled\n");

    if (termios_p.c_lflag & ICANON)
      printf ("Canonical input mode enabled\n");

    if (termios_p.c_lflag & ISIG)
      printf ("Signals Enabled\n");

    if (termios_p.c_lflag & NOFLSH)
      printf ("Disabled flush after interrupt, quit or suspend\n");

    if (termios_p.c_lflag & TOSTOP)
      printf ("Send SIGTTOU for background output\n");
#endif
} 

SerialDevice::TimedOut::TimedOut(int nBytesRead)
  : Exception("Timed Out")
{
      _nBytes = nBytesRead;
}

int SerialDevice::TimedOut::nBytesRead()
{
  return _nBytes;
}

SerialDevice::BufferFull::BufferFull()
  : Exception("Buffer Full")
{
}

SerialDevice::BadSystemCall::BadSystemCall(char *systemCall)
  : Exception(systemCall)
{
}

