/****************************************************************************/
/* Copyright (c) 2000 MBARI                                                 */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  :                                                               */
/* Filename : Supervisor.cc                                                 */
/* Author   : Tom O'Reilly                                                  */
/* Project  :                                                               */
/* Version  : 1.0                                                           */
/* Created  : 02/07/2000                                                    */
/* Modified :                                                               */
/* Archived :                                                               */
/****************************************************************************/
/* Modification History:                                                    */
/****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <sys/sched.h>
#include <sys/stat.h>
#include <dirent.h>
#include <time.h>
#include <unistd.h>
#include "Supervisor.h"
#include "TaskNames.h"
#include "LayeredControlIF.h"
#include "ExternalCommsIF.h"
#include "Syslog.h"
#include "System.h"
#include "StringConverter.h"

#define MaxExecArgs 10

Supervisor *Supervisor::_theSupervisor = 0;

Supervisor::Supervisor(int argc, char **argv)
{
  // Keep track of the most recently created Supervisor for cleanup 
  // on signal/exit
  _theSupervisor = this;

  if (parseOptions(argc, argv) == -1)
    exit(1);

  Boolean debug = True;

  dprintf("Supervisor::Supervisor() - simulated=%d, realTailCone=%d",
	  _simulated, _realTailCone);

  signal(SIGINT, Supervisor::signalHandler);
  signal(SIGTERM, Supervisor::signalHandler);
  signal(SIGQUIT, Supervisor::signalHandler);

  // We don't wait for children to die, and we don't
  // want any zombies either...
  signal(SIGCHLD, SIG_IGN);

  // Create data log subdirectory for this run
  createLogDir();

  // Create core system tasks
  createCoreTasks();

  if (_simulated) {
    createSimTasks();
  }
  else {
    // Create auxillary tasks
    createRealTasks();
  }
  
  _running = False;
  _startTime = time(0);
}


Supervisor::~Supervisor()
{
  // Shutdown all tasks
  shutdown(0);

  free((void *)_planFileName);
  free((void *)_abortFileName);
  free((void *)_deviceFileName);

  _theSupervisor = 0;
}


/*
Shutdown all tasks
*/
void Supervisor::shutdown(int exitCode)
{
  Boolean debug = True;

  dprintf("Supervisor::shutdown()\n");

  // Kill all processes in group
  kill(0, SIGINT);

  dprintf("Supervisor::shutdown() - done()\n");

  if (((time(0) - _startTime) > _minRunTime) && _terminateFileName) {
    // Shutdown occurred after system ran for specified time.
    // Launch specified "termination" tasks
    dprintf("termination file baseName: %s", _terminateFileName);
    const char *fileName = System::configurationFile(_terminateFileName);
    dprintf("termination file: %s", fileName);

    try {
      dprintf("Try to open termination file %s", fileName);
      createTasks(fileName);
    }
    catch (Exception e) {
      Syslog::write("%s", e.msg);
    }
  }

  ::exit(exitCode);
}



int Supervisor::checkTasks(Boolean restart)
{
  int nDied = 0;
  Boolean debug = True;
  TaskEntry *task;
  for (int i = 0; i < _tasks.size(); i++) {
    _tasks.get(i, &task);
    // Check to see if process exists
    if (kill(task->_pid, 0) == -1) {
      if (errno == ESRCH) {
	// Task isn't there anymore!
	Syslog::write("Task \"%s\" died?\n", task->_interface->name());
	nDied++;
	if (restart)
	  createTask(task->_interface->name(), task->_args, True);
      }
    }
  }
  return nDied;
}


void Supervisor::keepAlive()
{
  Boolean debug = False;
  dprintf("Supervisor::keepAlive()\n");
}


