#include <string.h>
#include <ctype.h>
#include <malloc.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <mbari/types.h>
#include <mbari/const.h>

extern "C" 
{
#include <mbari/types.h>
#include <dmUnix.h>
#include <semLib.h>	
#include <lstLib.h>	
#include <time.h>
#include "datamgr.h"
#include "dm_internal.h"
};
#include <Xm/Form.h>
#include <Xm/Label.h>
#include <Xm/PushB.h>
#include <Xm/Text.h>
#include <Xm/TextF.h>
#include <Xbae/Matrix.h>

#include <Vk/VkApp.h>
#include "DmLog.h"

/* Have to undef OK (which is #define'd by VxWorks stuff), since
VkGenericDialog.h has an enum symbol called "OK" */
#undef OK

#include <Vk/VkErrorDialog.h>
#include <Vk/VkFileSelectionDialog.h>


#define InfoTag      DM_INFO_MSG
#define BootTag      DM_BOOT_MSG
#define WarningTag   DM_WARN_MSG
#define ErrorTag     DM_ERR_MSG
#define CriticalTag  DM_CRIT_MSG


#define dprintf if (debug) fprintf


DmLogWindow::DmLogWindow(const char *name, 
			 const char *peerName,
			 const char *logFileName)
  : VkWindow(name)
{
  Widget baseWidget = 
    XtVaCreateWidget(name, xmFormWidgetClass, mainWindowWidget(), NULL);

  installDestroyHandler();


  Widget label = 
    XtVaCreateManagedWidget("peerNameLabel", xmLabelWidgetClass, baseWidget,
			    XmNleftAttachment, XmATTACH_FORM,
			    XmNtopAttachment, XmATTACH_FORM,
			    NULL);

  XmString xmstr = XmStringCreateSimple((char *)peerName);

  Widget peerNameWidget = 
    XtVaCreateManagedWidget("peerName", xmLabelWidgetClass, baseWidget,
			    XmNleftAttachment, XmATTACH_WIDGET,
			    XmNleftWidget, label,
			    XmNtopAttachment, XmATTACH_FORM,
			    XmNlabelString, xmstr,
			    NULL);
  XmStringFree(xmstr);


  label = 
    XtVaCreateManagedWidget("logFileLabel", xmLabelWidgetClass, baseWidget,
			    XmNleftAttachment, XmATTACH_FORM,
			    XmNtopAttachment, XmATTACH_WIDGET,
			    XmNtopWidget, peerNameWidget,
			    NULL);

  _logFileNameWidget = 
    XtVaCreateManagedWidget("logFileName", xmTextFieldWidgetClass, 
			    baseWidget,
			    XmNleftAttachment, XmATTACH_WIDGET,
			    XmNleftWidget, label,
			    XmNtopAttachment, XmATTACH_WIDGET,
			    XmNtopWidget, peerNameWidget,
			    NULL);

  XtAddCallback(_logFileNameWidget, XmNactivateCallback, 
		&DmLogWindow::logFileNameCallback, (XtPointer )this);

  Widget browse = 
    XtVaCreateManagedWidget("browse", xmPushButtonWidgetClass,
			    baseWidget,
			    XmNleftAttachment, XmATTACH_WIDGET,
			    XmNleftWidget, _logFileNameWidget,
			    XmNtopAttachment, XmATTACH_WIDGET,
			    XmNtopWidget, peerNameWidget,
			    NULL);

  XtAddCallback(browse, XmNactivateCallback,
		&DmLogWindow::browseCallback, (XtPointer )this);

  _statusMsg = 
    XtVaCreateManagedWidget("statusMsg", xmLabelWidgetClass, baseWidget,
			    XmNleftAttachment, XmATTACH_FORM,
			    XmNtopAttachment, XmATTACH_WIDGET,
			    XmNtopWidget, label,
			    NULL);


  _optionMenu = new VkOptionMenu(baseWidget, "optionMenu");
  _optionMenu->addAction("fromLastBoot", 
			 &DmLogWindow::optionCallback, this,
			 LastBootOption);

  _optionMenu->addAction("entireFile", 
			 &DmLogWindow::optionCallback, this,
			 EntireFileOption);

  XtVaSetValues(_optionMenu->baseWidget(),
		XmNtopAttachment, XmATTACH_WIDGET,
		XmNtopWidget, _statusMsg,
		XmNleftAttachment, XmATTACH_FORM,
		NULL);

  Arg args[16];

  int n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET);
  n++;
  XtSetArg(args[n], XmNtopWidget, _optionMenu->baseWidget());
  n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM);
  n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM);
  n++;
  XtSetArg(args[n], XmNrows, 20);
  n++;
  XtSetArg(args[n], XmNcolumns, 80);
  n++;
  XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT);
  n++;
  XtSetArg(args[n], XmNeditable, False);
  n++;
  XtSetArg(args[n], XmNcursorPositionVisible, False);
  n++;

  _messageList = 
    XmCreateScrolledText(baseWidget, "messageList", args, n);


  _nListChars = 0;

  XtManageChild(_messageList);

  _logFileName = strdup(logFileName);
  XmTextFieldSetString(_logFileNameWidget, _logFileName);

  _childPid = 0;
  _logFileFd = -1;
  _inputId = 0;
  _logFileFound = False;
  init();

  addView(baseWidget);
}


