/////////////////////////////////////////////////////////////////////////////
// run.cc
//
// SIMLIB version: 2.18
// Date: 2004-01-25
//
// Copyright (c) 1991-2004 Petr Peringer 
//
// This library is licensed under GNU Library GPL. See the file COPYING.
//

//
// description: control of simulation
//


////////////////////////////////////////////////////////////////////////////
// interface
////////////////////////////////////////////////////////////////////////////

#include "simlib.h"
#include "internal.h"


////////////////////////////////////////////////////////////////////////////
// implementation
////////////////////////////////////////////////////////////////////////////

SIMLIB_IMPLEMENTATION

////////////////////////////////////////////////////////////////////////////
//  SIMLIB global variables  ### move to extra module!!!
//
//  SIMLIB_*  --- internal variables
//  [A-Z]*    --- user-level variables
//

unsigned SIMLIB_version = __SIMLIB__;  // library version

// time-related variables
double SIMLIB_StartTime;       // time of simulation start
double SIMLIB_Time;            // simulation time
double SIMLIB_NextTime;        // next-event time
double SIMLIB_EndTime;         // time of simulation end

// read-only references to time variables
// ASSERTION: StartTime <= Time <= NextTime <= EndTime
const double & StartTime = SIMLIB_StartTime;	// time of simulation start
const double & Time      = SIMLIB_Time;  	// simulation time
const double & NextTime  = SIMLIB_NextTime;	// next-event time
const double & EndTime   = SIMLIB_EndTime;	// time of simulation end

// current entity pointer
Entity *SIMLIB_Current = NULL;
Entity *const &Current = SIMLIB_Current;	// read-only reference

// phase of simulation experiment
SIMLIB_Phase_t SIMLIB_Phase = START;
const SIMLIB_Phase_t & Phase = SIMLIB_Phase;	// read-only reference

////////////////////////////////////////////////////////////////////////////
// ETC...
long SIMLIB_StepCount = 0;		// ### for statistical purposes only


////////////////////////////////////////////////////////////////////////////
// private module variables

static bool StopFlag = false;           // if set, stop simulation run

////////////////////////////////////////////////////////////////////////////
// support for Delay blocks (internal)
//
DEFINE_HOOK(Delay);	// called in Run() and SampleDelays()
DEFINE_HOOK(DelayInit);	// called in Init()

// for explicit sampling
void SampleDelays() { // used at step-wise changes !!!###
  CALL_HOOK(Delay);
}

// ZDelays:
DEFINE_HOOK(ZDelayTimerInit); // called in Run()

////////////////////////////////////////////////////////////////////////////
// support for Petri net simulator (CPN) (internal)
//
#ifdef PN_SUPPORT
DEFINE_HOOK(PN1);  	// called in Run()
DEFINE_HOOK(PN2);  	// called in Run()
#endif

////////////////////////////////////////////////////////////////////////////
// support for simulation interrupt (user-level)
//
DEFINE_HOOK(Break);  	// called in Run()

void InstallBreak(void (*f)()) { // for user interface (in simlib.h)
    INSTALL_HOOK( Break, f );
}

////////////////////////////////////////////////////////////////////////////
// support for Samplers (internal)
//
DEFINE_HOOK(SamplerAct);	// called in Run()
DEFINE_HOOK(SamplerInit);	// called in Init()


// temporary #ifdef -- remove with old implementation ###
#ifdef NEW_WULIST

////////////////////////////////////////////////////////////////////////////
// WUclear hook --- if non-empty WUlist, call init function
//
DEFINE_HOOK(WUclear); // should be in run.cc

////////////////////////////////////////////////////////////////////////////
// SIMLIB_WUClear --- remove all items in WaitUntil list
//
void SIMLIB_WUClear() // should be removed -- ise CALL_HOOK instead
{
    CALL_HOOK(WUclear);
}

////////////////////////////////////////////////////////////////////////////
//
//
DEFINE_HOOK(WUget_next); 
////////////////////////////////////////////////////////////////////////////
// SIMLIB_DoActions --- central calling of interruptable procedures
// WARNING: SIMLIB_Current->_Run() should be called from this place only!
void SIMLIB_DoActions()
{
  // remove this:  ########### if(flag) SIMLIB_internal_error();
  do {
    SIMLIB_Current->_Run(); // perform event-dispatch
    SIMLIB_Current = 0;
    CALL_HOOK(WUget_next);  // check and activate next in WUlist
  }while( SIMLIB_Current != 0 );
  // remove this:  ########### if(flag) SIMLIB_internal_error();
}

#endif

////////////////////////////////////////////////////////////////////////////
//  Stop --- break of single simulation run
//
void Stop()
{
    dprintf(("\n ********************* STOP *********************\n"));
    if( SIMLIB_Phase != SIMULATION ) // if runs a simulation experiment
        SIMLIB_error(SFunctionUseError);
    StopFlag = true;
}


////////////////////////////////////////////////////////////////////////////
//  Abort --- end of simulation program
//
void Abort()
{
    dprintf(("\n ********************* ABORT *********************\n"));
    SIMLIB_Phase = ERROREXIT;
    exit(1); // end of program, errorcode 1
}