void Supervisor::createCoreTasks()
{
  char args[100];
  Boolean debug = True;

  fprintf(stderr, "Supervisor::createCoreTasks()\n");

  if (createTask(EventLogName, "", False) == -1) {
    Syslog::write("Couldn't start " EventLogName);
    shutdown(1);
  }

  if (createTask(WorkSiteName, "", False) == -1) {
    Syslog::write("Couldn't start " WorkSiteName);
    shutdown(1);
  }

  if (createTask(VehicleConfigurationName, "", False) == -1) {
    Syslog::write("Couldn't start " VehicleConfigurationName);
    shutdown(1);
  }

  if (createTask(DropWeightName, "", False) == -1) {
    Syslog::write("Couldn't start " DropWeightName);
    shutdown(1);
  }

  if (createTask(ExternalCommsName, "", False) == -1) {
    Syslog::write("Couldn't start " ExternalCommsName);
    shutdown(1);
  }

  if (createTask(NavigationName, "", False) == -1) {
    Syslog::write("Couldn't start " NavigationName);
    shutdown(1);
  }

  sprintf(args, "-plan %s -abort %s", _planFileName, _abortFileName);

  if (createTask(LayeredControlName, args, False) == -1) {
    Syslog::write("Couldn't start " LayeredControlName);
    shutdown(1);
  }

  if (createTask(DynamicControlName, "", False) == -1) {
    Syslog::write("Couldn't start " DynamicControlName);
    shutdown(1);
  }
}


void Supervisor::createSimTasks()
{
  if (!_simulated) {
    return;
  }

  if (createTask(SimulatorName, "", False) == -1) {
    Syslog::write("Couldn't start " SimulatorName);
    shutdown(1);
  }

  if (createTask(SimulatedDepthSensorName, "", False) == -1) {
    Syslog::write("Couldn't start " SimulatedDepthSensorName);
    shutdown(1);
  }

  if (createTask(SimulatedRangefinderName, "", False) == -1) {
    Syslog::write("Couldn't start " SimulatedRangefinderName);
    shutdown(1);
  }

  if (createTask(SimulatedAHRSName, "", False) == -1) {
    Syslog::write("Couldn't start " SimulatedAHRSName);
    shutdown(1);
  }

  if (createTask(SimulatedTailConeName, "", False) == -1) {
    Syslog::write("Couldn't start " SimulatedTailConeName);
    shutdown(1);
  }

  if (createTask(SimulatedGpsName, "", False) == -1) {
    Syslog::write("Couldn't start " SimulatedGpsName);
    shutdown(1);
  }

  if (createTask(SimulatedLblName, "", False) == -1) {
    Syslog::write("Couldn't start " SimulatedLblName);
    shutdown(1);
  }
  //
  // This probably shouldn't be here.  Ask Tom.  Ques: Does removing the LBL
  // stuff from Devices.cfg mean that it shouldn't run in *simulation* either?
  //
  if (createTask("scheduler", "", False) == -1) {
    Syslog::write("Couldn't start the ping scheduler.\n" );
    shutdown(1);
  }
  if (createTask(SimulatedDvlName, "", False) == -1) {
    Syslog::write("Couldn't start " SimulatedDvlName);
    shutdown(1);
  }
}


void Supervisor::createRealTasks()
{
  // Read configuration file; create specified tasks
  const char *fileName = System::configurationFile(_deviceFileName);

  createTasks(fileName);

  return;
}