DmLogWindow::~DmLogWindow()
{
  if (_logFileFd >= 0) {
    close(_logFileFd);
    XtRemoveInput(_inputId);
  }
  free((void *)_logFileName);
}


int DmLogWindow::init()
{
  _lastLine.reset();

  int selected = _optionMenu->getIndex();

  switch (selected) {
  case EntireFileOption:
    _tailLines = +1;
    break;

  case LastBootOption:
    if ((_tailLines = findLastBoot()) == 0)
      // Couldn't find boot message; show entire file
      _tailLines = +1;

    break;
  }

  
  // Clear list widget
  XtVaSetValues(_messageList, XmNvalue, "", NULL);
  _nListChars = 0;


  Boolean debug = False;
  dprintf(stderr, "DmLogWindow::init() - file %s\n", _logFileName);

  if (_logFileFd >= 0) {
    close(_logFileFd);
    _logFileFd = -1;
  }

  if (_childPid > 0) {
    kill(_childPid, SIGTERM);
    _childPid = 0;
  }

  if (_inputId > 0) {
    XtRemoveInput(_inputId);
    _inputId = 0;
  }

  _nInfoMsgs = _nWarningMsgs = _nCriticalMsgs = 0;

  Boolean error = False;
  char msg[256];
  struct stat fileStat;

  if (stat(_logFileName, &fileStat) == -1) {
    _logFileFound = False;
    perror(_logFileName);
    sprintf(msg, "Couldn't open log file \"%s\" for reading", _logFileName);
    error = True;
    XmString xmstr = XmStringCreateSimple(msg);
    XtVaSetValues(_statusMsg, XmNlabelString, xmstr, NULL);
    XmStringFree(xmstr);
    return -1;
  } 
  else
    _logFileFound = True;

  int p[2];
  if (pipe(p) == -1) {
    perror("pipe() system call failed");
    sprintf(msg, "pipe() system call failed");
    error = True;
    return -1;
  }

  char cmd[512];
  sprintf(cmd, "tail -f -n %+d %s", _tailLines, _logFileName);
  dprintf(stderr, "DmLogWindow::init() - cmd=%s\n", cmd);

  _childPid = fork();
  switch (_childPid) {
  case 0:
    // Child process
    // Re-direct stdout
    close(1);
    dup(p[1]);

    close(p[0]);

    // Fire "tail -f" command
    execl("/bin/sh", "sh", "-c", cmd, 0);

    // If we get here, execlp() failed
    perror("execlp() failed");
    exit(1);
    break;

  case -1:
    perror("fork() failed");
    exit(1);

  default:
    // Parent
    _logFileFd = p[0];
    _inputId = XtAppAddInput(theApplication->appContext(), 
			     _logFileFd,
			     (XtPointer )XtInputReadMask, 
			     DmLogWindow::dataReadyCallback, 
			     this);
    close(p[1]);

  }

  XmString xmstr = XmStringCreateSimple("");
  XtVaSetValues(_statusMsg, XmNlabelString, xmstr, NULL);
  XmStringFree(xmstr);
}


