#include <string.h>
#include <Xm/Form.h>
#include <Xm/RowColumn.h>
#include <Xm/Text.h>
#include <Xm/TextF.h>
#include <Xm/PushB.h>
#include <Xm/Label.h>
#include "Vk/VkErrorDialog.h"
#include "AdvControl.h"
#include "DmErrno.h"
#include "dopplerDM.h"
#include "PowerSwitchGui.h"
#include "powerDM.h"
#include "ComboBox.h"

#define dprintf if (debug) fprintf
 
#define MAX_PACKET_LEN 256

AdvCommand AdvControl::_commands[] = 
{
  "BAnnn", 
  "BT evaluation amplitude minimum. 1-255 counts",

  "BCnnn",
  "BT correlation magnitude minimum. 0-255 counts",

  "BDnnn",
  "BT delay before reacquire. 0-999 ensembles",

  "BEnnnn",
  "BT error velocity maximum. 1-5000 mm/s",

  "BFnnnnn",
  "BT depth guess. 0=AUTO, or 1-65535 cm",

  "BGnnn",
  "BT percent-good minimum. 1-100%",

  "BKn",
  "BT water-mass reference-layer mode. 0=off, 1=on, 2=lost, 3=no bottom-track",

  "BLmmm,nnnn,ffff",
  "BT water-mass reference-layer parameters (decimeters). min size; near boundary; far boundary",

  "BMn", 
  "BT mode. 4=default, t=default+coherent",

  "BPnnn",
  "BT pings per ensemble. 0-999 pings",

  "BRn",
  "BT resolution. 0=4%, 1=2%, 2=1%",

  "BXnnnn",
  "BT maximum tracking depth. 80-9999 decimeters",

  "BZnnn",
  "BT coherent ambiguity velocity. cms/s radial {NOT FULLY IMPLEMENTED}",

  "CBnnn",
  "Serial port control. baud rate; parity; stop bits",

  "CFnnnnn",
  "Flow control. ensemble cycling; ping cycling; binary; serial; recorder",

  "CG",
  "Go. start timed output pinging",

  "CK",
  "Keep parameters as user defaults",

  "CLn",
  "Leapfrog control. 0=output ens while pinging, 1=output ens before next ping",

  "CPnnn",
  "Transmit power. 0-255 counts",

  "CQnnn",
  "Transmit delay select. 0-127 counts {NOT FULLY IMPLEMENTED}",

  "CRn",
  "Retrieve parameters. 0=user, 1=factory",

  "CS",
  "Start pinging",

  "CTn",
  "Turnkey operation. 0=off, 1=on",

  "CXn",
  "Triggered transmit. 0=off, 1=on",

  "CZ",
  "Power down BBADCP",

  "EA+-nnnnn",
  "Heading alignment (physical). LSD=+/-0.01 deg; -179.99 to 180.00",

  "EB+-nnnnn",
  "Heading bias (electrical/magnetic). LSD=+/-0.01 deg; -179.00 to 180.00",

  "ECnnnn",
  "Speed of sound. 1400-1600 m/s",

  "EDnnnn",
  "Depth of transducer. 0-9999 decimeters",

  "EHnnnn",
  "Heading. LSD=0.01 deg; 0.00 to 359.99",

  "EP+-nnnn",
  "Pitch. LSD=0.01 deg; -20.00 to +20.00",

  "ER+-nnnn",
  "Roll. LSD=0.01 deg; -20.00 to +20.00",

  "ESnn",
  "Salinity. 0 to 40 parts per thousand",

  "ET+-nnnn",
  "Temperature. LSD=0.01 deg C; -5.00 to +40.00",

  "EXnnnnn", 
  "Coordinate transformation. type,type; tilts; 3-beam; bin mapping",

  "EZnnnnnnn",
  "Sensor source. EC; ED; EH; EP; ER; ES; ET;. 0=manual, 1=transducer, 2=synchro",

  "PA",
  "Pre-deployment tests",

  "PCnnn",
  "Built-in tests. 0=help",

  "PDnnn",
  "Data stream select. 0=actual, 1=simulated, 3=DVL1, 4=DVL2, 5=DVL3, 6=DVL4",

  "PInnnnnn",
  "Built-in tests. repeat; cpu; clock; timing; dsp; loop",

  "PM",
  "Distance-measuring facility",

  "PSn",
  "Show system parameters. 0=hard/firmware; 1=fxd ldr; 2=var ldr; 3=xdcr mtrx; 4=ping seq",

  "PTnnn",
  "Built-in tests. 0=help",

  "RAnnn",
  "Show number of deployments recorded",

  "RBnnn",
  "Blank check megabyte nnn of recorder memory. 0=all",

  "RDnnn",
  "Show current deployment selected",

  "RE ErAsE",
  "Erase all recorder memory",

  "RJ+-nnnnn",
  "Number of ensembles to jump. -99999 to +99999; 0=repeat last ensemble",

  "RSnnn,mmm",
  "Show recorder space used (MB), unused recorder space (MB). 999=erasing",

  "RT",
  "Built-in recorder tests (for PCMCIA recorders)",

  "SDa...r",
  "Speed log output select",

  "TEhhmmssff",
  "Time per ensemble. hours:minutes:seconds.hundredths of seconds",

  "TFyymmddhhmmss",
  "Time of first ping. year/month/day, hour:minute:second",
 
  "TPmmssff",
  "Time between ping groups. minutes:seconds.hundredths of seconds",

  "TSyymmddhhmmss",
  "Set real-time clock. year/month/day, hour:minute:second",

  "WAnnn",
  "False target threshold maximum. 0-255 counts; 255 disables",

  "WBn",
  "Bandwidth control. 0=wide, 1=narrow {NOT FULLY IMPLEMENTED}",
  
  "WCnnn",
  "WT low correlation threshold. 0-100%",

  "WDnnn nnn nnn",
  "WT data. velocity; correlation; intensity;%good;status;P0 P1;P2;P3",
  
  "WEnnnn",
  "WT error velocity threshold. 0=5000 mm/s",

  "WFnnnn",
  "WT blank after transmit. 0-9999 cm",

  "WGnnn",
  "WT percent-good minimum. 1-100%",

  "WHnnnnn",
  "WT beam 5 data out. velocity;correlation;intensity;%good;status",

  "WIn",
  "WT clip data past bottom. 0=off, 1=on",

  "WJn",
  "WT receiver gain select. 0=low, 1=high",

  "WLsss,eee",
  "WT water-mass reference-layer (begin cell, end cell). 0=off",

  "WMn",
  "WT profiling mode. 1-5",

  "WNnnn",
  "WT number of depth cells. 1-128",

  "WPnnnnn",
  "WT pings per ensemble. 0-16384",

  "WRnn",
  "WT mode 6 transmit percent",

  "WSnnnn",
  "WT depth cell size. 0-9999 cm",

  "WVnnn",
  "WT mode 1 ambiguity velocity (cm/s radial)",

  "WWnnn",
  "WT mode 1 pings before mode 4 re-acquire",

  "WXnnn",
  "WT mode 4 ambiguity velocity (cm/s radial)",

  "WZnnn",
  "WT mode 5 ambiguity velocity (cm/s radial)",

  NULL,
  NULL,
  FALSE
};


