/****************************************************************************/
/* Copyright (c) 2002 MBARI                                                 */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  :                                                               */
/* Filename : Lisst.cc                                                      */
/* Author   :                                                               */
/* Project  :                                                               */
/* Version  : 1.0                                                           */
/* Created  : 07/09/2002                                                    */
/* Modified :                                                               */
/* Archived :                                                               */
/****************************************************************************/
/* Modification History:                                                    */
/****************************************************************************/
#include <unistd.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <i86.h>
#include <fcntl.h>
#include <errno.h>
#include <iostream.h>
#include <time.h>
#include "TimeP.h"

#include <math.h>
#include <FloatAttribute.h>
#include <AttributeParser.h>
#include <StringAttribute.h>
#include <IntegerAttribute.h>
#include "Lisst.h"
#include "Syslog.h"

#define READ_TIMEOUT 1000   // milliseconds
#define MAX_RECORD_BYTES 2000
#define EXPECTED_RECORD_LENGTH 243  // 40 values @ 6 chars each, plus CRLF, plus '*'
#define EXPECTED_NVALS  40  // expected number of items in a data record
#define RECORD_TERMINATOR "}"

////////////////////////////////////////////////////////////////
// Lisst ctor
////////////////////////////////////////////////////////////////
Lisst::Lisst(SerialDevice *serialDevice, Boolean verbose)
     : _verbose(verbose),
       SerialDeviceDriver("Lisst", serialDevice, MAX_RECORD_BYTES, RECORD_TERMINATOR,
			  READ_TIMEOUT, 3)
{
  _log    = new LisstLog(this, DataLog::BinaryFormat);
}


////////////////////////////////////////////////////////////////
// Lisst dtor
////////////////////////////////////////////////////////////////
Lisst::~Lisst()
{
  stop();
  delete _log;
}


////////////////////////////////////////////////////////////////
// Lisst initialization function - prepare device for data
// collection
////////////////////////////////////////////////////////////////
DeviceIF::Status Lisst::initialize()
{
  Boolean debug = _verbose;

  char cmd[200];
  char response[300];
  int  nbytes = 0;

  int value = 3;
//  setsockopt(_device->getFd(), SOL_SOCKET, SO_RCVLOWAT, &value, sizeof(int));
  // Send a CRLF to acquire the prompt
  //
  sprintf(cmd, "\xd\xa");
  if (_device->write(cmd, strlen(cmd)) != strlen(cmd))
  {
    return DeviceIF::Error;
  }

  // Flush the echo and prompt
  //
  delay(1000);
  flushReadBuffer((unsigned char*)response, 299, &nbytes); response[nbytes]='\0';
  dprintf("%s read after \\n", response);

  // Reset the unit's date and time
  //
  if (setTime(time((long*)0)) != DeviceIF::Ok)
  {
    return DeviceIF::Error;
  }

  // Start the flow of data
  //
  return start();

}

///////////////////////////////////////////////////////////////
// flushReadBuffer - reads whatever is in the read buffer and
// returns it in record buffer
///////////////////////////////////////////////////////////////
DeviceIF::Status Lisst::flushReadBuffer(unsigned char *record,
					int maxRecordBytes,
					int *nBytesRead)
{
  Boolean debug = _verbose;
  *nBytesRead = 0;

  try 
  {
    //
    // Read whatever data is in the read buffer
    //
    *nBytesRead = _device->readNChars((char *)record,
				      min(maxRecordBytes,_device->nRecvdBytes()), 1000);
  }
  catch (SerialDevice::TimedOut) {
    *nBytesRead = 0;
    dprintf("%s::readRecord() - serial device timed out", name());
    Syslog::write("%s::flushReadRecord() - record so far: %s", name(), record);
  }
  catch (SerialDevice::BufferFull) {
    *nBytesRead = maxRecordBytes;
    Syslog::write("%s::readRecord() - serial device buffer full", name());
  }
  catch (Exception e) {
    *nBytesRead = 0;
    Syslog::write("%s::readRecord() - driver caught exception:%s",
		  name(), e.msg);
    throw;
  }

  return DeviceIF::Ok;
}

////////////////////////////////////////////////////////////////////////////////
// readRecord() - read data from the device. Return Ok if a valid record was
//                acquired, otherwise Error.
////////////////////////////////////////////////////////////////////////////////
//
DeviceIF::Status Lisst::readRecord(unsigned char *record,
					   int maxRecordBytes,
					   const char *recordTerminator,
					   unsigned readTimeout,
					   int *nBytesRead)
{
  Boolean debug = _verbose;
  Boolean readError = False;
  int nbytes;
  *nBytesRead = 0;

  try 
  { 
    memset(record, 0, maxRecordBytes);
    //look for first curly brace
    do {
	nbytes = read(_device->getFd(),
			  (char *)(record), 100);
    } while ((nbytes > 0) && !memchr(record, '{', maxRecordBytes));
    if (nbytes < 0) return DeviceIF::Error;
    printf("got first curly brace\n");

    //now read rest of record, terminated by a closing curly brace
    *nBytesRead = nbytes;
    do {
    	nbytes = read(_device->getFd(),
			  (char *)(record + *nBytesRead),
		          (int)(maxRecordBytes - *nBytesRead));
    	if (nbytes > 0) *nBytesRead += nbytes;
	if (*nBytesRead >= maxRecordBytes) return DeviceIF::Error;
    } while (!memchr((char *)(record),'}', maxRecordBytes)); 
  }
  catch (SerialDevice::TimedOut) {
    printf("nbytes is %d\n", *nBytesRead);
    readError = True;
    dprintf("%s::readRecord() - serial device timed out", name());
    Syslog::write("%s::readRecord() - record so far: %s", name(), record);
    if (!strncmp((const char *)record, "OK", 2)) {
	Lisst::initialize();
	Syslog::write("%s::readRecord() - resetting lisst", name());
    } 
  }
  catch (SerialDevice::BufferFull) {
    readError = True;
    Syslog::write("%s::readRecord() - serial device buffer full", name());
  }
  catch (Exception e) {
    readError = True;
    Syslog::write("%s::readRecord() - driver caught exception:%s",
		  name(), e.msg);
    throw;
  }

  dprintf("%s::readRecord(): %d bytes => %s\n", name(), *nBytesRead, record);

  if (readError)
  {
    return DeviceIF::Error;
  }
  else
  {
    return DeviceIF::Ok;
  }

}

