/****************************************************************************/
/* Copyright (c) 2001 MBARI                                                 */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  :                                                               */
/* Filename : Isus.cc                                                       */
/* Author   :                                                               */
/* Project  :                                                               */
/* Version  : 1.0                                                           */
/* Created  : 06/01/2001                                                    */
/* 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 "Isus.h"
#include "Syslog.h"

#define READ_TIMEOUT 2000

Isus::Isus(SerialDevice *serialDevice, Boolean lampOn, Boolean verbose)
     : _verbose(verbose), _lampOn(lampOn),
       SerialDeviceDriver("Isus", serialDevice, MaxRecordBytes, "\xa",
			  READ_TIMEOUT, 3)
{
  _output = new IsusOutput(SharedData::ReadWrite);
  _log    = new IsusLog(this, DataLog::BinaryFormat);
  _nitrate = 0.;
  _temp    = 0.;
  _quality = 0.;
  _nBytes = 0;
}


Isus::~Isus()
{
  stop();
  delete _output;
  delete _log;
}


DeviceIF::Status Isus::initialize()
{
  Boolean debug = _verbose;

  _device->flushReceiveBuf();
  _device->clearPort();

  if (_lampOn)
    Syslog::write("Isus:initializing Isus with lamp on...");
  else
    Syslog::write("Isus:initializing Isus with lamp off...");

  char cmd[200];
  sprintf(cmd, "W\n");

  // Tell unit to wakeup. If accepted, it's good to go.
  //
  try {
  if (_device->write(cmd, strlen(cmd)) == strlen(cmd))
  {
    // Wait a short amount of time and then set the time of the
    // Isus unit to match the MVC time. Isus uses 1970 as the
    // beginning of time, as does qnx.
    //
    delay(1000);
    try {
      _device->readUntil(cmd, 200, "\n", 2000);
    }
    catch(...) {};
    dprintf("Isus:init():Wakeup - response:%s", cmd);

    sprintf(cmd, "W\n");
    if (_device->write(cmd, strlen(cmd)) == strlen(cmd))
    {
      try {
      _device->readUntil(cmd, 200, "\n", 2000);
      }
      catch(...){};
      dprintf("Isus:init():Wakeup - response:%s", cmd);

      sleep(1);
      long t1970 = time((long*)0);
      if (setTime(t1970) == DeviceIF::Ok)
      {
	// Wait another small amount of time and start processing
	//
	try {
	_device->readUntil(cmd, 200, "\n", 2000);
        }
        catch(...) {};
	dprintf("Isus:init():Set time response:%s", cmd);

	Syslog::write("Isus:comms initialized, warmup countdown begun...");
	_output->data._deviceReady = True;
	writeOutput("initialize()");

	start();

        // Now wait for device to warm-up, then start acquring data
        //
        sleep(35);
        _device->flushReceiveBuf();
	Syslog::write("Isus:20 seconds of warmup remaining...");
        sleep(22);
        _device->flushReceiveBuf();
        return DeviceIF::Ok;

      }
      else
	Syslog::write("Isus:initialize(): time reset failure");
    }
  }
  }
  catch (Exception e)
  {
    Syslog::write("Isus::initialize() Exception: %s", e.msg);
  }

  stop();
  _output->data._deviceReady = False;
  writeOutput("initialize()");
  return DeviceIF::Error;
}
 
////////////////////////////////////////////////////////////////////////////////
//
//
////////////////////////////////////////////////////////////////////////////////
//
DeviceIF::Status Isus::readRecord(unsigned char *record,
					   int maxRecordBytes,
					   const char *recordTerminator,
					   unsigned readTimeout,
					   int *nBytesRead)
{
  Boolean debug = False;
  Boolean readError = False;
  Boolean noRecord = True;
  char* eor = NULL;

  // Keep track of time spent in this method 
  //
  long t0, t1;
  t0 = t1 = Time::milliseconds();

  // Set up for select() call
  //
  fd_set readFds;
  struct timeval timeout_tv;
  FD_ZERO(&readFds);
  FD_SET(_device->getFd(), &readFds);
  _nBytes = 0;

  // Main loop
  //
  // Keep recv()ing until a complete record is read or
  // until my time has expired.
  //
  while (noRecord)
  {
    // Convert from milliseconds to timeval format
    timeout_tv.tv_sec  = (readTimeout-(t1-t0)) / 1000;
    timeout_tv.tv_usec = ((readTimeout-(t1-t0)) % 1000) * 1000;
    dprintf("Waiting on select() for %d ms\n", readTimeout);

    // Wait until data is available or my time is up
    //
    switch (select(_device->getFd() + 1, &readFds, 0, 0, &timeout_tv)) {
    case -1:
      Syslog::write("%s::select() failed\n", name());
      readError = True;
      break;

    case 0:
      // My time has expired without a complete record
      //
      Syslog::write("%s::select() timeout\n", name());
      break;

    default:
      // Data was recv()ed from the device
      //
      _nBytes += recv(_device->getFd(), (char*)(_recordBuf+_nBytes),
		      (maxRecordBytes - _nBytes - 1), 0);

      // Look for end-of-record marker in the buffer
      //
      _recordBuf[_nBytes] = '\0';
      dprintf("Isus::readRecord() - recv()ed %d bytes %s\n",
	      _nBytes, _recordBuf);
      if (eor = strstr((char*)_recordBuf, recordTerminator))
      {
	// Got a complete record
	//
	noRecord = False;
	readError = False;
	long readTime = Time::milliseconds() - t0;
      }
      else if (_nBytes == (maxRecordBytes-1))
      {
	// Not a record yet, make sure we didn't reach the end
	// of the buffer. If we have, then nothing to do except
	// start recv()ing at the beginning of the buffer again.
	//
	Syslog::write("Isus::Buffer full!");
	readError = True;
	_nBytes = 0;
      }
      break;
    }

    // Time is up, break out of the while loop
    //    
    t1 = Time::milliseconds();
    if (noRecord && (t0 + readTimeout) <= t1)
    {
      readError = True;
      break;
    }

    // Continue recv()ing, but update the remaining time
    //
    readTimeout -= (t1 - t0);
  }

  // No record yet and were done. Return Error so that processRecord
  // isn't called with a bad record.
  //
  if (readError)
  {
    dprintf("No record yet\n");
    return DeviceIF::Error;
  }
  else
  {
    // Record terminator reached, so copy _nBytes from recordBuf
    // to record, report the number of bytes read, and return.
    //
    //    *nBytesRead = (eor-_recordBuf+strlen(recordTerminator));
    //    memcpy(record, _recordBuf, *nBytesRead);
    dprintf("Isus::readRecord() - recv()ed %d bytes %s\n",
	    _nBytes, _recordBuf);
    *nBytesRead = _nBytes;
    memcpy(record, _recordBuf, _nBytes);
    record[*nBytesRead] = '\0';
    dprintf("Isus::readRecord(): got record %s\n %s\n from %d total bytes\n",
	     record, _recordBuf,  _nBytes);

    // Copy any remaining bytes in the recordBuf from the "end" to
    // the start so we don't lose them.
    //
    /*
    if (_nBytes > *nBytesRead)
    {
      _nBytes = _nBytes - *nBytesRead;
      memcpy(_recordBuf, _recordBuf+*nBytesRead, _nBytes);
      dprintf("Isus::readRecord: copied %d bytes to start of recordBuf",
	       _nBytes);
    } else {
      _nBytes = 0;
    }
    */
    _nBytes = 0;
    return DeviceIF::Ok;
  }
}

