//    Copyright 2004-2010, various authors
//
//    This program is free software; you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation; either version 2 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program; if not, write to the Free Software
//    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

/*
   nml_mod.cc

   Definition of NML_MODULE class.
   */

/*
   Modification history:

    9-Sep-1997 WPS eliminated LOAL_LOCAL_VARIABLES, UNLOAD_LOCAL_VARIABLES,
    READ_CHILD_BUFFERS, WRITE_CHILD_BUFFERS, and DETERMINE_CHILD_STATUS
   29-May-1997 WPS replaced include nml_emc.hh with nml_oi.hh
    3-Apr-1997 WPS brought over the functions from RCS_MODULE_C
   11-Feb-1997 WPS modified  calc_avg_time, update_line_history, and
   stop_timing all use exec_history but fail to check to
   see if it is initialized.
   I override them here minimize changes to rcs_module.cc
   3-Dec-1996  FMP changed NML to RCS_CMD/STAT_CHANNEL for commandIn,
   statusOut, etc.
   112696-hui, changed the base class for commandInData, etc., from NMLmsg
   to RCS_CMD_MSG and RCS_STAT_MSG.
   16-Oct-1996  FMP inited done to 0 in ctor
   29-Jul-1996  FMP moved NML_ERROR, NML_TEXT, NML_DISPLAY, and NML_STATUS
   into nml_emc.cc
   29-Jul-1996  FMP added NML_TEXT and NML_DISPLAY classes
   10-Jun-1996  Fred Proctor added malloc/free of cmdOutstanding array
   5-Jun-1996  Fred Proctor added NML error stuff
   16-Apr-1996  Fred Proctor added NMLmsg * initing
   5-Apr-1996  Fred Proctor created
    */

#ifdef __cplusplus
extern "C"
{
#endif

#include <stdlib.h>		// malloc()
#include <string.h>		// strcpy()
#include <errno.h>		// errno
#include <stdarg.h>
#include <math.h>		// fabs()

#ifdef __cplusplus
}
#endif


#include <rtapi_string.h>
#include "nml_mod.hh"
#include "nml_oi.hh"		// NML_ERROR, NML_TEXT, NML_DISPLAY
#include "rcs_print.hh"
#include "rcs_exit.hh"


NML_MODULE::NML_MODULE (const char *inifile, const char *section)
{
  zero_common_vars ();
}


NML_MODULE::NML_MODULE ()
{
  zero_common_vars ();
}


void
NML_MODULE::zero_common_vars ()
{
  commandIn = 0;
  force_command = 0;
  commandInData = 0;
  statusOut = 0;
  statusOutData = 0;
  subs = 0;
  commandLastNum = 0;
  commandOutstanding = 0;
  timer = 0;
  numSubordinates = 0;
  errorLog = 0;
  done = 0;
  statusInData = 0;
  commandOutData = 0;


  cycle_start = 0;
  cycle_stop = 0;
  command_time_averaged = 0;
  new_command_sequence = 0;
  new_line_num_sequence = 0;
  new_sup_request = 0;

  delta_clock = 0;
  command_current_time = 0;

  pause_status = 0;
  command = 0;
  last_line = 0;

  execute = 0;
  command_time = 0;
  state = UNINITIALIZED_STATE;
  status = UNINITIALIZED_STATUS;

  sup_req_num = 0;
  sup_req_num_echo = 0;
  command_num = 0;
  command_num_echo = 0;

  // state table vars
  matched = 0;
  stateBegin = 0;

/*! \todo Another #if 0 */
#if 0
//below 010997-hui
  exec_status.command_num = 0;
  exec_status.command = 0;
  exec_status.status = 0;
  exec_status.state = 0;
  exec_status.line_num = 0;
  exec_status.command_time = 0;

  run_command.run_state = 0;
  run_command.step_num = 0;

  run_status.step_num_echo = 0;
  run_status.dummy1 = 0;
#endif

  source_line = -1;
  source_file = NULL;

  commands_received = 0;
  commands_executed = 0;
  last_command_completed_serial_number = -1;
  total_run_time = 0;
  cycles = 0;
  max_cycle_time = 0;
  min_cycle_time = 1e6;
  last_cycle_time = 0;
  start_run_time = 0;
  stop_run_time = 0;
  last_start_run_time = 0;
  subs_allocated = 0;
  expected_cycle_time = 0;
  min_run_time = 1e6;
  max_run_time = 0;
  start_cycle_time = 0;
  proc_name = 0;
  temp_file = 0;
  temp_line = 0;
  Dclock_expiration = 0;
  Dclock_start_time = 0;
  log_line = 0;
  log_src = 0;
}

