#lang scheme/base
;; Simplified Simulation System

;; Event definition and scheduling

;; The event list is maintained in ascending order - the first item of
;; the list is the next event to be executed.
(define *event-list* '())

;; Each event has a time the event is to be executed, the function to
;; be executed, and the (evaluated) arguments to the function.
(define-struct event (time function arguments) #:mutable)

;; schedule: event -> void
;; Add an event to the event list.
(define (schedule event)
  (set! *event-list* (event-schedule event *event-list*)))

;; event-schedule: event x list of events -> list of events
;; Return a new list of events corresponding to the given avent added
;; to the given list of events.
(define (event-schedule event event-list)
  (cond ((null? event-list)
         (list event))
        ((< (event-time event)
            (event-time (car event-list)))
         (cons event event-list))
         (cons (car event-list)
               (event-schedule event (cdr event-list))))))

;; Simulation control routines

;; Global simulation control variables
(define *time* 0.0)            ; current simulation time
(define *event* #f)            ; currently executing event
(define *loop-exit* #f)        ; main loop exit continuation
(define *loop-next* #f)        ; main loop next continuation

;; wait/work: real -> void
;; Simulate the delay while work is being done.  Add an event to
;; execute the current continuation to the event list.
(define (wait/work delay)
  (let/cc continue
    ;; Reuse the current event - it would become garbage anyway
    (set-event-time! *event* (+ *time* delay))
    (set-event-function! *event* continue)
    (set-event-arguments! *event* '())
    (schedule *event*)
    ;; Done with this event
    (set! *event* #f)
    ;; Return to the main loop

;; start-simulation: -> void
;; This is the main simulation loop.  As long as there are events to
;; be executed (or the simulation is explicitly stopped): remove the
;; next event from the event list, advance the clock to the time of
;; the event, and apply the event's functions to it's arguments.
(define (start-simulation)
  (let/ec exit
    ;; Save the main loop exit continuation
    (set! *loop-exit* exit)
    ;; Main loop
    (let loop ()
      ;; Exit if no more events
      (when (null? *event-list*)
      (let/cc next
        ;; Save the main loop next continuation
        (set! *loop-next* next)
        ;; Execute the next event
        (set! *event* (car *event-list*))
        (set! *event-list* (cdr *event-list*))
        (set! *time* (event-time *event*))
        (apply (event-function *event*)
               (event-arguments *event*)))

;; stop-simulation: -> void
;; Stop the execution of the current simulation (by jumping to its
;; exit continuation).
(define (stop-simulation)

;; Random Distributions (to remove external dependencies)

;; random-float -> real
;; Returns a random real in (0.0, 1.0].
(define (random-float)
  (/ (exact->inexact (random 2147483647))

;; random-flat: real x real -> real
;; Returns a random real number from a uniform distribution between a
;; and b.
(define (random-flat a b)
  (+ a (* (random-float) (- b a))))

;; random-exponential: real -> real
;; Returns a random real number from an exponential distribution with
;; mean mu.
(define (random-exponential mu)
  (* (- mu) (log (random-float))))

;; Example Simulation Model

;; generator: integer -> void
;; Process to generate n customers arriving into the system.
(define (generator n)
  (do ((i 0 (+ i 1)))
      ((= i n) (void))
    (wait/work (random-exponential 4.0))
    (schedule (make-event *time* customer (list i)))))

;; customer: integer -> void
;; The ith customer into the system.  The customer is in the system
;; 2 to 10 minutes and then leaves.
(define (customer i)
  (printf "~a: customer ~a enters~n" *time* i)
  (wait/work (random-flat 2.0 10.0))
  (printf "~a: customer ~a leaves~n" *time* i))

;; run-simulation: integer
;; Run the simulation for n customers (or until explicitly stopped at
;; some specified time).
(define (run-simulation n)
  ;; Reset the time and the event list
  (set! *time* 0.0)
  (set! *event-list* '())
  ;; Schedule the customer generator
  (schedule (make-event 0.0 generator (list n)))
  ;; Stop the simulation at the specified time (optional)
  ;;(schedule (make-event 50.0 stop-simulation '()))
  ;; Start the simulation main loop

;; Run the simulation for 10 customers.
(run-simulation 10)