//
//  Vrml 97 library
//  Copyright (C) 1998 Chris Morley
//
//  %W% %G%
//  VrmlNodeTimeSensor.cpp

#include "VrmlNodeTimeSensor.h"
#include "VrmlNodeType.h"

#include "MathUtils.h"


#include "VrmlScene.h"

// TimeSensor factory. Add each TimeSensor to the scene for fast access.

static VrmlNode *creator( VrmlScene *scene ) 
{
  return new VrmlNodeTimeSensor(scene);
}


// Define the built in VrmlNodeType:: "TimeSensor" fields

VrmlNodeType *VrmlNodeTimeSensor::defineType(VrmlNodeType *t)
{
  static VrmlNodeType *st = 0;

  if (! t)
    {
      if (st) return st;		// Only define the type once.
      t = st = new VrmlNodeType("TimeSensor", creator);
    }

  VrmlNodeChild::defineType(t);	// Parent class
  t->addExposedField("cycleInterval", VrmlField::SFTIME);
  t->addExposedField("enabled", VrmlField::SFBOOL);
  t->addExposedField("loop", VrmlField::SFBOOL);
  t->addExposedField("startTime", VrmlField::SFTIME);
  t->addExposedField("stopTime", VrmlField::SFTIME);
  t->addEventOut("cycleTime", VrmlField::SFTIME);
  t->addEventOut("fraction_changed", VrmlField::SFFLOAT);
  t->addEventOut("isActive", VrmlField::SFBOOL);
  t->addEventOut("time", VrmlField::SFTIME);

  return t;
}


VrmlNodeType *VrmlNodeTimeSensor::nodeType() const { return defineType(0); }


VrmlNodeTimeSensor::VrmlNodeTimeSensor( VrmlScene *scene ) :
  VrmlNodeChild(scene),
  d_cycleInterval(1.0),
  d_enabled(true),
  d_loop(false),
  d_startTime(0.0),
  d_stopTime(0.0),
  d_isActive(false),
  d_lastTime(-1.0)
{
  if (d_scene) d_scene->addTimeSensor(this);
}

VrmlNodeTimeSensor::~VrmlNodeTimeSensor()
{
  if (d_scene) d_scene->removeTimeSensor(this);
}

VrmlNode *VrmlNodeTimeSensor::cloneMe() const
{
  return new VrmlNodeTimeSensor(*this);
}


VrmlNodeTimeSensor* VrmlNodeTimeSensor::toTimeSensor() const
{
  return (VrmlNodeTimeSensor*) this;
}

void VrmlNodeTimeSensor::addToScene(VrmlScene *s, const char*)
{ if (d_scene != s && (d_scene = s) != 0) d_scene->addTimeSensor(this); }


ostream& VrmlNodeTimeSensor::printFields(ostream& os, int indent)
{
  if (! FPEQUAL(d_cycleInterval.get(), 1.0))
    PRINT_FIELD(cycleInterval);
  if (! d_enabled.get()) PRINT_FIELD(enabled);
  if (d_loop.get()) PRINT_FIELD(loop);
  if (! FPZERO(d_startTime.get())) PRINT_FIELD(startTime);
  if (! FPZERO(d_stopTime.get())) PRINT_FIELD(stopTime);

  return os;
}


//
// Generate timer events. If necessary, events prior to the timestamp (inTime)
// are generated to respect stopTimes and cycleIntervals. The timestamp
// should never be increased. This assumes the event loop delivers pending
// events in order (ascending time stamps). Should inTime be modified?
// Should ensure continuous events are delivered before discrete ones
// (such as cycleTime, isActive).