NML_MODULE::~NML_MODULE ()
{
  int t;

  if (commandIn != 0)
    delete commandIn;
  commandIn = 0;

  if (statusOut != 0)
    delete statusOut;
  statusOut = 0;

  if (errorLog != 0)
    delete (errorLog);
  errorLog = 0;

  if (subs != 0)
    {
      for (t = 0; t < numSubordinates; t++)
	{
	  if (subs[t] == 0)
	    {
	      continue;
	    }
	  if (subs[t]->commandOut != 0)
	    {
	      delete subs[t]->commandOut;
	      subs[t]->commandOut = 0;
	    }
	  if (subs[t]->statusIn != 0)
	    {
	      delete subs[t]->statusIn;
	      subs[t]->statusIn = 0;
	    }
	  subs[t]->commandOutData = 0;
	  subs[t]->statusInData = 0;
	  delete subs[t];
	  subs[t] = 0;
	}

      free(subs);

/*! \todo Another #if 0 */
#if 0
      if (NULL != ini)
	{
	  delete ini;
	  ini = NULL;
	}
#endif
      subs = 0;
    }

  if (commandOutstanding != 0)
    {
      free(commandOutstanding);
      commandOutstanding = 0;
    }

  if (commandLastNum != 0)
    {
      free(commandLastNum);
      commandLastNum = 0;
    }

  if (timer != 0)
    {
      delete timer;
      timer = 0;
    }

  if (proc_name != 0)
    {
      free(proc_name);
      proc_name = 0;
    }

}

void
NML_MODULE::setCmdChannel (RCS_CMD_CHANNEL * cmd_channel)
{
  if (NULL == cmd_channel)
    {
      rcs_print_error ("Command channel is NULL.\n");
      rcs_exit (-1);
    }

  commandIn = cmd_channel;
  if (!commandIn->valid ())
    {
      rcs_print_error ("Command channel is invalid.\n");
      rcs_exit (-1);
    }
  else
    {
      commandInData = commandIn->get_address ();
    }
  if (NULL != commandIn->cms)
    {
      proc_name = strndup(commandIn->cms->ProcessName, LINELEN);
    }
}

void
NML_MODULE::setStatChannel (RCS_STAT_CHANNEL * stat_channel,
			    RCS_STAT_MSG * stat_msg)
{
  if (NULL == stat_channel)
    {
      rcs_print_error ("Status channel is NULL.\n");
      rcs_exit (-1);
    }

  statusOut = stat_channel;
  if (!statusOut->valid ())
    {
      rcs_print_error ("Status channel is invalid.\n");
      rcs_exit (-1);
    }
  if (NULL == stat_msg)
    {
      rcs_print_error ("Status out message is NULL.\n");
      rcs_exit (-1);
    }
  if (stat_msg->type <= 0)
    {
      rcs_print_error
	("Status out message must have positive type. (type = %d)\n",
	 (int)stat_msg->type);
      rcs_exit (-1);
    }
  if (stat_msg->size < ((long) sizeof (RCS_STAT_MSG)))
    {
      rcs_print_error
	("Status out message must have a size of atleast sizeof(RCS_STAT_MSG) or %zu bytes,",
	 sizeof (RCS_STAT_MSG));
      rcs_print_error ("but the status out message size was only %ld.\n",
		       stat_msg->size);
      rcs_exit (-1);
    }
  statusOutData = stat_msg;
}

void
NML_MODULE::setErrorLogChannel (NML * errorLog_channel)
{
  if (NULL == errorLog_channel)
    {
      rcs_print_error ("Error Log channel is NULL.\n");
      rcs_exit (-1);
    }

  errorLog = errorLog_channel;
  if (!errorLog->valid ())
    {
      rcs_print_error ("Error Log channel is invalid.\n");
      rcs_exit (-1);
    }

}

