//=========================================================================
// 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 "SerialDevice.h"
#include "Time.h"


SerialDevice::SerialDevice(const char *ttyName)
  : DeviceInterface(ttyName)
{
    char errorBuf[256];

    errorBuf[0] = 0x00;

    _commsDebugMode = DebugOff;		// Debugging is off by default

    _fd = open(ttyName, O_RDWR);	// Open device

    _dev = fdopen(_fd,"rwb");		// Get a file pointer

    if (_fd == ERROR) {
      sprintf(errorBuf, "SerialDevice::SerialDevice() - "
	      "open of \"%s\" failed: %s",
	      ttyName, strerror(errno));

      throw BadSystemCall(errorBuf);
    }

                                  // Store orig termios structure
    if (tcgetattr( _fd, &orig_termios_p) == ERROR) {
      sprintf(errorBuf, "SerialDevice::SerialDevice() - F_GETFT failed: %s",
	      strerror(errno));
      throw BadSystemCall(errorBuf);
    }

    tcflush(_fd, TCIOFLUSH);		// Flush input and output queues
}


SerialDevice::~SerialDevice()
{
    if (_fd != ERROR)
    {
      tcsetattr( _fd, TCSADRAIN, &orig_termios_p);
      close (_fd);	    		// Close device

    if (_commsDebugMode == DebugOn)
      Syslog::write ("Device closed\n");
    }
}


void SerialDevice::reset()
{
}

int SerialDevice::nonBlocking()
{
    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 status;
}

int SerialDevice::blocking()
{
	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");
	}

	return status;
}