DeviceIF::Status Isus::processRecord(unsigned char *record, int nBytes)
{
  Boolean debug = _verbose;

  if (nBytes <= 0)
    return DeviceIF::Ok;

  _output->data._badComms = False;    // Assume comms will go OK


  // What kind of message is this?
  //
  char msgtype = record[0];
  char *nptr = NULL;
  switch(msgtype)
  {
  case 'A': // ACK  - acknowledging a command
    break;

  case 'N': // NACK - some kind of problem with the Isus
    Syslog::write("Isus: processRecord() - %s", record+5);
    _output->data._badComms = True;
    break;

  default:

    // Find the measurements in the record
    //
    // Skip the first part (date/time)
    //
    char *rec = strdup((char *)record);

    nptr = strtok(rec, ",");
    if (nptr == NULL)
    {
      Syslog::write("Isus: processRecord() - bad date/time: %s", record);
      break;
    }

    // Read temperature
    //
    nptr = strtok(NULL, ",");
    if (nptr == NULL)
    {
      Syslog::write("Isus: processRecord() - bad temperature data: %s", record);
      break;
    }

    int nitems;
    double temp;
    nitems = sscanf(nptr, "%lf", &temp);
    if (nitems == 1)
    {
      dprintf("Isus:processRecord(): Temp value=%lf", temp);
      _temp = temp;
    }

    // Read nitrate
    //
    nptr = strtok(NULL, ",");
    if (nptr == NULL)
    {
      Syslog::write("Isus: processRecord() - bad nitrate data: %s", record);
      break;
    }

    double nitrate;
    nitems = sscanf(nptr, "%lf", &nitrate);
    if (nitems == 1)
    {
      dprintf("Isus:processRecord(): Nitrate value=%lf", nitrate);
      _nitrate = nitrate;
    }

    // Read quality
    //
    nptr = strtok(NULL, ",");
    if (nptr == NULL)
    {
      Syslog::write("Isus: processRecord() - bad quality data: %s", record);
      break;
    }

    double quality;
    nitems = sscanf(nptr, "%lf", &quality);
    if (nitems == 1)
    {
      dprintf("Isus:processRecord(): Quality value=%lf", quality);
      _quality = quality;
    }

    free (rec);
    break;

  }

  try
  {
    writeOutput("processRecord()");
  }
  catch (SharedData::AccessError error)
  {
    fprintf(stderr, "Isus::processRecord(): SharedData::write() error");
    exit(1);
  }

  _log->write();

  return DeviceIF::Ok;

}