int
NML_MODULE::addSubordinate (RCS_CMD_CHANNEL * cmd_channel,
			    RCS_STAT_CHANNEL * stat_channel)
{
  int sub_num = numSubordinates;
  setSubordinates (numSubordinates + 1);
  if (NULL == cmd_channel)
    {
      logError ("Command Channel for subordinate %d is NULL.\n", sub_num);
      rcs_exit (-1);
    }
  if (NULL == stat_channel)
    {
      logError ("Status Channel for subordinate %d is NULL.\n", sub_num);
      rcs_exit (-1);
    }
  subs[sub_num]->commandOut = cmd_channel;
  if (!subs[sub_num]->commandOut->valid ())
    {
      logError ("Command Channel for subordinate %d is invalid.\n", sub_num);
      rcs_exit (-1);
    }
  else
    {
      subs[sub_num]->commandOutData =
	subs[sub_num]->commandOut->get_address ();
    }
  commandOutData[sub_num] = subs[sub_num]->commandOutData;

  subs[sub_num]->statusIn = stat_channel;
  if (!subs[sub_num]->statusIn->valid ())
    {
      logError ("Command Channel for subordinate %d is invalid.\n", sub_num);
      rcs_exit (-1);
    }
  else
    {
      subs[sub_num]->statusInData = subs[sub_num]->statusIn->get_address ();
    }
  statusInData[sub_num] = subs[sub_num]->statusInData;
  return sub_num;
}

int
  NML_MODULE::use_realloc =
  1;

int
NML_MODULE::setSubordinates (int number)
{
  int t;
  int previousNumSubordinates = numSubordinates;
  if (NULL == subs)
    {
      subs_allocated = 0;
    }

  if (subs_allocated > numSubordinates)
    {
      numSubordinates = number;
      subs[number]->commandOut = 0;
      subs[number]->commandOutData = 0;
      subs[number]->statusIn = 0;
      subs[number]->statusInData = 0;
      statusInData[number] = 0;
      commandOutData[number] = 0;
      return 0;
    }

  if (number < 0)
    return -1;

  // record number of subordinates
  numSubordinates = number;

  // get out now if nothing else to do
  if (number == 0)
    return 0;

  // allocate subordinate NML channels
  // On NT we sometimes get a lame problem with realloc
  if (use_realloc)
    {
      NML_SUBORDINATE_STRUCT **subsNew = (NML_SUBORDINATE_STRUCT **) realloc (subs,
						  number *
						  sizeof
						  (NML_SUBORDINATE_STRUCT *));

      RCS_STAT_MSG** statusInDataNew = (RCS_STAT_MSG **) realloc (statusInData,
						number *
						sizeof (RCS_STAT_MSG *));

      RCS_CMD_MSG** commandOutDataNew = (RCS_CMD_MSG **) realloc (statusInData,
						 number *
						 sizeof (RCS_CMD_MSG *));

      if (subsNew)           subs=subsNew;
      if (statusInDataNew)   statusInData=statusInDataNew;
      if (commandOutDataNew) commandOutData=commandOutDataNew;

      if (!subsNew || !statusInDataNew ||!commandOutDataNew) {
          free(subs);
          free(statusInData);
          free(commandOutData);
          rcs_print_error ("Out of memory.\n");
          rcs_exit (-1);
      }
    
    }
  else
    {


      NML_SUBORDINATE_STRUCT **old_subs = subs;
      RCS_STAT_MSG **old_statusInData = statusInData;
      RCS_CMD_MSG **old_commandOutData = commandOutData;

      subs =
	(NML_SUBORDINATE_STRUCT **) malloc (number *
						  sizeof
						  (NML_SUBORDINATE_STRUCT *));
      if (NULL != old_subs && NULL != subs)
	{
	  memcpy (subs, old_subs,
		  previousNumSubordinates *
		  sizeof (NML_SUBORDINATE_STRUCT *));
	}
      free(old_subs); old_subs=NULL;

      statusInData =
	(RCS_STAT_MSG **) malloc (number * sizeof (RCS_STAT_MSG *));
      if (NULL != old_statusInData && NULL != statusInData)
	{
	  memcpy (statusInData, old_statusInData,
		  previousNumSubordinates * sizeof (RCS_STAT_MSG *));
	}
      free(old_statusInData); old_statusInData=NULL;

      commandOutData =
	(RCS_CMD_MSG **) malloc (number * sizeof (RCS_CMD_MSG *));
      if (NULL != old_commandOutData && NULL != commandOutData)
	{
	  memcpy (commandOutData, old_commandOutData,
		  previousNumSubordinates * sizeof (RCS_CMD_MSG *));
	}
      free(old_commandOutData); old_commandOutData=NULL;
      
      if (NULL == subs || NULL == statusInData || commandOutData)
        {
          rcs_print_error ("Out of memory.\n");
          rcs_exit (-1);
        }

    }
/*! \todo Another #if 0 */
#if 0
  // allocate commandOutstanding array
  commandLastNum = (int *) realloc (commandLastNum, number * sizeof (int));
  commandOutstanding = (int *) realloc (commandOutstanding,
					number * sizeof (int));
#endif


  // initialize each NML channel in the new arrays to 0
  for (t = previousNumSubordinates; t < number; t++)
    {
      subs[t] = new NML_SUBORDINATE_STRUCT ();
      if (NULL == subs[t])
	{
	  rcs_print_error ("Out of memory.\n");
	  rcs_exit (-1);
	}
      subs[t]->commandOut = 0;
      subs[t]->commandOutData = 0;
      subs[t]->statusIn = 0;
      subs[t]->statusInData = 0;
      statusInData[t] = 0;
      commandOutData[t] = 0;

      //      commandOutstanding[t] = 0;
    }

  subs_allocated = numSubordinates;
  return 0;
}

