/* Event-Driven Simulation Framework
   H. Conrad Cunningham
   Version #1: 31 March 2010

   This is a Scala translation of the C++ Discrete Event-Driven
   Simulation Framework given in section 21.2 of Timothy Budd's
    _An Introduction to Object-Oriented Programming_, Third
   Edition.

   Significant differences from the Budd C++ version:
   - use of scala.collection.mutable.PriorityQueue for the eventQueue
   - use of scala.util.Random for random integer generation and
     addition of methods rand and randBetween in singleton object Simulation 
     to work like Budd's C++ code
   - use of the Ordered trait for Event, with an appropriate definition to 
     enable the earliest times to have the highest priority
   - instance variables are made private

123456789012345678901234567890123456789012345678901234567890123456789012345678

*/


/* Class Event is the base class for all events in this event-driven
   simulation framework.  Its parameter "etime" denotes the simulation
   time at which the event is to be scheduled.  Event mixes in the
   Ordered trait so that the events can be totally ordered by
   simulation time for scheduling.  Ordered requires that a "compare"
   method be implemented.

   The behaviors of specific Event subtypes must be defined in a
   concrete implementation for method processEvent.
*/

abstract class Event(etime: Int) extends Ordered[Event] {

  if (etime < 0) 
    throw(new RuntimeException(
          "Attempt to create Event with negative time " + etime + "."))

  /* Method "processEvent" is the hook method for this event-driven
     simulation framework. It must be defined in a subclass of Event
     to give the specific behavior for a specific kind of event.
  */

  def processEvent


  /* Method "time" returns the simulation time for which this event is
     scheduled.
  */

  def time: Int = etime


  /* Method "compare" compares this.time (i.e., etime) to that.time.
     This is a method required for the Ordered trait.

     For this to work as needed with the PriorityQueue, items that
     have higher priority must be > items with lower priority. The
     highest priority items are the ones with SMALLER simulation
     times. Thus we need to give the comparison a polarity opposite
     from the normal integer ordering.  If this.time < that.time as
     integers, "compare" returns a positive number; if equal, it
     returns 0; and if this.time > that.time, it returns a negative
     number. 

  */

  def compare(that: Event): Int = that.time - etime
}


/* Class Simulation is the concrete simulation driver class in the
   event-driven simulation framework.
*/

import scala.collection.mutable.PriorityQueue

class Simulation(begin: Int) {

  private var curTime    = begin // current simulation time
  private val eventQueue = new PriorityQueue[Event] // future events queue

  def this() { this(0) }

  // Add newEvent to future events queue
  def scheduleEvent(newEvent: Event) { 
    eventQueue.enqueue(newEvent) 
    // println("After scheduleEvent " + 
    //        eventQueue.toList.mkString("eventQueue(", ",", ")"))
  }

  // Template method for simulation framework
  final def run {
    while (!eventQueue.isEmpty) {        // while more events to process
      val nextEvent = eventQueue.dequeue //   get nearest event
      curTime       = nextEvent.time     //   advance simulation clock
      // println("In run loop, curTime = " + curTime + ", next event = " 
      //         + nextEvent)
      nextEvent.processEvent             //   execute the event
    }
  }

  // Return the current simulation time
  def currentTime: Int = curTime

}


/* Singleton object Simulation holds various utilities that
   applications of the framework may need.  In particular, it holds the
   random number generator and implements the calls used by the code
   in Budd's application from his textbook.
*/

import scala.util.Random

object Simulation {

  private var random = new Random

  // Returns a random integer in range [0,range-1]
  def rand(range: Int): Int = random.nextInt(range)

  // Returns a random integer in range [first,last]
  def randBetween(first: Int, last: Int): Int = first + rand(last-first+1)

  // Create a new random number generator with the given Int "seed".
  def setRandSeed(seed: Int)  { random = new Random(seed) }

  // Create a new random number generator with the given Long "seed".
  def setRandSeed(seed: Long) { random = new Random(seed) }
 
}