VkMenuDesc AdvControl::_modeMenuDesc[] = 
{
  {ACTION, "groundTrackMode", &AdvControl::modeCallback},
  {ACTION, "waterMassMode", &AdvControl::modeCallback},
  {ACTION, "combinedMode", &AdvControl::modeCallback},
  {END}
};


AdvControl::AdvControl(const char *name, Widget parent)
  : DmGuiObject(name, parent)
{
  _baseWidget = XtVaCreateWidget(name, xmFormWidgetClass, parent, NULL);
  installDestroyHandler();

  Widget mgr =
    XtVaCreateWidget("mgr", xmRowColumnWidgetClass, _baseWidget,
		     XmNtopAttachment, XmATTACH_FORM,
		     XmNleftAttachment, XmATTACH_FORM,
		     XmNorientation, XmHORIZONTAL,
		     NULL);
  

  char itemName[MaxDmNameLen];
  strcpy(itemName, POWER_DM_PREFIX);
  strcat(itemName, DOPPLER_SONAR_DM);
  
  _power = 
    new PowerSwitchGui("power", mgr, itemName, "Power", 100, 50, 500,
		       True);

  _power->show();
  _power->addCallback(PowerSwitchGui::statusChangeCallback, this,
		      (VkCallbackMethod )&AdvControl::powerStateCallback);
  
  _startPinging = 
    XtVaCreateManagedWidget("startPinging", xmPushButtonWidgetClass, mgr,
			    NULL);
  
  XtAddCallback(_startPinging, XmNactivateCallback,
		&AdvControl::startPingCallback, (XtPointer )this);
  
  _stopPinging = 
    XtVaCreateManagedWidget("stopPinging", xmPushButtonWidgetClass, mgr,
			    NULL);

  XtAddCallback(_stopPinging, XmNactivateCallback,
		&AdvControl::stopPingCallback, (XtPointer )this);

  XtManageChild(mgr);

  _commandLabel = 
    XtVaCreateManagedWidget("commandLabel", xmLabelWidgetClass, _baseWidget,
			    XmNleftAttachment, XmATTACH_FORM,
			    XmNtopAttachment, XmATTACH_WIDGET,
			    XmNtopWidget, mgr,
			    NULL);

  
  int nCommands = 0;
  int maxLength = 0;
  
  AdvCommand *advCommand = &_commands[0];
  while (advCommand->mnemonic != NULL)
  {
    nCommands++; 
    if (strlen(advCommand->mnemonic) > maxLength)
      maxLength = strlen(advCommand->mnemonic);
    
    advCommand++;
  }
  
  XmStringTable strings = 
    (XmStringTable )XtMalloc((nCommands + 1) * sizeof(XmString));
  
  if (!strings)
  {
    setError("AdvControl::AdvControl() - XtMalloc() of string table failed");
    return;
  }

  char buf[512];
  
  for (int i = 0; i < nCommands; i++)
  {
    sprintf(buf, "%-15s %s", _commands[i].mnemonic, _commands[i].description);
    strings[i] = XmStringCreateSimple(buf);
  }
 
  _command =  new ComboTextField("command", _baseWidget, &strings, 
				 nCommands); 

  _command->addCallback(ComboTextField::acceptCallback, this,
			(VkCallbackMethod )&AdvControl::writeCommand);

  _command->addCallback(ComboTextField::listSelectCallback, this,
			(VkCallbackMethod )&AdvControl::commandSelectCallback);

  _command->set("");
  
  XtVaSetValues(_command->baseWidget(),
		XmNleftAttachment, XmATTACH_FORM,
		XmNtopAttachment, XmATTACH_WIDGET,
		XmNtopWidget, _commandLabel,
		NULL);

  _command->show();

  Widget label = 
    XtVaCreateManagedWidget("responseLabel", xmLabelWidgetClass, _baseWidget,
			    XmNtopAttachment, XmATTACH_WIDGET,
			    XmNtopWidget, mgr,
			    XmNleftAttachment, XmATTACH_WIDGET,
			    XmNleftWidget, _command->baseWidget(),
			    NULL);  

  Arg args[10];
  int nArgs = 0;
  
  XtSetArg(args[0], XmNrows, MaxStoredResponses);
  XtSetArg(args[1], XmNcolumns, 30);
  XtSetArg(args[2], XmNeditable, False);
  XtSetArg(args[3], XmNeditMode, XmMULTI_LINE_EDIT);

  _responses = 
    XmCreateScrolledText(_baseWidget, "response", args, 4);
  
  XtVaSetValues(XtParent(_responses),
		XmNleftAttachment, XmATTACH_WIDGET,
		XmNleftWidget, _command->baseWidget(),
		XmNrightAttachment, XmATTACH_FORM,
		XmNtopAttachment, XmATTACH_WIDGET,
		XmNtopWidget, label,
		NULL);  

  XtManageChild(_responses);
  
  
  _responseQueue = new CircularQueue<char *>(MaxStoredResponses);
  if (_responseQueue->error())
  {
    setError("creation of responseQueue failed");
    return;
  }
  
  char errorBuf[errorMsgSize];

  _dmCommand = new DmStringObject(DOPPLER_COMMAND_DM, MAX_PACKET_LEN);
  if (_dmCommand->error())
  {
    _dmCommand->errorMsg(errorBuf);
    setError(errorBuf);
    return;
  }
  
  if (addDmObject(_dmCommand, DmProvideFlag) == -1)
    return;
 
  _dmResponse = new DmStringObject(DOPPLER_RESPONSE_DM, MAX_PACKET_LEN);
  if (_dmResponse->error())
  {
    _dmResponse->errorMsg(errorBuf);
    setError(errorBuf);
    return;
  }
  
  if (addDmObject(_dmResponse, DmConsumeFlag) == -1)
    return;

  _dmPing = new DmBooleanObject(DOPPLER_START_PINGING_DM);
  if (_dmPing->error())
  {
    _dmPing->errorMsg(errorBuf);
    setError(errorBuf);
    return;
  }

  if (addDmObject(_dmPing, DmProvideFlag | DmConsumeFlag) == -1)
    return;

  _dmStopPing = new DmEmptyObject(DOPPLER_INTERRUPT_DM);
  if (_dmStopPing->error())
  {
    _dmStopPing->errorMsg(errorBuf);
    setError(errorBuf);
    return;
  }

  if (addDmObject(_dmStopPing, DmProvideFlag | DmConsumeFlag) == -1)
    return;
  
  if (setConsumePeriod(AperiodicGuaranteed) == -1)
    return;

  _lastResponseTime = 0.;
  _pinging = FALSE;
  _powerStatus = SWITCH_OFF;
  
  updateGui();
}