/*
   Error, text, and display functions. Note that all these go to
   the errorLog channel
   */

int
NML_MODULE::setLogInfo (const char *src, int l)
{
  log_line = l;
  log_src = src;
  return (0);
};

int
NML_MODULE::logError (const char *fmt, ...)
{
  NML_ERROR error_msg;
  va_list ap;

  memset (error_msg.error, 0, NML_ERROR_LEN);
  if (log_line > 0 && log_src != NULL)
    {
      snprintf (error_msg.error, sizeof(error_msg.error), "%s:%d ", log_src, log_line);
    }

  // write args to NML message
  va_start (ap, fmt);
  vsprintf (error_msg.error, fmt, ap);

  va_end (ap);

  // force a NULL at the end for safety
  error_msg.error[NML_ERROR_LEN - 1] = 0;

  set_print_rcs_error_info (NULL, -1);
  print_rcs_error_new ("%s\n", error_msg.error);


  // check channel for validity
  if (errorLog == NULL)
    return -1;
  if (!errorLog->valid ())
    return -1;

  // write it
  errorLog->write (error_msg);

  return 0;
}


int
NML_MODULE::logText (const char *fmt, ...)
{
  NML_TEXT text_msg;
  va_list ap;

  // check channel for validity
  if (errorLog == NULL)
    return -1;
  if (!errorLog->valid ())
    return -1;

  // write args to NML message
  va_start (ap, fmt);
  vsprintf (text_msg.text, fmt, ap);
  va_end (ap);

  rcs_print ("%s\n", text_msg.text);

  // force a NULL at the end for safety
  text_msg.text[NML_TEXT_LEN - 1] = 0;

  // write it
  errorLog->write (text_msg);

  return 0;
}

int
logTextToNML (NML * errorLog, const char *fmt, ...)
{
  NML_TEXT text_msg;
  va_list ap;

  // check channel for validity
  if (errorLog == NULL)
    return -1;

  // write args to NML message
  va_start (ap, fmt);
  vsprintf (text_msg.text, fmt, ap);
  va_end (ap);

  //  rcs_print("%s\n", text_msg.text);

  // force a NULL at the end for safety
  text_msg.text[NML_TEXT_LEN - 1] = 0;

  // write it
  if (errorLog->write (text_msg) < 0)
    {
      return -1;
    }

  return 0;
}