////////////////////////////////////////////////////////////////////////////
//  SIMLIB_Init --- initialization of simulation system and some parts of
//                  model; some checks
//  initialize:
//   - Time, StartTime, EndTime
//   - calendar (SQS)
//   - WaitUntilList
//   - existing continuous blocks, ...
//   - Delay blocks
//
void SIMLIB_Init(double T0, double T1, unsigned version)
{
  dprintf(("\n\t ************************* Init(%g,%g) \n",T0,T1));
  // first some checks
  if(version != SIMLIB_version) {  // check versions
    dprintf(("\n SIMLIB library version %x.%02x ",
              SIMLIB_version >> 8, SIMLIB_version & 0xFF));
    dprintf((" SIMLIB header version %x.%02x \n",
              version >> 8, version & 0xFF));
    SIMLIB_error(InconsistentHeader);  // exit program 
  }
  if( SIMLIB_Phase == INITIALIZATION ) SIMLIB_error(TwiceInitError);
  if( SIMLIB_Phase == SIMULATION ) SIMLIB_error(InitInRunError);
  SIMLIB_Phase = INITIALIZATION;
  /////////////////////////////////////////////////////////////////
  if( T0 < SIMLIB_MINTIME ) SIMLIB_error(InitError);
  if( T1 > SIMLIB_MAXTIME ) SIMLIB_error(InitError);
  if( T0 >= T1 )            SIMLIB_error(InitError);
  /////////////////////////////////////////////////////////////////
  // set simulation parameters
  _SetTime(StartTime,T0);
  _SetTime(Time,T0);
  _SetTime(EndTime,T1);

// set reasonable defaults for Step limits ??? 
// if not set by user first ### add flag
//  MaxStep = tend/100;
//  MinStep = MaxStep/1000;

  // ADD list of functions to call: (dynamically!!!, priority)
  // something like atexit(): atInit, atRunStart, atRunEnd

  SQS::Clear();                  // initialize calendar
  SIMLIB_WUClear();             // initialize WaitUntilList
  SIMLIB_ContinueInit();        // initialize status 1 ###
  //^^^^^^^^^^^^^^ I think it is not good ###!!!

  CALL_HOOK(SamplerInit);	// initialize all Samplers
  CALL_HOOK(DelayInit);		// initialize all Delays
}


////////////////////////////////////////////////////////////////////////////
// Run --- main simulation control
//
void Run() {
  dprintf(("\n\t ********** Run() --- START \n"));

  // first some checks
  if( SIMLIB_Phase != INITIALIZATION )
      SIMLIB_error(RunUseError); // bad use of Run()
  if( NextTime < StartTime ) 
      SIMLIB_internal_error();   // never reached ###???
 
  // welcome to the SIMLIB simulation control algorithm :-)

  // initialize variables
  SIMLIB_Phase = SIMULATION;
  StopFlag = false;               // flag for stop simulation
  SIMLIB_StepCount = 0;           // ### number of continuous steps

  // call init functions
  SIMLIB_ContinueInit();          // initialize status 2 ###
  
  CALL_HOOK(ZDelayTimerInit);	  // activate all ZDelayTimers
  CALL_HOOK(SamplerAct);	  // activate all Samplers
  CALL_HOOK(Break);               // user can stop simulation by any key?

#ifdef PN_SUPPORT // CPN extension
  CALL_HOOK(PN1);
  CALL_HOOK(PN2);
#endif

  // main loop
  while( Time < EndTime && !StopFlag )  {
      int endFlag = NextTime > EndTime; // !!! ### achtung !!!
      if( endFlag ) 
          _SetTime( NextTime, EndTime ); // limit NextTime to EndTime
      if( NextTime > Time )  {  // no event at Time
          if( IntegratorContainer::isAny() || StatusContainer::isAny() ) {
              // there are integrators or status variables, so we enter
              // -------------- CONTINUOUS SIMULATION ---------------
              SIMLIB_ResetStatus = true;   // don't use previous step buffers
	                                   // is really needed always ????
	      CALL_HOOK(Delay);            // DELAY: sample input
              while( NextTime > Time )  {  // 'mini' steps
                                           // is not scheduled event or end ...
                  IntegrationMethod::StepSim(); // *** continuous sim. step ***
                  SIMLIB_StepCount++;      // some statistics ###??? 
                  SIMLIB_DoConditions();   // perform state events
		  CALL_HOOK(Delay);        // DELAY: sample input
                  CALL_HOOK(Break); // user can stop simulation by any key?
                  if(StopFlag) // ### is possible !!!
	              break;
              }
          } else { // no integrators, status blocks, ...
              _SetTime( Time, NextTime ); // set next event reactivation time
#             ifdef PN_SUPPORT // CPN extension
              CALL_HOOK(PN2);
#             endif
          }
      } // if (NextTime>Time)
      //CONDITION: is event in Time OR end of simulation
      if( endFlag )  break; // end of simulation ???### (pozor na podminky=>planovani!)
      if( StopFlag ) break; // ###??? must be here ???
      while( Time >= NextTime && !SQS::Empty() ) { // there are events at Time
          SIMLIB_Current = SQS::Current();   // get first record from calendar
          SIMLIB_DoActions();  // perform actions (see waitunti.cc)
          SIMLIB_Current = NULL;            // ###clear current activity pointer
#         ifdef PN_SUPPORT // CPN extension
	  //###!!! move after loop !!! ??? why here? ask Vladimir!
          if( SQS::Empty() || Time < NextTime )
            CALL_HOOK(PN1);
#         endif
          CALL_HOOK(Break); // user can stop simulation by any key?
          if( StopFlag ) 
	     break;
        }
  } // main loop
  IntegrationMethod::IntegrationDone(); // terminate integration run
  SIMLIB_Phase = TERMINATION;
  dprintf(("\n\t ********** Run() --- END \n"));
}

// end of module