////////////////////////////////////////////////////////////////////////////////
// processRecord() - parse the data record into member variables
//
////////////////////////////////////////////////////////////////////////////////
//
DeviceIF::Status Lisst::processRecord(unsigned char *record, int nBytes)
{
  Boolean debug = _verbose;

  // Format string for scanf - a bunch of integers
  //
  char *recFormat =
    "{ %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d }";

  for (int i = 0; i < nBytes; i++) {
	if (record[i] == 0xd) record[i] = 0x20;
	if (record[i] == 0xa) record[i] = 0x20;
	if (record[i] == 0x0) record[i] = 0x20;
  }

  dprintf("Lisst 100: %s\n", record);

  char *recordHead = (char *)memchr(record,'{', nBytes);
  if (!recordHead) {
	printf("unable to find LISST record head\n");
	return DeviceIF::Error;
  }

  // Read the values from the string and log them
  //
  int nvals = sscanf((const char*)recordHead, recFormat, // record+1 because '*' not always present
		     &_det1,  &_det2,  &_det3,  &_det4,  &_det5,  &_det6,  &_det7,  &_det8,
		     &_det9,  &_det10, &_det11, &_det12, &_det13, &_det14, &_det15, &_det16,
		     &_det17, &_det18, &_det19, &_det20, &_det21, &_det22, &_det23, &_det24,
		     &_det25, &_det26, &_det27, &_det28, &_det29, &_det30, &_det31, &_det32,
		     &_laserTrans, &_volt, &_aux1, &_laserRef, &_pressure, &_temp,
		     &_time1, &_time2);

  if (nvals < EXPECTED_NVALS)
  {
    Syslog::write("Lisst processRecord(): parsing error - only read %d values", nvals);
    return DeviceIF::Error;
  }
  else
  {
    _log->write();
    return DeviceIF::Ok;
  }

}

////////////////////////////////////////////////////////////////////////////////
// Tell the LISST to stop processing
//
DeviceIF::Status Lisst::stop()
{
  DeviceIF::Status returnStat = DeviceIF::Ok;
  char reply[2000];
  int nbytes;

  Syslog::write("Lisst:stopping Lisst...");

  // Send a ctrl-c, then RUN to reset  
  char cmd = 0x03;

  do {
    memset(reply, 0x0, 2000);
    _device->write(&cmd, 1);
    nbytes = read(_device->getFd(), reply, 2000);
  } while (nbytes && !memchr(reply, '>', nbytes));
 
  delay(500);

  return returnStat;
}
 
///////////////////////////////////////////////////////////////////////////////
//
// This routine commands the unit to start processing
//
///////////////////////////////////////////////////////////////////////////////
//
DeviceIF::Status Lisst::start()
{
  Boolean debug = _verbose;

  Syslog::write("Lisst:starting Lisst...");

  char cmd[100];
  char response[300];
  int nbytes = 0;

  // Select option "1" - start collecting data
  //
  delay(500);

  flushReadBuffer((unsigned char*)response, 299, &nbytes); response[nbytes]='\0';
  dprintf("%s read before 1", response);

  sprintf(cmd, "GO\xd\xa");
  if (_device->write(cmd, strlen(cmd)) == strlen(cmd))
  {
    Syslog::write("Lisst:started");

    delay(500);
    flushReadBuffer((unsigned char*)response, 299, &nbytes); response[nbytes]='\0';
    dprintf("%s read after 1", response);

    return DeviceIF::Ok;
  }
  else
  {
    Syslog::write("Lisst:start failure");
    return DeviceIF::Error;
  }
}

////////////////////////////////////////////////////////////////////////////////
//
// This routine commands the unit to reset it's internal clock to a given
// time. The input is the number of seconds since 1970/01/01 00:00:00.
//
////////////////////////////////////////////////////////////////////////////////
//
DeviceIF::Status Lisst::setTime(unsigned long t1970)
{
  Boolean debug = _verbose;

  // Use the DATE command to reset the unit's date/time to match the MVC's
  //
  char cmd[100];
  char response[300];
  int nbytes = 0;

  // Delays are used to sync with device
  //
  delay(500);
  flushReadBuffer((unsigned char*)response, 299, &nbytes); response[nbytes]='\0';
  dprintf("%s read after \\n", response);

  // Get the localtime
  //
  struct tm *hytime = localtime((time_t*)&t1970);
  sprintf(cmd, "SC %02d/%02d/%2d %02d:%02d:%02d\xd\xa",
	  hytime->tm_mon+1, hytime->tm_mday, hytime->tm_year+1900-2000,
	  hytime->tm_hour,  hytime->tm_min,  hytime->tm_sec);
  dprintf("Setting LISST time to %s", cmd);

  if (_device->write(cmd, strlen(cmd)) != strlen(cmd))
  {
    return DeviceIF::Error;
  }

  delay(50);

  flushReadBuffer((unsigned char*)response, 299, &nbytes); response[nbytes]='\0';

  return DeviceIF::Ok;

}