int SerialDevice::raw()
{
     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()
{
     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)
{
  //  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;
}

int SerialDevice::getFd()
{
  return (_fd);
}

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


int SerialDevice::nRecvdBytes()
{
  if (_fd == ERROR)
    return ERROR;

  // Return number of bytes available to be read
  return dev_ischars(_fd);
}

int SerialDevice::sendBreak()
{
  if (_commsDebugMode == DebugOn)
    Syslog::write("Sending break\n");

  // Send a 300 millisecond break
  return tcsendbreak(_fd, 300);
}

int SerialDevice::sendBreak(int milliseconds)
{
  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 )
{

  // 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 )
{

  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)
{
    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");

  if (timeout > 0) {
      // Convert from milliseconds to timeval format
      timeout_tv.tv_sec  = timeout / 1000;
      timeout_tv.tv_usec = (timeout % 1000) * 1000;

      FD_ZERO(&readFds);
      FD_SET(_fd, &readFds);

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


      switch (select(_fd + 1, &readFds, 0, 0, &timeout_tv)) {
        case -1:
	  if (_commsDebugMode == DebugOn)
	    Syslog::write ("select: failed\n");
	  throw BadSystemCall("select");
	  
        case 0:
	    if (_commsDebugMode == DebugOn)
		Syslog::write ("select: timeout\n");
	    return -1;
	    //throw TimedOut(0);
	    
      default:
	  if (_commsDebugMode == DebugOn)
	      Syslog::write ("select: data available to read\n");
	  
      } /* switch */
  } /* if */
  
  if ( (nBytes = ::read(_fd, buf, maxBytes)) == ERROR) {
      if (_commsDebugMode == DebugOn)
	  Syslog::write ("read failed\n");
      throw BadSystemCall("select");
  }

  buf[nBytes] = '\0';		// Null terminate string

  if ( _commsDebugMode == DebugOn )
    Syslog::write("%s: read %d chars %s", ttyname(_fd), nBytes, buf);

  return nBytes;

}

/*-----------------------------------------------------------------------*/
int SerialDevice::readNChars(char *buf, int count, int 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(count != nBytes)
      Syslog::write("ReadNBytes:: Expecting %d bytes and got %d bytes.  There seem to be issues.", count, nBytes);
  
  if ( _commsDebugMode == DebugOn )
      Syslog::write("read %d chars from dev %s\n", nBytes, ttyname(_fd));
  
  return nBytes;
}

int SerialDevice::write(const char *string, int nBytes)
{
  if (::write( _fd, string, nBytes) == ERROR)
  {
    if (_commsDebugMode == DebugOn)
      Syslog::write ("write failed\n");
    return ERROR;
  }

  if ( _commsDebugMode == DebugOn )
    Syslog::write("%s: wrote %d chars %s", ttyname(_fd), nBytes, string);

  return nBytes;
}

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;
}

int SerialDevice::writeFlush(const char *string, int nBytes)
{
	int w = write(string,nBytes);
	int flush = fflush(_dev);
	int drain = tcdrain(_fd);
	if (flush || drain) Syslog::write("Error flushing write buffer");
	return w;
}

void SerialDevice::clearPort(void)
{
  char buf[256];
  // tcflush(_fd, TCIFLUSH);		// Flush input queues
  while (dev_ischars(_fd) > 0 )
    ::read(_fd, buf, 256);
}


int SerialDevice::flushReceiveBuf()
{

#if 0
    int nBytes, numReady, tries = 3;
    char buf[BUFSIZ];

    while ( (numReady = nRecvdBytes()) > 0 )
    {
	if ( tries-- > 0 )
	{
	    nBytes = numReady > sizeof(buf) ? sizeof(buf) : numReady;
	    nBytes = ::read( _fd, buf, nBytes );
	    if ( nBytes == ERROR )
	    {
                if (_commsDebugMode == DebugOn)
                {
  		  Syslog::write("clearPort dev %d: %s %m\n", ttyname(_fd));
		  return ERROR;
		}
	    }

	    if ( _commsDebugMode == DebugOn )
		Syslog::write("%d cleared from dev %s\n", nBytes, ttyname(_fd));
	} /* tries */
	else
	{
            if (_commsDebugMode == DebugOn)
		    Syslog::write("clearPort leaves %d on %s\n",
		       numReady, ttyname(_fd) );
	    return ERROR;
	}
    } /* while */

#endif
    return 0;
}

// throw (TimedOut, BufferFull, BadSystemCall);

int SerialDevice::readUntil(char *buf, int maxBytes, char *term, int timeout)
{
  unsigned long t0, readTimeout;

  t0 = Time::milliseconds();

  //_commsDebugMode = DebugOn;

  if ( _commsDebugMode == DebugOn )
      Syslog::write("readUntil: term = %s", term );

  char c;
  int i = 0;
  int nBytes = 0;
  int bytesRead = 0;

  while (term[i] != '\0')
    {
      unsigned int elapsed = Time::milliseconds() - t0;

      if (elapsed > timeout)
	  return -1;
      //throw TimedOut(nBytes);

      if (nBytes == maxBytes) {
	   if( _commsDebugMode == DebugOn )
		Syslog::write("readUntil: Buffer full! - %d chars: %s",
			      nBytes, buf );

	   throw BufferFull();
      }

      // Time remaining for read()
      readTimeout = max(timeout - elapsed, 1);

      // Read a single character from tty port
      bytesRead = SerialDevice::readNChars( buf+nBytes, 1, readTimeout);
      
      //if error or we didn't get our byte
      if(bytesRead == ERROR || bytesRead != 1 ) {
	  return -1;
	  //throw CommsFailure();
      } else {
	  
	  c = *(buf + nBytes);
	  nBytes++;
	  
	  // Is character read part of term? */
	  if ( c != term[i++] )
	      i = 0;
	  
	  if ( _commsDebugMode == DebugOn )
	      Syslog::write("readUntil: read (%02X) %c", c, c);
      } /* else */
    } /* while */ 
  if (i != strlen(term)) {
      // Must have timed out
      if ( _commsDebugMode == DebugOn )
	  Syslog::write("readUntil: expired in %d having read %d chars)",
			Time::milliseconds() - t0, nBytes);
      
      throw TimedOut(nBytes);
      
  } /* if */
  
  if ( _commsDebugMode == DebugOn )
      Syslog::write("readUntil: succeeded in %d msec",
		    Time::milliseconds() - t0);
  
  return nBytes;
}


int SerialDevice::readUntil(char *term, int timeout)
{
    /* old code
       
       char buf[256];
       int nBytes, status;
       
       nBytes = SerialDevice::read(buf, strlen(term), timeout);
       
       status = (strcmp(term, buf) == 0) ? OK : ERROR;
       
       if ( _commsDebugMode == DebugOn )
       Syslog::write("readUntil %s\n", (status == OK ) ? "Succeed" : "Failed");
       
       return status;
    */
    unsigned long t0, readTimeout;
    
    t0 = Time::milliseconds();
    
    if ( _commsDebugMode == DebugOn )
	Syslog::write("readUntil: term = %s\n", term );
    
    char c;
    int i = 0;
    int nBytes = 0;
    int bytesRead = 0;

    while (term[i] != '\0') {
	int elapsed = Time::milliseconds() - t0;
	if (elapsed > timeout)
	    return -1;
	//throw TimedOut(nBytes);
	
	readTimeout = max(timeout - elapsed, 1);
	
	// Read a single character from tty port
	bytesRead = SerialDevice::readNChars( &c, 1, readTimeout);

	//if error or we didn't get our byte
	if(bytesRead == ERROR || bytesRead != 1 ) {
	    return -1;
	    //throw CommsFailure();
	} else {
	    nBytes++;
		
	    // Is character read part of term? */
	    if ( c != term[i++] )
		i = 0;
	    
	    if ( _commsDebugMode == DebugOn )
		Syslog::write("readUntil: read %c", c);
	}
	
    } /* while */
    
    if ( _commsDebugMode == DebugOn )
	Syslog::write("readUntil: succeeded in %d msec\n",
		      Time::milliseconds() - t0);
    
    return OK;
}

int SerialDevice::confirm(char *term, int timeout)
{
  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);
    }
  catch (CommsFailure e) 
      {
	  Syslog::write("SerialDevice::confirm() - %s", e.msg);
      }
      
  return status;
}


void SerialDevice::printTermios()
{
    struct termios termios_p;

    tcgetattr( _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");

}

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

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

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

SerialDevice::CommsFailure::CommsFailure()
  : Exception("Comms Failure")
{
}

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