AdvControl::~AdvControl()
{
  _responseQueue->startIter();
  for (int i = 0; i < _responseQueue->length(); i++)
  {
    char **response = _responseQueue->next();
    free(*response);
  }
}


void AdvControl::updateGui(DM_Item handle)
{
  Errno errno;
  char errorBuf[errorMsgSize];
  DM_Time t;
  char response[MAX_PACKET_LEN];
  
  if ((errno = _dmResponse->read(response, &t)) != SUCCESS)
  {
    sprintDmError(errno, "AdvControl::updateGui()", errorBuf);
    setError(errorBuf);
    return;
  }

  double responseTime = t.tv_sec + 0.000001 * t.tv_usec;
  if (responseTime != _lastResponseTime)
  {
    char *buf = strdup(response);
    _responseQueue->add(&buf);
    _lastResponseTime = responseTime;

    // Clear text widget
    XmTextSetString(_responses, "");

    int insertPoint = 0;
    _responseQueue->startIter();
    
    // Display last responses
    for (int i = 0; i < _responseQueue->length(); i++)
    {
      sprintf(response, "%s\n", *_responseQueue->next());
      XmTextInsert(_responses, insertPoint, response);
      insertPoint += strlen(response);
    }

    // Show last response at bottom of text window
    XmTextShowPosition(_responses, insertPoint);
    
  }
  
  if (errno = (_dmPing->read(&_pinging, &t)) != SUCCESS)
  {
    sprintDmError(errno, "AdvControl::updateGui()", errorBuf);
    setError(errorBuf);
    return;
  }

  if (_pinging)
  {
    XtSetSensitive(_startPinging, False);
    XtSetSensitive(_stopPinging, True);
  }
  else
  {
    XtSetSensitive(_startPinging, True);
    XtSetSensitive(_stopPinging, False);
  }

  return;
}