////////////////////////////////////////////////////////////////////////////////
//
//
DeviceIF::Status Isus::stop()
{
  Syslog::write("Isus:stopping Isus...");

  _device->flushReceiveBuf();
  
  char cmd[100];
  sprintf(cmd, "S\n");
  if (_device->write(cmd, strlen(cmd)) == strlen(cmd))
  {
    delay(200);
    _device->flushReceiveBuf();
    try {
    _device->readUntil(cmd, 100, "\n", 2000);
    }
    catch(...){};
    Syslog::write("Isus:stopped - response %s", cmd);
    return DeviceIF::Ok;
  }
  else
  {
    Syslog::write("Isus:stop failure");
    return DeviceIF::Error;
  }
}
 
///////////////////////////////////////////////////////////////////////////////
//
// This routine commands the Isus unit to start processing using the stored
// phase setup.
//
///////////////////////////////////////////////////////////////////////////////
//
DeviceIF::Status Isus::start()
{
  Syslog::write("Isus:starting Isus...");

  char cmd[100];

  if (_lampOn)
    sprintf(cmd, "D\n");
  else
    sprintf(cmd, "R\n");

  if (_device->write(cmd, strlen(cmd)) == strlen(cmd))
  {
    try {
    _device->readUntil(cmd, 100, "\n", 2000);
    }
    catch(...){};
    Syslog::write("Isus:started");
    return DeviceIF::Ok;
  }
  else
  {
    Syslog::write("Isus:start failure");
    return DeviceIF::Error;
  }
}

void Isus::writeOutput(char *msg)
{
  try
  {
    _output->write();
  }
  catch (SharedData::AccessError error)
  {
    fprintf(stderr, "Isus::%s: SharedData::write() error", msg);
    exit(1);
  }
}

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

  // Send a "T" command and then wait for the device to
  // ready itself for the arguments.
  //
  char cmd[100];
  sprintf(cmd, "T");

  if (_device->write(cmd, strlen(cmd)) == strlen(cmd))
  {
    sleep(1);

    // Now send the command arguments one character at a time
    // with a small delay in between each.
    //
    sprintf(cmd, ",%ld\n", t1970);
    int i, cmdlen = strlen(cmd);
    for (i = 0; i < cmdlen; i++)
    {
      dprintf("%c", cmd[i]);
      if (_device->write(cmd+i, 1) != 1)
      {
	// Something bad happened, abort
	//
	break;
      }
      delay (100);
    }

    // Did we complete? Great. Return an Ok.
    //
    if (i == cmdlen)
    {
      Syslog::write("Isus:time set with val %ld -> %s", 
		    (long)t1970, asctime(localtime((long*)&t1970)));
      return DeviceIF::Ok;
    }
  }

  // Something failed along the way.
  //
  Syslog::write("Isus:time reset failure (%s)", cmd);
  return DeviceIF::Error;

}