void Supervisor::createTasks(const char *fileName)
{
  Boolean debug = False;
  char errorBuf[256];

  FILE *fp = fopen(fileName, "r");
  if (fp == 0) {
    sprintf(errorBuf, "Supervisor::createTasks() - \n"
	    "couldn't open task config file \"%s\"", fileName);

    throw Exception(errorBuf);
  }
  
  System::copyToLogDir(fileName);

  char buf[512];

  while (fgets(buf, sizeof(buf), fp) != 0) {

    char *ptr;

    if ((ptr = strchr(buf, '\r')) != 0) 
      // Remove carriage-return
      *ptr = '\0';

    if ((ptr = strchr(buf, '\n')) != 0) 
      // Remove newline
      *ptr = '\0';

    if ((ptr = strchr(buf, '#')) != 0) 
      // Ignore everything following comment character
      *ptr = '\0';

    int nToken = 0;
    char *token;
    char *programName;
    char args[512];
    args[0] = '\0';
    char *argsPtr = args;
    ptr = buf;
    while ((token = strtok(ptr, " \t")) != 0) {
      ptr = 0;

      if (nToken == 0) {
	programName = token;
      }
      else {
	// Argument to program
	sprintf(argsPtr, "%s ", token);
	argsPtr += strlen(argsPtr);
	if (argsPtr > (args + sizeof(args))) {
	  sprintf(errorBuf, "Supervisor::createRealTasks() - \n"
		  "Overflow of command argument buffer");

	  fclose(fp);
	  throw Exception(errorBuf);
	}
      }
      nToken++;
    }
    if (nToken) {

      dprintf("Supervisor::createTasks() - "
	      "spawning %s %s", programName, args);

      // Spawn the program
      if (createTask(programName, args, False) == -1) {
	sprintf(errorBuf, "Supervisor::createTasks() - \n"
		"Exec of \"%s %s\" failed", programName, args);
	
	fclose(fp);
	throw Exception(errorBuf);
      }
    }
  }

  fclose(fp);
}


int Supervisor::createTask(const char *taskName, const char *args,
			   Boolean restarted)
{
  Boolean debug = False;

  fprintf(stderr, "Supervisor::createTask() - %s\n", taskName);

  char buf[512];
  strcpy(buf, args);

  // Parse argument string into argv array
  const char *argv[MaxExecArgs];
  argv[0] = taskName;
  char *ptr = buf;
  char *token;
  int nArgs = 1;
  while ((token = strtok(ptr, " \t")) != 0) {
    ptr = 0;
    if (nArgs >= MaxExecArgs - 1) {
      // Too many arguments (note: need to reserve last arg for
      // "restart" option)
      Syslog::write("Supervisor::createTask() - %s %s - too many arguments\n",
		    taskName, args);
      shutdown(1);
    }

    argv[nArgs++] = token;
  }

  if (restarted)
    // Tells Task that it needs to re-initialize itself
    argv[nArgs++] = RestartMnem;

  // Mark end of argument list
  argv[nArgs] = 0;

  for (int i = 0; i < nArgs; i++) {
    dprintf("createTask(), argv[%d]=%s\n", i, argv[i]);
  }

  char errorBuf[512];
  pid_t taskPid;

  Boolean error = False;

  // Create Task process
  switch ((taskPid = fork())) {

  case 0:
    // In child; execute Task program
    dprintf("Supervisor::createTask() - execvp(\"%s\")...\n", taskName);
    execvp(taskName, argv);
    sprintf(errorBuf, "Supervisor::createTask() - execvp() of \"%s\" failed",
	    taskName);

    perror(errorBuf);
    shutdown(1);

    break;

  case -1:
    // Fork failed
    perror("Supervisor::createTask() - fork() failed");
    error = True;
    break;

  default:
    // In parent; add TaskEntry to table
    dprintf("Supervisor::createTask() - parent task; add TaskEntry\n");
    addTaskEntry(taskName, taskPid, args);
    dprintf("Supervisor::createTask() - added TaskEntry.\n");
  }

  dprintf("Supervisor::createTask() - done with switch\n");
  if (error)
    return -1;
  else
    return 0;
}


void Supervisor::addTaskEntry(const char *taskName, pid_t pid, 
			      const char *args)
{
  Boolean debug = False;
  TaskEntry *taskEntry;
  
  // Check to see whether task is already in table
  for (int i = 0; i < _tasks.size(); i++) {
    _tasks.get(i, &taskEntry);
    if (!strcmp(taskName, taskEntry->_interface->name())) {
      // Task is already in list. Use same interface, just replace pid
      taskEntry->_pid = pid;
      _tasks.set(i, &taskEntry);
      return;
    }
  }

  // Task is not yet in list; add it
  TaskInterface *interface = new TaskInterface(taskName, taskName, 
					       DoNotConnect);

  taskEntry = new TaskEntry(pid, interface, args);
  _tasks.add(&taskEntry);
}