void DmLogWindow::processNewData()
{
  Boolean debug = False;
  char buf[4096];
  int nbytes;

  // Read new data. If data completely fills the buffer,
  // and there is more to be read, this function will be automatically
  // invoked again until there's no more data in file descriptor.
  if ((nbytes = read(_logFileFd, buf, sizeof(buf))) == -1)
    perror("DmLogWindow::processNewData() - read()");

  buf[nbytes] = '\0';

  for (int i = 0; i < nbytes; i++) {

    _lastLine.addChar(buf[i]);

    if (_lastLine.complete()) {

      // Got a complete line; append to widget
      XmTextInsert(_messageList, _nListChars, (char *)_lastLine.buf());

      if (highLightMsg(_lastLine.buf())) {
	// Highlight this line in widget
	XmTextSetHighlight(_messageList, _nListChars, 
			   _nListChars + strlen(_lastLine.buf()), 
			   XmHIGHLIGHT_SELECTED);

	// Need to count each type of message; but just dummy for now
	_nWarningMsgs++;
      }

      _nListChars += _lastLine.nChars();

      // Get ready for next line
      _lastLine.reset();
    }
  }

  dprintf(stderr, "processNewData(): logFile=%s, nbytes=%d, nListChars=%d\n",
	  _logFileName, nbytes, _nListChars);
}


Boolean DmLogWindow::highLightMsg(const char *msg)
{
  // Find first non-blank char
  char *ptr = (char *)&msg[0];
  while (isspace(*ptr)) {
    ptr++;
  }

  if (!strncmp(ptr, WarningTag, strlen(WarningTag)) ||
      !strncmp(ptr, ErrorTag, strlen(ErrorTag)) ||
      !strncmp(ptr, CriticalTag, strlen(CriticalTag)) ||
      !strncmp(ptr, BootTag, strlen(BootTag)))
    return True;
  else
    return False;
}


int DmLogWindow::findLastBoot()
{
  FILE *fp;
  char errorBuf[512];

  if ((fp = fopen(_logFileName, "r")) == 0) {
    return -1;
  }

  int bootLine = 0;
  int line = 0;
  char buf[256];

  while (fgets(buf, sizeof(buf), fp)) {

    line++;

    char *ptr;
    if ((ptr = strstr(buf, BootTag)) != 0) {
      bootLine = line;
    }
  }

  fclose(fp);

  if (bootLine == 0) {
    sprintf(errorBuf, "Peer boot message not found in logfile %s",
	    _logFileName);

    fprintf(stderr, "%s\n", errorBuf);

    theErrorDialog->postAndWait(errorBuf);
  }

  return bootLine;
}


void DmLogWindow::logFileNameCallback(Widget w,
				      XtPointer clientData,
				      XtPointer callData)
{
  Boolean debug = False;
  dprintf(stderr, "DmLogWindow::logFileNameCallback()\n");

  DmLogWindow *obj = (DmLogWindow *)clientData;
  free(obj->_logFileName);

  char *txt = XmTextGetString(obj->_logFileNameWidget);
  obj->_logFileName = strdup(txt);
  XtFree(txt);

  obj->init();
}


void DmLogWindow::browseCallback(Widget w,
				 XtPointer clientData,
				 XtPointer callData)
{
  DmLogWindow *obj = (DmLogWindow *)clientData;
  char dir[256];
  char *ptr = strrchr(obj->_logFileName, '/');

  if (ptr) {
    // Set default directory to that of current log file
    strncpy(dir, obj->_logFileName, ptr - obj->_logFileName);
    dir[ptr - obj->_logFileName] = '\0';
    theFileSelectionDialog->setDirectory(dir);
  }

  if (theFileSelectionDialog->postAndWait() == VkDialogManager::OK) {

    XmTextSetString(obj->_logFileNameWidget, 
		    (char *)theFileSelectionDialog->fileName());

    obj->logFileNameCallback(w, clientData, callData);
  }
}
			   

void DmLogWindow::dataReadyCallback(XtPointer clientData,
				    int *fid,
				    XtInputId *id)
{
  DmLogWindow *obj = (DmLogWindow *)clientData;

  Boolean debug = False;
  dprintf(stderr, "DmLogWindow::dataReadyCallback(); fid=%d, obj->fid=%d\n",
	  *fid, obj->_logFileFd);

  obj->processNewData();

}


void DmLogWindow::optionCallback(Widget w,
				 XtPointer clientData,
				 XtPointer callData)
{
  DmLogWindow *obj = (DmLogWindow *)clientData;
  obj->init();
}