int
NML_MODULE::requestDisplay (const char *display)
{
  NML_DISPLAY display_msg;

  // check channel for validity
  if (errorLog == NULL)
    return -1;
  if (!errorLog->valid ())
    return -1;

  // write args to NML message
  rtapi_strxcpy (display_msg.display, display);

  // force a NULL at the end for safety
  display_msg.display[NML_DISPLAY_LEN - 1] = 0;

  // write it
  errorLog->write (display_msg);

  return 0;
}




// The rest of the functions here were taken from RCS_MODULE_C
// Virtual Functions
//

void
NML_MODULE::DECISION_PROCESS (void)
{

}


void
NML_MODULE::setSelfCommand (RCS_CMD_MSG * cmd)
{
  if (NULL == cmd || NULL == statusOutData || NULL == commandInData
      || NULL == commandIn)
    {
      return;
    }
  if (NULL == commandIn->cms)
    {
      return;
    }
  if (cmd->size > commandIn->cms->size)
    {
      rcs_print_error
	("NML_MODULE::setSelfCommand, Command too big! %ld(0x%lX) > %ld(0x%lX)\n",
	 cmd->size, cmd->size, commandIn->cms->size, commandIn->cms->size);
    }
  statusOutData->echo_serial_number++;
  cmd->serial_number = statusOutData->echo_serial_number + 1;
  commandIn->write (cmd);
  memcpy (commandInData, cmd, cmd->size);
  statusOutData->command_type = cmd->type;
  force_command = 1;
}

void
NML_MODULE::read_command_in ()
{
  NMLTYPE type;

  if (force_command)
    {
      force_command = 0;
      return;
    }

  switch (type = commandIn->read ())
    {
    case -1:
      logError ("Can not read input command. (%d)", commandIn->error_type);
      if (NULL != statusOutData)
	{
	  statusOutData->command_type = type;
	}
      break;

    case 0:
      // no new command
      break;

    default:
      commandInData = commandIn->get_address ();
      if (NULL != statusOutData)
	{
	  statusOutData->command_type = type;
	}
      break;
    }
}

void
NML_MODULE::read_subordinates_status ()
{
  int t;
  NMLTYPE type;

  // read NML STATUS buffer from subordinates
  for (t = 0; t < numSubordinates; t++)
    {
      if (NULL == subs[t])
	{
	  continue;
	}
      if (NULL == subs[t]->statusIn)
	{
	  continue;
	}
      switch (type = subs[t]->statusIn->peek ())
	{
	case -1:
	  // error on NML channel
	  logError ("Can not read status from subodinate %s (%d).\n",
		    subs[t]->statusIn->cms->BufferName,
		    subs[t]->statusIn->error_type);
	  break;

	case 0:
	  // no new data
	  break;

	default:
	  subs[t]->statusInData = subs[t]->statusIn->get_address ();
	  if (NULL != subs[t]->statusInData
	      && NULL != subs[t]->commandOutData)
	    {
	      if (subs[t]->statusInData->echo_serial_number !=
		  subs[t]->commandOutData->serial_number)
		{
		  subs[t]->statusInData->status = RCS_EXEC;
		}
	    }
	  // something new in STATUS
	  break;
	}
    }
}

void
NML_MODULE::READ_COMM_BUFFERS (void)
{
  read_command_in ();
  read_subordinates_status ();
  check_if_new_command ();
}

void
NML_MODULE::PRE_PROCESS (void)
{

}

void
NML_MODULE::POST_PROCESS (void)
{

}

void
NML_MODULE::write_status_out ()
{
  if (NULL == statusOutData)
    {
      return;
    }
  // update NML STATUS
  statusOutData->command_type = commandInData->type;
  statusOutData->state = state;
  statusOutData->status = (RCS_STATUS) status;
  if (status == RCS_DONE &&
      last_command_completed_serial_number != commandInData->serial_number)
    {
      last_command_completed_serial_number = commandInData->serial_number;
      commands_executed++;
    }

  statusOutData->source_line = source_line;
  if (NULL != source_file)
    {
      snprintf(statusOutData->source_file, 64, "%s", source_file);
    }

  // write STATUS
  if (-1 == statusOut->write (statusOutData))
    {
      logError ("bad write to status (%d)\n", statusOut->error_type);
    }
}