void VrmlNodeTimeSensor::update( VrmlSFTime &inTime )
{
  VrmlSFTime timeNow( inTime );

  if (d_enabled.get())
    {
      if ( d_lastTime > inTime.get() )
	d_lastTime = inTime.get();

      // Become active at startTime if either the valid stopTime hasn't
      // passed or we are looping.
      if (! d_isActive.get() &&
	  d_startTime.get() <= timeNow.get() &&
	  d_startTime.get() >= d_lastTime &&
	  ( (d_stopTime.get() < d_startTime.get() ||
	     d_stopTime.get() > timeNow.get()) ||
	    d_loop.get() ))
	{
	  d_isActive.set(true);
	  theSystem->debug("TimeSensor.%s isActive TRUE\n", name());

	  // Start at first tick >= startTime
	  eventOut( timeNow.get(), "isActive", d_isActive );

	  eventOut( timeNow.get(), "time", timeNow );
	  eventOut( timeNow.get(), "fraction_changed", VrmlSFFloat(0.0) );
	  eventOut( timeNow.get(), "cycleTime", timeNow );

	}

      // Running (active and enabled)
      else if ( d_isActive.get() )
	{
	  double f, cycleInt = d_cycleInterval.get();
	  bool deactivate = false;

	  // Are we done? Choose min of stopTime or start + single cycle.
	  if ( (d_stopTime.get() > d_startTime.get() &&
		d_stopTime.get() <= timeNow.get()) ||
	       ( (! d_loop.get()) &&
		 d_startTime.get() + cycleInt <= timeNow.get()) )
	    {
	      d_isActive.set(false);

	      // Must respect stopTime/cycleInterval exactly
	      if (d_startTime.get() + cycleInt < d_stopTime.get())
		timeNow = d_startTime.get() + cycleInt;
	      else
		timeNow = d_stopTime;

	      deactivate = true;
	    }

	  if (cycleInt > 0.0 && timeNow.get() > d_startTime.get())
	    f = fmod( timeNow.get() - d_startTime.get(), cycleInt );
	  else
	    f = 0.0;

	  // Fraction of cycle message
	  VrmlSFFloat fraction_changed( FPZERO(f) ? 1.0f : (float)(f / cycleInt) );
	  eventOut( timeNow.get(), "fraction_changed", fraction_changed );

	  // Current time message
	  eventOut( timeNow.get(), "time", timeNow );

	  // End of cycle message (this may miss cycles...)
	  if ( FPEQUAL(fraction_changed.get(), 1.0) )
	    eventOut( timeNow.get(), "cycleTime", timeNow );

	  if (deactivate)
	    eventOut( timeNow.get(), "isActive", d_isActive );
	}

      // Tell the scene this node needs quick updates while it is active.
      // Should check whether time, fraction_changed eventOuts are
      // being used, and set delta to cycleTime if not...
      if (d_isActive.get()) d_scene->setDelta( 0.0 );

      d_lastTime = inTime.get();

    }

}

// Ignore set_cycleInterval & set_startTime when active, deactivate
// if set_enabled FALSE is received when active.

void VrmlNodeTimeSensor::eventIn(double timeStamp,
				 const char *eventName,
				 const VrmlField *fieldValue)
{
  const char *origEventName = eventName;
  if ( strncmp(eventName, "set_", 4) == 0 )
    eventName += 4;

  theSystem->debug("TimeSensor.%s eventIn %s\n", name(), origEventName);

  // Ignore set_cycleInterval & set_startTime when active
  if ( strcmp(eventName,"cycleInterval") == 0 ||
       strcmp(eventName,"startTime") == 0 )
    {
      if (! d_isActive.get())
	{
	  setField(eventName, *fieldValue);
	  char eventOutName[256];
	  strcpy(eventOutName, eventName);
	  strcat(eventOutName, "_changed");
	  eventOut(timeStamp, eventOutName, *fieldValue);
	}
    }

  // Shutdown if set_enabled FALSE is received when active
  else if ( strcmp(eventName, "enabled") == 0 )
    {
      setField(eventName, *fieldValue);
      if ( d_isActive.get() && ! d_enabled.get() )
	{
	  d_isActive.set(false);

	  // Send relevant eventOuts (continuous ones first)
	  VrmlSFTime timeNow( timeStamp );
	  eventOut(timeStamp, "time", timeNow);

	  double f, cycleInt = d_cycleInterval.get();
	  if (cycleInt > 0.0)
	    f = fmod( timeNow.get() - d_startTime.get(), cycleInt );
	  else
	    f = 0.0;

	  // Fraction of cycle message
	  VrmlSFFloat fraction_changed( FPZERO(f) ? 1.0f : (float)(f / cycleInt) );

	  eventOut(timeStamp, "fraction_changed", fraction_changed);
	  eventOut(timeStamp, "isActive", d_isActive);
	}

      eventOut(timeStamp, "enabled_changed", *fieldValue);
    }

  // Let the generic code handle the rest.
  else
    VrmlNode::eventIn( timeStamp, origEventName, fieldValue );

  // TimeSensors shouldn't generate redraws.
  clearModified();
}

// Set the value of one of the node fields.

void VrmlNodeTimeSensor::setField(const char *fieldName,
				  const VrmlField &fieldValue)
{
  if TRY_FIELD(cycleInterval, SFTime)
  else if TRY_FIELD(enabled, SFBool)
  else if TRY_FIELD(loop, SFBool)
  else if TRY_FIELD(startTime, SFTime)
  else if TRY_FIELD(stopTime, SFTime)
  else
    VrmlNodeChild::setField(fieldName, fieldValue);
}