int AdvControl::startPinging()
{
  char errorBuf[errorMsgSize];
  Errno errno;
  DM_Time t;
  t.tv_sec = time(NULL);
  t.tv_usec = 0;
  MBool value = TRUE;
 
  if ((errno = _dmPing->write(value, &t)) != SUCCESS)
  {
    sprintDmError(errno, "AdvControl::startPinging()", errorBuf);
    setError(errorBuf);
    theErrorDialog->postAndWait(errorBuf);
    return -1;
  }
  return 0;
}


int AdvControl::stopPinging()
{
  char errorBuf[errorMsgSize];
  DM_Time t;
  t.tv_sec = time(NULL);
  t.tv_usec = 0;

  Errno errno;
  
  if ((errno = _dmStopPing->write(&t)) != SUCCESS)
  {
    sprintDmError(errno, "AdvControl::stopPinging()", errorBuf);
    setError(errorBuf);
    theErrorDialog->postAndWait(errorBuf);
    return -1;
  }

  MBool value = FALSE;
 
  if ((errno = _dmPing->write(value, &t)) != SUCCESS)
  {
    sprintDmError(errno, "AdvControl::stopPinging()", errorBuf);
    setError(errorBuf);
    theErrorDialog->postAndWait(errorBuf);
    return -1;
  }

  return 0;
}