void
NML_MODULE::write_commands_to_subordinates ()
{
  int t;
  // write subordinates
  for (t = 0; t < numSubordinates; t++)
    {
      if (subs[t]->commandOutData == NULL)
	{
	  continue;
	}
      if (subs[t]->commandOutData->type == 0)
	{
	  continue;
	}
      if (subs[t]->statusInData != NULL)
	{
	  if (subs[t]->statusInData->echo_serial_number ==
	      subs[t]->commandOutData->serial_number
	      && subs[t]->statusInData->echo_serial_number > 0
	      && subs[t]->modification_number <= 0)
	    {
	      subs[t]->commandOutData->type = 0;
	      continue;
	    }
	}
      if (-1 == subs[t]->commandOut->write (subs[t]->commandOutData))
	{
	  logError ("Error writing to %s (%d).\n",
		    subs[t]->commandOut->cms->BufferName,
		    subs[t]->commandOut->error_type);
	}
      else
	{
	  //if the subordinates  command buffer is queued then
	  // mark the command so it isn't sent out again.
	  if (subs[t]->commandOut->cms->queuing_enabled)
	    {
	      subs[t]->commandOutData->type = 0;
	    }
	}
    }
}

void
NML_MODULE::WRITE_COMM_BUFFERS (void)
{
  write_status_out ();
  write_commands_to_subordinates ();
}


//***********************************************************************************
//* Overhead ** Overhead ** Overhead ** Overhead **Overhead ** Overhead **Overhead **
//***********************************************************************************

void
NML_MODULE::controller (void)
{

  check_cycle_time_start ();
  READ_COMM_BUFFERS ();
  PRE_PROCESS ();

  /* reset state table flags */
  stateBegin = 1;
  matched = 0;

  if (commandInData != NULL)
    {
      if (statusOutData != NULL)
	{
	  if (statusOutData->command_type > 0)
	    {
	      DECISION_PROCESS ();
	    }
	}
    }

  POST_PROCESS ();
  WRITE_COMM_BUFFERS ();
  check_cycle_time_end ();

}

void
NML_MODULE::check_cycle_time_start ()
{
  start_run_time = etime ();
  cycles++;
  if (cycles < 2)
    {
      start_cycle_time = start_run_time;
    }
  else
    {
      last_cycle_time = start_run_time - last_start_run_time;
      if (last_cycle_time > max_cycle_time)
	{
	  max_cycle_time = last_cycle_time;
	}
      if (last_cycle_time < min_cycle_time)
	{
	  min_cycle_time = last_cycle_time;
	}
    }
  last_start_run_time = start_run_time;
}


void
NML_MODULE::check_if_new_command (void)
{
  if (NULL == commandInData)
    {
      return;
    }
  if (NULL == statusOutData)
    {
      return;
    }
  if (statusOutData->echo_serial_number != commandInData->serial_number)
    {
      state = NEW_COMMAND;
      status = RCS_EXEC;
      commands_received++;
      statusOutData->echo_serial_number = commandInData->serial_number;
      statusOutData->command_type = commandInData->type;
      command_time_averaged = 0;
      new_command_sequence = 1;
      new_line_num_sequence = 1;
//    exec_status.line_num = 0;
    }
}

void
NML_MODULE::print_statistics ()
{
  double total_time = stop_run_time - start_cycle_time;
  rcs_print ("\n*************************************************\n");
  if (NULL != proc_name)
    {
      rcs_print ("Module Name: %s\n", proc_name);
    }
  rcs_print ("Total cycles: %d\n", cycles);
  rcs_print ("Total time: %f\n", total_time);
  if (cycles > 0)
    {
      rcs_print ("Average Cycle Time: %f\n", total_time / cycles);
    }
  else
    {
      rcs_print ("Average Cycle Time: CAN NOT BE DETERMINED\n");
    }

  rcs_print ("Minimum Cycle Time: %f\n", min_cycle_time);
  rcs_print ("Max Cycle Time: %f\n", max_cycle_time);
  rcs_print ("Commands Received: %d\n", commands_received);
  if (total_time > 0)
    {
      rcs_print ("Commands Received per second: %f\n",
		 commands_received / total_time);
    }
  else
    {
      rcs_print ("Commands Received per second: CAN NOT BE DETERMINED\n");
    }

  if (total_time > 0)
    {
      rcs_print ("Load: %f%%\n", total_run_time * 100 / total_time);
    }
  else
    {
      rcs_print ("Load: CAN NOT BE DETERMINED\n");
    }

  rcs_print ("*************************************************\n");
}

