//=========================================================================
// Summary  : */
// Filename : PeriodicTask.cc
// Author   : */
// Project  : */
// Revision : 1
// Created  : 2000/08/19
// Modified : 2000/08/19
//=========================================================================
// Description :
//=========================================================================
#include <sys/kernel.h>
#include "PeriodicTask.h"
#include "Syslog.h"
#define DefaultSampleInterval 2000

#define UntilInterrupt 1000000

Boolean PeriodicTask::_timerExpired = False;

PeriodicTask::PeriodicTask(const char *name)
  : DeviceDriver(name)
{
  Boolean debug = False;
  dprintf("PeriodicTask::PeriodicTask()\n");
  _maxInterval = 0;
  _accumulatedInterval = 0;
  computeSampleInterval();

  _eventService = new EventService("eventService", this);
}


PeriodicTask::~PeriodicTask()
{
  delete _eventService;
}


void PeriodicTask::computeSampleInterval()
{
  int minInterval = MAXINT;
  int i;
  CallbackList *list;
  
  _sampleInterval = DefaultSampleInterval;
  if (_callbackLists.size() == 0) {
    computeDelay();
    return;
  }

  for (i = 0; i < _callbackLists.size(); i++)
  {
    _callbackLists.get(i, &list);
  
    if (list->sampleInterval < minInterval)
      minInterval = list->sampleInterval;
    
    if (list->sampleInterval > _maxInterval)
      _maxInterval = list->sampleInterval;
  }

  // Find greatest integer by which all intervals are divisible
  for (int gcd = 2; gcd <= minInterval; gcd++)
  {
    Boolean allDivisible = True;
    
    for (i = 0; i < _callbackLists.size(); i++)
    {
      _callbackLists.get(i, &list);
      if (list->sampleInterval % gcd)
      {
	allDivisible = False;
	break;
      }
    }
    if (allDivisible)
      _sampleInterval = gcd;
  }
  
  computeDelay();
  return;
}


void PeriodicTask::computeDelay()
{
  Boolean debug = False;
  dprintf("PeriodicTask::computeDelay() for task %s: sampleInterval=%d\n",
	  name(), _sampleInterval);

  _delay.tv_sec = _sampleInterval / 1000;
  _delay.tv_nsec = (_sampleInterval % 1000) * 1000000;
}


int PeriodicTask::invokePeriodicCallbacks()
{
  Boolean debug = False;

  _accumulatedInterval += _sampleInterval;

  int nInvoked = 0;

  for (int i = 0; i < _callbackLists.size(); i++)
  {
    CallbackList *list;
    _callbackLists.get(i, &list);
    
    if (!(_accumulatedInterval % list->sampleInterval))
    {
      for (int j = 0; j < list->callbacks.size(); j++)
      {
	CallbackObj *callbackObj; 
	list->callbacks.get(j, &callbackObj);

	callMemberFunction(this, callbackObj->_callback)();
	nInvoked++;
      }
    }
  }

  if (_accumulatedInterval >= _maxInterval)
    _accumulatedInterval = 0;

  return nInvoked;
}



void PeriodicTask::addPeriodicCallback(unsigned int millisec,
				       CallbackMethod callback)
{
  int interval = millisec;

  CallbackObj *callbackObj = new CallbackObj(callback);
  CallbackList *list;

  for (int i = 0; i < _callbackLists.size(); i++)
  { 
    _callbackLists.get(i, &list);
    if (list->sampleInterval == interval)
    {
      // Check to see if callback is already assigned for this interval
      Boolean found = False;
      for (int j = 0; j < list->callbacks.size(); j++)
      {
	CallbackObj *cbObj;
	list->callbacks.get(j, &cbObj);
	if (cbObj->_callback == callback)
	{
	  found = True;
	  break;
	}
      }  
      if (!found)
      {
        list->callbacks.add(&callbackObj);
        return;
      }
      else 
      {
	// Callback already assigned for this interval
	return;
      }
    }
  }

  // New sampling interval
  list = new CallbackList();
  list->sampleInterval = interval;
  list->callbacks.add(&callbackObj);

  _callbackLists.add(&list);

  // Compute minimum sampling interval needed
  computeSampleInterval();
}


void PeriodicTask::run()
{
  Boolean debug = False;

  dprintf("PeriodicTask::run() (%s) - sampleInterval=%d, maxInterval=%d",
	  name(), _sampleInterval, _maxInterval);

  //I have no idea why this was happening, but it's scary
  //and utterly unneccessary.  Out it goes.
  //_eventTriggers = new EventTriggers(serverPid());

  startSampling();

  setTimer();

  while (True)
  {
    while (mode() == Task::Nominal)
    {
      // Use _timerExpired to determine if EventService::processEvent() 
      // returned because timer expired. (_timerExpired is set to True in
      // alarmSignalHandler())
      _timerExpired = False;

      // Need to enable ALARM signal handling here
      signal(SIGALRM, PeriodicTask::alarmSignalHandler);

      // Wait for event from other process. Returns when event is 
      // received and processed, or timer expires
      _eventService->processEvent();

      // Need to disable ALARM signal handling here
      signal(SIGALRM, SIG_IGN);

      if (_timerExpired)
	// Invoke periodic callback(s)
	invokePeriodicCallbacks();	
      
      // now we need to process Commands
      // of course we need to add a kludge for the tailcone
      // sigh
      if (!(_Child_Processes_Own_Commands))
	if (processCommand() != DeviceIF::Ok)
	  Syslog::write("PeriodicTask::run() - error processing Command!\n");
    }
  }
}


void PeriodicTask::startSampling()
{
  return;
}


void PeriodicTask::setTimer()
{
  // Describe signal info
  struct sigevent signalSpec;
  signalSpec.sigev_signo = SIGALRM;

  // Create the timer
  _timer = timer_create(CLOCK_REALTIME, &signalSpec);

  // Set the signal handler
  signal(SIGALRM, PeriodicTask::alarmSignalHandler);
  
  //////////////////////////////////////////////
  // Set the timer
  struct itimerspec timerSetting;

  timerSetting.it_value.tv_sec = _delay.tv_sec;
  timerSetting.it_value.tv_nsec = _delay.tv_nsec;
  timerSetting.it_interval.tv_sec = _delay.tv_sec;
  timerSetting.it_interval.tv_nsec = _delay.tv_nsec;

  timer_settime(_timer, 0, &timerSetting, 0);
}


void PeriodicTask::alarmSignalHandler(int signo)
{
  Boolean debug = False;
  dprintf("PeriodicTask::alarmSignalHandler()\n");

  _timerExpired = True;
}


void PeriodicTask::log(int millisec)
{
  // NOT IMPLEMENTED
  return;
}