void AdvControl::startPingCallback(Widget w,
				   XtPointer clientData,
				   XtPointer callData)
{
  AdvControl *obj = (AdvControl *)clientData;
  obj->startPinging();
}



void AdvControl::stopPingCallback(Widget w,
				  XtPointer clientData,
				  XtPointer callData)
{
  AdvControl *obj = (AdvControl *)clientData;
  obj->stopPinging();
}


void AdvControl::powerStateCallback(VkCallbackObject *obj,
				    void *clientData,
				    void *callData)
{
  switchStatus status = (switchStatus )callData;
  switch (status)
  {
    case SWITCH_ON:
    case SWITCH_ON_CASCADE:
    if (_powerStatus != SWITCH_ON && _powerStatus != SWITCH_ON_CASCADE)
    {
      /* Power just turned on */
      XtSetSensitive(_startPinging, True);
      XtSetSensitive(_stopPinging, False);
    }
    break;
    
    default:
    XtSetSensitive(_startPinging, False);
    XtSetSensitive(_stopPinging, False);
  }

  _powerStatus = status;
}


void AdvControl::writeCommand(VkCallbackObject *obj,
			      void *clientData,
			      void *callData)
{
  Boolean debug = False;
  dprintf(stderr, "AdvControl::writeCommand()\n");
  char errorBuf[errorMsgSize];
  
  
  const char *cmd = _command->get();
  
  // Check for valid command
  Boolean valid = False;
  sprintf(errorBuf, "\"%s\" - Unknown ADV command", cmd);

  AdvCommand *advCommand = &_commands[0];
  while (advCommand->mnemonic != NULL)
  {
    if (!strncmp(advCommand->mnemonic, cmd, 2))
    {
      if (strlen(advCommand->mnemonic) > 2)
      {
        // Parameters required
	if (strlen(cmd) > 2)
	{
	  valid = True;
	  break;
	}
	else
	  sprintf(errorBuf, "\"%s\" - missing parameters", cmd);
      }
      else
      {
	// No parameters allowed
	if (strlen(cmd) == 2)
	{
	  valid = True;
	  break;
	}
	else
	  sprintf(errorBuf, "\"%s\" - extraneous parameters", cmd);
      }
      
      /* Invalid command; missing or extraneous parameters */
      break;
    }
    advCommand++;
  }

  if (!valid)
  {
    theErrorDialog->postAndWait(errorBuf);
    return;
  }

  if (_pinging)
  {
    theErrorDialog->postAndWait("Cannot write commands while ADV is pinging");
    return;
  }
  
  if (_power->status() != SWITCH_ON)
  {
    theErrorDialog->postAndWait("ADV is not powered on");
  }
  
  DM_Time t;
  t.tv_sec = time(NULL);
  t.tv_usec = 0;
  
  Errno errno;
  
  if ((errno = _dmCommand->write((char *)cmd, &t)) != SUCCESS)
  {
    sprintDmError(errno, "AdvControl::writeCommand()", errorBuf);
    setError(errorBuf);
    theErrorDialog->postAndWait(errorBuf);
  }
  
}


void AdvControl::commandSelectCallback(VkCallbackObject *obj,
				       void *clientData,
				       void *callData)
{
  Boolean debug = False;
  
  dprintf(stderr, "AdvControl::commandSelectCallback()\n");

  int itemPosition = (int )callData;

  Widget comboBox = _command->comboBox();
  int index = XmComboBoxGetSelectedPos(comboBox);
  char choice[100];
  strcpy(choice, _commands[index-1].mnemonic);
  dprintf(stderr, "Selected item \"%s\"\n", choice);

  // Remove trailing comment (separated from command by ' ')
  char *ptr = strchr(choice, ' ');
  if (ptr)
    *ptr = '\0';

  _command->set(choice);
}


void AdvControl::testCallback(Widget w,
			      XtPointer clientData,
			      XtPointer callData)
{
  Boolean debug = False;
  
  dprintf(stderr, "AdvControl::testCallback()\n");
}


void AdvControl::selectTestCallback(Widget w,
				    XtPointer clientData,
				    XtPointer callData)
{
  Boolean debug = False;
  
  dprintf(stderr, "AdvControl::selectTestCallback()\n");
}


void AdvControl::modeCallback(Widget w,
			      XtPointer clientData,
			      XtPointer callData)
{
}