void
NML_MODULE::check_cycle_time_end ()
{
  stop_run_time = etime ();
  total_run_time += stop_run_time - start_run_time;
}




void
NML_MODULE::set_file_and_line (char *_source_file, int _source_line)
{
  temp_file = _source_file;
  temp_line = _source_line;
}


/*  'stateBegin' and 'matched' are set to 1 and 0, respectively, by
    controller(). Calls to stateMatch() in the controller logic
    will use these to determine if the line number needs to be set to
    1 or incremented, and if matches have occurred so that only one
    state table line is executed per pass. */
int
NML_MODULE::stateMatch (char *_source_file, int _source_line, int st,
			int conds)
{
  set_file_and_line (_source_file, _source_line);
  return stateMatch (st, conds);
}

int
NML_MODULE::stateMatch (int st, int conds)
{
  if (matched)
    {
      return 0;
    }

  // check if this is the first line in the state table
  if (stateBegin)
    {
      if (NULL != statusOutData)
	{
	  statusOutData->line = 0;
	}
      source_line = -1;
      source_file = NULL;
      stateBegin = 0;
      if (state != st || !conds)
	{
	  temp_line = -1;
	  temp_file = NULL;
	}
    }
  else
    {
      // lines have gone before-- just increment
      if (NULL != statusOutData)
	{
	  statusOutData->line++;
	}
    }

  // see if we match state and conditions
  if (state == st && conds)
    {
      matched = 1;
      source_file = temp_file;
      source_line = temp_line;
      return 1;
    }
  else
    {
      // no match
      return 0;
    }
}

void
NML_MODULE::stateNext (int st)
{
  state = (RCS_STATE) st;
}

void
NML_MODULE::stateNext (RCS_STATE st)
{
  state = (RCS_STATE) st;
}

int
NML_MODULE::sendCommand (RCS_CMD_MSG * cmd_msg, int sub_num)
{
  if (sub_num >= numSubordinates || sub_num < 0)
    {
      return -1;
    }
  if (cmd_msg == NULL)
    {
      return -1;
    }
  if (cmd_msg->size <= 0 || cmd_msg->type <= 0)
    {
      return -1;
    }
  if (NULL == subs[sub_num])
    {
      return -1;
    }
  if (NULL == subs[sub_num]->statusInData)
    {
      return -1;
    }
  if (NULL == subs[sub_num]->commandOutData)
    {
      return -1;
    }
  if (NULL == subs[sub_num]->commandOut)
    {
      return -1;
    }
  if (NULL == subs[sub_num]->commandOut->cms)
    {
      return -1;
    }

  if (cmd_msg->size >= subs[sub_num]->commandOut->cms->size)
    {
      return -1;
    }
  memcpy (subs[sub_num]->commandOutData, cmd_msg, cmd_msg->size);
  subs[sub_num]->modification_number = 0;
  subs[sub_num]->commandOutData->serial_number =
    subs[sub_num]->statusInData->echo_serial_number + 1;
  return (0);
}

int
NML_MODULE::modifyCommand (RCS_CMD_MSG * cmd_msg, int sub_num)
{
  if (sub_num >= numSubordinates || sub_num < 0)
    {
      return -1;
    }
  if (cmd_msg == NULL)
    {
      return -1;
    }
  if (NULL == subs[sub_num])
    {
      return -1;
    }
  if (NULL == subs[sub_num]->commandOutData)
    {
      return -1;
    }
  cmd_msg->serial_number = subs[sub_num]->commandOutData->serial_number;
  memcpy (subs[sub_num]->commandOutData, cmd_msg, cmd_msg->size);
  subs[sub_num]->modification_number++;
  return (0);
}

void
NML_MODULE::loadDclock (double expiration)
{
  Dclock_expiration = expiration;
  Dclock_start_time = etime ();
}

int
NML_MODULE::checkDclock ()
{
  return (fabs (etime () - Dclock_start_time) < Dclock_expiration);
}