void Supervisor::run() 
{
  _running = True;

  while (True) {

    // Check status of Tasks
    if (checkTasks(True) > 0) {
      // Something died
      Syslog::write("Supervisor::Supervisor() - task(s) died; exiting\n");
      shutdown(1);
    }

    // Reset watchdog timer
    keepAlive();

    sleep(2);
  }
}


void Supervisor::exit(int exitCode)
{
  shutdown(exitCode);
}


void Supervisor::createLogDir()
{
  Boolean debug = False;

  char errorBuf[256];

  char *auvLogDir = getenv(AuvLogDirName);

  if (auvLogDir == 0) {

    Syslog::write("Environment variable %s not set\n",
		  AuvLogDirName);

    exit(1);
  }

  // Determine current year and day-of-year
  time_t timeNow = time(0);
  struct tm *now = gmtime(&timeNow);

  int currentYear = now->tm_year + 1900;
  int currentDoy = now->tm_yday + 1;

  DIR *directory;

  if ((directory = opendir(auvLogDir)) == 0) {
    Syslog::write("Can't open log directory \"%s\"", auvLogDir);
    sprintf(errorBuf, "Supervisor::createLogDir(), openDir(): ", 
	    strerror(errno));
    Syslog::write(errorBuf);
    exit(1);
  }

  int maxRun = -1;

  char path[256];
  struct dirent *entry;

  while ((entry = readdir(directory)) != 0) {
    
    sprintf(path, "%s/%s", auvLogDir, entry->d_name);

    struct stat fileStat;

    if (stat(path, &fileStat) == -1) {

      sprintf(errorBuf, "Supervisor::createLogDir(), stat(%s):\n%s", 
	      path, strerror(errno));

      Syslog::write(errorBuf);
      exit(1);
    }

    if (!S_ISDIR(fileStat.st_mode)) {
      dprintf("file %s is NOT a directory\n", entry->d_name);
      continue;
    }

    // Parse year, day-of-year, and run # from file name
    strcpy(path, entry->d_name);
    char *pathPtr = path;
    int nToken = 0;
    Boolean reject = False;
    char *token;

    while ((token = strtok(pathPtr, ".")) != 0 && !reject) {
      pathPtr = 0;

      // Determine if token conforms to log subdirectory name format
      if (!StringConverter::isInteger(token)) {
	break;
      }


      switch (nToken) {

      case 0:

	// Year
	if (StringConverter::stringToInteger(token) != currentYear)
	  reject = True;

	break;
	
      case 1:
	// Day of year
	if (StringConverter::stringToInteger(token) != currentDoy)
	  reject = True;

	break;

      case 2:
	// Run number. If we get here, then year/day-of-year corresponds
	// to today.
	maxRun = max(maxRun, StringConverter::stringToInteger(token));
	break;

      default:
	reject = True;
      }

      nToken++;
    }
  }

  closedir(directory);

  sprintf(path, "%s/%d.%03d.%02d", 
	  auvLogDir, currentYear, currentDoy, maxRun + 1);

  // Create new directory
  Syslog::write("Create log directory \"%s\"\n", path);
  mode_t mode = 0777;
  if (mkdir(path, mode) == -1) {
    sprintf(errorBuf, "Supervisor::createLogDir(), mkdir(): %s", 
	    strerror(errno));

    Syslog::write(errorBuf);
    exit(1);
  }

  // Create symbolic link to new directory
  char linkName[256];
  sprintf(linkName, "%s/%s", auvLogDir, LatestLogDirName); 

  // First delete link (in case it already exists)
  unlink(linkName);

  char cmd[256];
  sprintf(cmd, "ln -s %s %s", path, linkName);

  // Have to use system() to create link, because QNX link() does not 
  // work for directories!!!
  if (system(cmd) != 0) {

    sprintf(errorBuf, "Supervisor::createLogDir()\ncommand \"%s\" failed",
	    cmd);

    Syslog::write(errorBuf);
    exit(1);
  }

  // Want to 'tee' stderr to a syslog file in 'latest' directory
  char syslogFileName[256];
  sprintf(syslogFileName, "%s/syslog", linkName);

  Boolean error = False;
  pid_t child;
  int pipeFd[2];

  pipe(pipeFd);

  switch ((child = fork())) {

  case 0:
    // In child
    // Close stdin
    close(0);

    // Get stdin from pipe
    dup(pipeFd[0]);
    close(pipeFd[1]);

    // Execute 'tee' with '-i' option, so that signals are properly
    // delivered to other kids in process group.
    execlp("tee", "tee", "-i", syslogFileName, 0);
    perror("Supervisor::createLogDir() - execlp() of \"tee\" failed");
    shutdown(1);
    break;

  case -1:
    // Fork failed
    perror("Supervisor::createLogDir() - fork() failed");
    error = True;
    break;

  default:
    // In parent; 
    // Close stderr
    close(2);
    dup(pipeFd[1]);
    close(pipeFd[0]);
  }
}


