/****************************************************************************/
/* Copyright (c) 2000 MBARI                                                 */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  :                                                               */
/* Filename : Parosci.cc                                                    */
/* Author   :                                                               */
/* Project  :                                                               */
/* Version  : 1.0                                                           */
/* Created  : 02/07/2000                                                    */
/* Modified :                                                               */
/* Archived :                                                               */
/****************************************************************************/
/* Modification History:                                                    */
/****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <i86.h>
#include <fstream.h>
#include <sys/dev.h>
#include <termios.h>
#include "Parosci.h"
#include "Syslog.h"
#include "AttributeParser.h"
#include "IntegerAttribute.h"
#include "WorkSiteIF.h"
#include "SerialDevice.h"

#define PARO_MODE_ATTEMPTS 3

static short parosciPeriod = 10;

static char *INT_RESET_CONFIRM_STRING ="*0001PR=00024";
static char *UNIT_CONFIRM_STRING ="0100UN\xd\xa";
static char *UNIT_CONFIRM_RESPONSE_STRING ="*0001UN=";

static char *UNIT_RESET_STRING ="0100EW*0100UN=1\xd\xa";
static char *UNIT_RESET_RESPONSE ="*0001UN=1";
static char *FIXED_LENGTH_OUTPUT = "0100EW*0100DL=1\xd\xa";
#define REPLY_STR_LEN (21)

static char *MODE_CONFIRM_STRING ="0100MD\xd\xa";
static char *MODE_CONFIRM_RESPONSE ="*0001MD=0";

static char *SANITY_CHECK_STRING ="0100P3\xd\xa";

static char *GET_PRESSURE_STRING ="0100P1\xd\xa";
static char *READ_PRESSURE_STRING ="*0100DB\xd\xa";
static char *STOP_PRESSURE_STRING = "*0100P1\xd\xa";
static char *GET_TEMP_PERIOD ="0100Q1\xd\xa";

static char *prefix ="0100";

typedef enum{ TC, C1, C2, C3, D1, D2, T1, T2, T3, T4, T5, 
		U0, Y1, Y2, Y3
		} parocoeff;

static const char *Parosci::coeffNameList[] = {
  "TC", "C1", "C2", "C3", "D1", "D2","T1","T2","T3","T4","T5",
  "U0","Y1","Y2","Y3"
};

static const double Parosci::kPsiToBar  = 0.0689476;
static const double Parosci::kBarToPsi  = 14.503774;

void timeoutHandler(int signo)
{
}

//== The members =================================================

Parosci::Parosci(SerialDevice *device, int baro_corr_flag,
		 char *resonhost)
  : Task("Parosci"),_attributes("Parosci"), _resonhost(NULL)
{
  _device = device;
  _output = new ParosciOutput(SharedData::ReadWrite);
  _output->data.deviceStatus = DeviceIF::Initializing;
  _output->write();
  _baro_corr_flag = baro_corr_flag;
  if (resonhost) _resonhost = strdup(resonhost);
  if (resonhost) Syslog::write("Parosci::reson host is %s", _resonhost);
  if (!resonhost) Syslog::write("Parosci::reson host is NULL");
  initialize();
}

Parosci::~Parosci()
{
  delete _output;
  delete _log;
  if (_resonhost) free(_resonhost);
  if (_edgetechIF) delete _edgetechIF;
  if (_reson7K) delete _reson7K;
  if (timer_id > 0) timer_delete(timer_id);
}

void Parosci::startupPressurePeriod()
{
  writeCommand(GET_PRESSURE_STRING);
}

DeviceIF::Status Parosci::initialize()
{
  char command[100];
  int val,i,serialNo;
  double pres_0;
  Boolean debug = True;

  // == Initialize class variables ================================
  site_latitude = 0.0;
  pressure=0.0; depth=0.0; 
  pres_period = -1.0; temp_period=-1.0;

  // == Configure the site latitude ===============================
  try {
    WorkSiteIF worksite("worksite");
    site_latitude = worksite.latitude();
    start_depth = worksite.getStartDepth();
  } 
  catch(SharedObjectClient::MissingServer e) {
    site_latitude = 45.0;
    Syslog::write("Parosci.cc -- Worksite server is not running. "
		  "Using default lat of %lf", site_latitude );
  }
  catch(...) {
    Syslog::write("Parosci.cc  failed on instantiation of WorkSiteIF");
  }

  // try to set up interface to edgetech FSDW
  try {
  _edgetechIF = new EdgetechFSDWIF("edgetechFSDW",5);
   Syslog::write("Parosci:: edgetechIF is 0x%x\n",_edgetechIF);
  }
  catch (...) {
	  Syslog::write("%s: unable to open edgetechFSDW interface\n", name());
      _edgetechIF = NULL;
  }


  _reson7K = NULL;
  if (_resonhost) {
  	try {
   		Syslog::write("parosci:: reson host is %s", _resonhost);
   		_reson7K = new Reson7K(_resonhost);
   		Syslog::write("parosci:: reson interface successfully created");
  	}
  	catch(...) {
    		Syslog::write("parosci:: reson interface not opened");
    		_reson7K = NULL;
  	}
   }

  // == Read pressure resolution and temperature interval from config file
  //

  _attributes.add(new IntegerAttribute("PR", "Pressure Resolution",
				       &_pr, INT_TIME));
  _attributes.add(new IntegerAttribute("TempInterval",
		  "Take a temp period after this many pressure periods",
				       &_tempInt, 10));

  const char *configFileName = System::configurationFile("parosci.cfg");
  AttributeParser::parse(configFileName, &_attributes);

  dprintf("Parosci.cc -- parsed PR (%d) and TempInterval (%d)",
	  _pr, _tempInt);

  // Create a data file to record data about the instrument
  //
  const char* pdatfile = System::configurationFile("parosci.dat");
  ofstream pdat(pdatfile, ios::trunc);

  long now;
  time(&now);
  pdat << "# " << ctime(&now) << "#" << endl;
  pdat << "# Paroscientific Data File" << endl;
  pdat << "# ========================" << endl;
  
  // == Configure the ParosciOutput ===============================

  _output->data.depth = 0.00;
  _output->data.pressure = 0.00;
  _output->data.temp = 0.00;
  _output->data.updateTime.tv_sec = 0.0;
  _output->data.deviceStatus = DeviceIF::Ok;

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


  // == Configure the periodic callback ===========================
  //addPeriodicCallback( parosciPeriod, 
  //	       (CallbackMethod)Parosci::periodicParosciCallback );

  //== Set up logger ==============================================

  _log = new ParosciLog( this, DataLog::BinaryFormat );


  //== Initialize the serial port =================================

  _device->commsDebugMode(SerialDevice::DebugOff);
  //_device->commsDebugMode(SerialDevice::DebugOn);

  _device->setLineFormat(BAUD, DATA_BITS, STOP_BITS, PARITY);
  _device->nonRaw();
  writeCommand("0100EW*0100MD=0\xd\xa"); sleep(1);
  _device->clearPort();
  _device->flushReceiveBuf();

  //== Get serial no  ===============================================
  //
  writeCommand("0100SN\xd\xa");
  if (_device->readNChars(reply, REPLY_LEN, PARO_TIMEOUT) != -1)
    //     if (_device->read(reply, REPLY_LEN, PARO_TIMEOUT) != -1)
  {
    if (sscanf(reply, "%*7s=%d",&serialNo) !=1)
      //error
    {
      Syslog::write("Parosci.cc -- Failed to read serial number");
      throw Exception("Failed to read serial numbre");
    }
  }
  else
  {
    Syslog::write("Parosci.cc -- Failed to read serial number");
    throw Exception("Failed to read serial numbre");
  }
  dprintf("Parosci.cc -- Serial # is %d", serialNo);
  pdat << "# Serial Number = " << serialNo << endl;
  pdat << "#" << endl;
  pdat << "# Setup Information" << endl;
  pdat << "# -----------------" << endl;

  //== Put Parosci into proper mode ===================================
  //  Try more than once - this has failed fairly regularly in
  //  the past.

  // Record the mode in the data file
  pdat << "#\tMode: MD = 0" << endl;   // Setting MD=0

  int mi;
  for (mi = 0; mi < PARO_MODE_ATTEMPTS; mi++)
  {
    writeCommand("0100EW*0100MD=0\xd\xa");
    if( _device->confirm("*0001MD=0\xd",PARO_TIMEOUT) == -1)
    {
        Syslog::write("Parosci.cc -- Failed to set mode");
        continue;  // error try again
    }
    else
    {
      // If it works once, break out of loop
      //
      dprintf("Parosci::init() - mode set okay");
      break;
    }
  }

  if (mi == 3)
  {
    //error
    throw Exception("Parosci.cc -- Failed to set mode");
  }

  // If we failed every time, there really is a problem
  //
  if (mi >= PARO_MODE_ATTEMPTS)
  {
    //error
    throw Exception("Parosci.cc -- Failed to set mode");
  }


  //== Reduce integration times ==================================
  //
  // First, find out what the current setting is
  //
  writeCommand("0100PI\xd\xa");
  if (_device->confirm("*0001PI=",PARO_TIMEOUT) == -1)
  {
    //error
    Syslog::write("Parosci.cc -- Failed to read integration times");
    throw Exception("Failed to read integration times");
  }
  
  if (_device->readUntil(reply, REPLY_LEN, "\xa", PARO_TIMEOUT) != -1)
  {
    if ( sscanf(reply,"%d",&val) !=1)
    {
      //error
      Syslog::write("Parosci.cc -- Failed to read integration times");
      throw Exception("Failed to read integration times");
    }
    dprintf("Parosci.cc -- PI reply = %d\n", val);

    // If the current setting differs from that in the config file,
    // change it
    //
    if (val != _pr)
    {
      Syslog::write("Parosci.cc -- Current integration time = %d",
		    val );
      Syslog::write("Parosci.cc --      setting to new time = %d",
		    _pr);

      //reset integration time
      sprintf(command, "0100EW*0100PI=%d\xd\xa", _pr);
      writeCommand(command);
      delay(50);

      // Make sure device got PR command
      //
      //#if 0
      sprintf(command, "*0001PI=");
      dprintf("Parosci.cc -- Confirming %s", command);
      if (_device->confirm(command, PARO_TIMEOUT) == -1)
      {
	//error
	Syslog::write("Parosci.cc -- Failed to set new integration times");
	throw Exception("Failed to set new integration times");
      }
      //#endif
	  
    }
  }
  // Record the pressure resolution in the data file
  pdat << "#\tPressure Interval: PI = " << _pr << endl;
  pdat << "#\tTemp Period every " << _tempInt << " Pressure Periods" << endl;

  //== Set the units on comp. pressure are in bar ==================
  //
  writeCommand(UNIT_CONFIRM_STRING);
  if (_device->confirm(UNIT_CONFIRM_RESPONSE_STRING, PARO_TIMEOUT) == -1)
  {
    //error
    Syslog::write("Parosci.cc -- Failed to confirm units");
    throw Exception("Failed to confirm units");
  }
  
  //     if (_device->read(reply, REPLY_LEN, PARO_TIMEOUT) != -1)
  if (_device->readUntil(reply, REPLY_LEN, "\xa", PARO_TIMEOUT) != -1)
  {
    if (sscanf(reply, "%d",&val) !=1)
    {
      Syslog::write("Parosci.cc -- Failed to confirm units");
      throw Exception("Failed to confirm units");
    }
    dprintf("Parosci.cc -- units = %d", val);

    // Set units to PSI if not that already
    //
    if (val != UNITS_VAL)
    {
      Syslog::write("Parosci.cc -- Current output units = %d",
		    val );
      Syslog::write("Parosci.cc --    Setting new units = %d",
		    UNITS_VAL );
      //reset units
      writeCommand(UNIT_RESET_STRING);
      if (_device->confirm(UNIT_RESET_RESPONSE, PARO_TIMEOUT) == -1)
      {
	//error
	Syslog::write("Parosci.cc -- Failed to set new units");
	throw Exception("Failed to set new units");
      }
    }
  }
  // Record the units code in the data file
  pdat << "#\tPressure Units (PSI): UN = " << UNITS_VAL << endl;

  // == Confirm MODE is 0 ====================================
  //
  writeCommand(MODE_CONFIRM_STRING);
  if (_device->confirm(MODE_CONFIRM_RESPONSE, PARO_TIMEOUT) == -1)
  {
    //error
    Syslog::write("Parosci.cc -- Failed to confirm mode");
    throw Exception("Failed to confirm mode");
  }

  // == Retrieve conversion coefficients ======================
  //
  delay(500);
     
  _device->clearPort();

  //  strcpy(msg_buf,"\t")
  pdat << "#" << endl;
  pdat << "# Device Coefficients" << endl;
  pdat << "# -------------------" << endl;

  for (i=0 ; i < sizeof(coeffNameList)/sizeof(char *) ; i++)
  {
    sprintf(command, "%s%s\xd\xa",prefix,coeffNameList[i]);
    writeCommand(command);
    delay(10);

    // get the reply
    //
    try {
      if (_device->readUntil(reply, REPLY_LEN, "\xa", 5000) == -1 ||
	  sscanf(reply,"%*7s=%lf",&pc[i]) !=1)
      {
	//error
	Syslog::write("Parosci.cc -- Failed to read coefficient %d (%d): %02x",
		      i, strlen(reply), reply[0] );
      }
    }
    catch(...)
    {
      Syslog::write("Parosci.cc -- could not get coefficient");
    }
    pdat << "#\t" << coeffNameList[i] << " = " << pc[i] << endl;
  }

  delay(100);
     
  // == Test the setup ======================================
  //
  // sanity check: ask instrument for compensated pressure, then get
  // raw pres & temp periods and calculate compensated pressure
  // using above coeffs. Then restart the pressure readings

  writeCommand(SANITY_CHECK_STRING);
  if (_device->confirm("*0001",PARO_TIMEOUT) != -1
      && _device->read(reply, REPLY_LEN, PARO_TIMEOUT) > 0
      && sscanf(reply, "%lf",&pres_0) == 1)
  {
    Syslog::write("Parosci.cc -- Initial pressure = %lf",
		  psiToBar(pres_0) );
  }
  else
  {
    //error
    Syslog::write("Parosci.cc -- Failed to query pressure for "
		  "sanity check: %s",reply);
    throw Exception("Failed to query pressure for sanity check");
  }

  // now get pressure period
  //
  startupPressurePeriod();

  _device->confirm("*0001", 2*PARO_TIMEOUT);
  _device->readUntil(reply, REPLY_LEN, "\xa", PARO_TIMEOUT);
  if (sscanf(reply, "%lf", &pres_period) == 1)
  {
    Syslog::write("Parosci.cc -- Parosci initial pressure period = %lf",
		  pres_period );
  }
  else
  {
    //error
    Syslog::write("Parosci.cc -- Failed to query pressure period"
		  " for sanity check");
    throw Exception("Failed to query pressure period for sanity check");
  }
  //_device->write(STOP_PRESSURE_STRING, strlen(STOP_PRESSURE_STRING));
  //tcdrain(_device->getFd()); delay(10);
  //_device->clearPort();

  // and temperature period (this stops the pressure readouts)
  //
  writeCommand(GET_TEMP_PERIOD);
//  delay(10);
  if (_device->confirm("*0001",2*PARO_TIMEOUT) != -1
      && _device->readUntil(reply, REPLY_LEN, "\xa", PARO_TIMEOUT) > 0
      //	 && _device->read(reply, REPLY_LEN, PARO_TIMEOUT) > 0
      && sscanf(reply,"%lf",&temp_period) == 1)
  {
    Syslog::write("Parosci.cc -- Parosci initial temp period = %lf",
		  temp_period );
  }
  else
  {
    //error
    Syslog::write("Parosci.cc -- Failed to query temperature period for "
		  "sanity check");
    throw Exception("Failed to query temperature period for sanity"
		    " check");
  }
//  startupPressurePeriod();

  // Calculating initial pressure
  //
  init_temp = comp_temp( temp_period );
  if (_baro_corr_flag) {
    init_pres = comp_press_bar(pres_period,temp_period);
  } else {
    init_pres = DEFAULT_BARO_CORRECTION_BAR;
  }

  Syslog::write("initial pressure is %f\n", init_pres);

  writeCommand(FIXED_LENGTH_OUTPUT);
  // Hurray! Paroscientific initialization completed
  //
  Syslog::write("Parosci.cc -- Paroscientific configured.");

  _output->data.deviceStatus = DeviceIF::Ok;
  _output->write();
  System::copyToLogDir(pdatfile);
  remove (pdatfile);

//  struct sigevent event;
//  event.sigev_signo = SIGUSR1;
//  timer_id = timer_create(CLOCK_REALTIME, &event);
//  signal(SIGUSR1, timeoutHandler);

  writeCommand("0100EW*0100MD=2\xd\xa"); //turn on streaming output

  _device->raw();
  _device->blocking();
  return DeviceIF::Ok;
} 

DeviceIF::Status Parosci::processRecord()
{
  try {
    if( getParosci() == OK) 
    {
	  
//      	  Syslog::write("Parosci.cc -- pressure = %lf; temp = %lf; "
//      			"depth = %lf", pressure, _output->data.temp, depth );
	  
      _output->data.pressure = pressure;
      _output->data.depth = depth;
      _output->data.surface_pressure = init_pres;
      _output->data.updateTime.tv_sec = readtime.tv_sec;
      _output->data.updateTime.tv_nsec = readtime.tv_nsec;
      _output->data.deviceStatus = DeviceIF::Ok;

      // -- Log the data -----------------------------------------
      _log->write();

      // -- and now make data available to server ----------------
      _output->write();

      // -- insert data into edgetechSystem, if present
      insertPressureRecordEdgetech();
      insertPressureRecordReson7K();

      return DeviceIF::Ok;
    }
    else 
    {
      //changed to return OK even if there was a read error - hjt
      _output->data.deviceStatus = DeviceIF::Error;
      _output->write();
      return DeviceIF::Error;
    }
  }

  catch(...) {
	Syslog::write("caught getParosci exception");
 	_output->data.deviceStatus = DeviceIF::Error;
	return DeviceIF::Error;
  }

  return DeviceIF::Ok;
}

void Parosci::insertPressureRecordEdgetech() {
	if (_edgetechIF) {
		EdgetechFSDWIF::sPressureRecord etPres;

		etPres.condSM = 0; // NOT SET VALID
		etPres.depthM = depth;
		etPres.pressurePSI = barToPsi(pressure);
		etPres.salPSU = 0; // NOT SET VALID
		etPres.sosMS = 0;  // NOT SET VALID
		etPres.tempC = 0;  // NOT SET VALID

		etPres.flags = (EdgetechFSDWIF::PRESSFLG_DEPTHVALID | EdgetechFSDWIF::PRESSFLG_PRESSVALID);

		_edgetechIF->relayPressureRecord(&etPres);


	}
}

void Parosci::insertPressureRecordReson7K() {
	if (_reson7K) {
	   _reson7K->sendPressureDepth(depth);
        }
}

// Write a command string to the Parosci in two parts.
// First, write a '*', to stop the stream from the device.
// Then write the command string. The command string should
// NOT have a leading '*'
//
void Parosci::writeCommand(const char *cmd)
{
  // Write the leading '*' required of every Parosci command
  //
  _device->clearPort();
  _device->write("*", 1); 
  _device->write(cmd, strlen(cmd));
}

///////////////////////////////////////////////////////////////////////
// Add start_depth to depth calculation for docking missions
//
int Parosci::getParosci(void)
{
  Boolean debug = False;

  int nconv;
  double value;
  static int parosci_count = 0; //number of times this routine is called
  static int temp_cycle = 10;
  static int last_read_was_temp = 0; //read temp every 10 cycles
  struct itimerspec timer;

  static int temp_reject_count=0, pres_reject_count=0;
  char c;
  int i;
  int nbytes, nbytesRead;
  char buf[20];
  pres_period = 0; temp_period = 0;


  memset(reply, 0, REPLY_LEN);
  do {
	nbytes = ::dev_read(_device->getFd(), reply, 1, 1, 0, 50, 0, 0);
	if (nbytes == 0) break;
  } while (reply[0] != '*');
  do {
     nbytesRead = ::dev_read(_device->getFd(), reply+nbytes, 1, 1, 0, 50, 0, 0);
     if (nbytesRead > 0) nbytes += nbytesRead;
     if (nbytesRead < 0) return ERROR;
     if (nbytes > REPLY_LEN) return ERROR;
  } while (reply[nbytes-1] != 0xa);

/* 
  nbytes += ::dev_read(_device->getFd(), reply+1,
		      REPLY_STR_LEN-1, REPLY_STR_LEN-1, 0,
                      50, 0, 0);

  if (nbytes != REPLY_STR_LEN) {
	Syslog::write("Parosci - Error - got %d bytes, expected %d\n",
			nbytes, REPLY_STR_LEN);
	return ERROR;
  }
*/
  reply[nbytes] = 0x0; //put null  terminator in string

  if (strncmp("*0001", reply, 5) != 0) {
	Syslog::write("Parosci: Error - could not parse pressure header %s(%d)\n", reply, nbytes);
	return ERROR;
  }

  if (strncmp(reply+nbytes-6, "bar", 3) != 0) {
	Syslog::write("Parosci::units are wrong %s\n", reply);
	return ERROR;
  }

  dprintf("Parosci.cc --  pressure reply with value is %s", reply);
  if (sscanf(reply+5,"%lf",&pressure) != 1) {
    //read error
    Syslog::write("Parosci.cc -- Failure to parse reply: %s", reply);
    return ERROR;
  }

  _output->data.temp = 0; 
  if (pressure <MAX_PRESSURE && pressure > MIN_PRESSURE) {
    depth = corrected_depth (pressure - init_pres);
    depth += start_depth;  // vehicle mission may start below the surface
  } else {
    //error bad pressure
    Syslog::write("Parosci.cc -- Error:Bad pressure %f bar\n",
			  pressure);
    Syslog::write("Parosci - Error: %f pres period %f temp period\n",
			  pres_period, temp_period);
    Syslog::write("Reply is %s\n", reply);
    return ERROR;
  }
  clock_gettime(CLOCK_REALTIME,&readtime);
  dprintf("Parosci.cc -- %d bytes buffered now", _device->nRecvdBytes());
  return OK;
}