void Supervisor::signalHandler(int sigNo)
{
  Boolean debug = True;

  if (!_theSupervisor)
    // Doesn't exist
    return;

  dprintf("Supervisor::signalHandler(); caught signal %d\n", sigNo);

  Supervisor::cleanup();

  ::exit(1);
}


void Supervisor::cleanup()
{
  Boolean debug = True;

  dprintf("Supervisor::cleanup() - delete _theSupervisor\n");

  delete _theSupervisor;

  dprintf("Supervisor::cleanup() - done\n");
  return;
}



int Supervisor::parseOptions(int argc, char **argv)
{
  _minRunTime = 300;
  _simulated = False;
  _realTailCone = True;

  _planFileName = strdup("normalMplan.cfg");
  _abortFileName = strdup("abortMplan.cfg");
  _deviceFileName = strdup("devices.cfg");
  _terminateFileName = 0;

  Boolean error = False;

  for (int i = 1; i < argc; i++) {

    if (!strcmp(argv[i], "-plan")) {
      if (i < argc - 1) {
	free((void *)_planFileName);
	_planFileName = strdup(argv[++i]);
      }
      else
	error = True;
    }

    else if (!strcmp(argv[i], "-abort")) {
      if (i < argc - 1) {
	free((void *)_abortFileName);
	_abortFileName = strdup(argv[++i]);
      }
      else
	error = True;
    }

    else if (!strcmp(argv[i], "-dev")) {
      if (i < argc - 1) {
	free((void *)_deviceFileName);
	_deviceFileName = strdup(argv[++i]);
      }
      else
	error = True;
    }

    else if (!strcmp(argv[i], "-term")) {
      if (i < argc - 1) {
	free((void *)_terminateFileName);
	_terminateFileName = strdup(argv[++i]);
      }
      else
	error = True;
    }

    else if (!strcmp(argv[i], "-minrun") && i < argc - 1) {
      _minRunTime = atoi(argv[++i]);
      if (_minRunTime <= 0) {
	Syslog::write("Invalid time: %s", argv[i]);
	error = True;
      }
    }

    else if (!strcmp(argv[i], "-sim")) {
      _simulated = True;
      _realTailCone = False;
    }

    else if (!strcmp(argv[i], "-simtail")) {
      _simulated = True;
      _realTailCone = True;
    }

    else {
      Syslog::write("Illegal option: %s", argv[i]);
      error = True;
    }
  }

  if (error) {
    Syslog::write("usage: %s "
		  "[-sim][-simtail][-plan file][-abort file][-dev file][-term file][-minrun secs]", 
		  argv[0]);
    return -1;
  }
  else
    return 0;
}