void Parosci::periodicParosciCallback( void )
{
}

void Parosci::run()
{
    while (1) processRecord();
}

//COMP_TEMP: calculates the temperature on the Parosci in degC
// using the algorithm described in appendix G of the Parosci
//Digiquartz Manual
//
double Parosci::comp_temp(double t_per)
{
  double U, Temp;

  U = t_per - pc[U0];

  Temp = (pc[Y1] + (pc[Y2] + pc[Y3]*U)*U)*U;

  //     printf("Given t_per = %lf, U = %lf, temp = %lf\n",
  //	    t_per, U, Temp );
  
  return Temp;

}

//== comp_press_bar =============================================
// comp_press_bar: calculates the temparature compensated pressure in bar
// using the method in Appendix G of the Parosci Digiquartz manual
//
double Parosci::comp_press_bar(double p_per, double t_per)
{
  double U, Temp;
  double Tn, C, D, T0, psi;

  U = t_per - pc[U0];
  C = pc[C1] + (pc[C2] + pc[C3]*U)*U;
  D = pc[D1] + pc[D2]*U;
  T0 = pc[T1] + (pc[T2] + (pc[T3] + (pc[T4] + pc[T5]*U)*U)*U)*U;
  Tn = T0*T0/(p_per*p_per);
  psi = C*(1-Tn) * (1-D*(1-Tn));

  return psiToBar(psi);
}

double Parosci::corrected_depth(double bar)
{
  double corrected, dbar, C, gr;
  static double st2;
  static int first=1;
  if (first) // calc & store sin^2(latitude)
    {
      st2 = sin ( site_latitude);
      st2 *= st2;
      first = 0;
    }
  //convert to dbar
  dbar = 10*bar;
  C = (((-1.8200e-15*dbar + 2.279e-10)*dbar - 2.5212e-05)*dbar + 9.72659)
    * dbar;
  gr = 9.780318*((2.3600e-05*st2 + 5.2788e-3)*st2 + 1) + 1.092e-06*dbar;

  corrected = C/gr;
  return corrected;
}
