(declare (usual-integrations))
(declare (integrate-external "stable/sim" "queue"))

;; In this communications model it takes MESSAGE-DELAY units of time for
;; a message to be transmitted.  For a message to be detected its start
;; must be clear of any overlaps with other messages.  If several (two
;; or more) messages have any overlap then the first is delivered as
;; 'COLLISION and the subsequent messages are lost.

;; If a `clean' message is transmitted during the execution of the
;; interrupt handler then it is lost.
;;

;; The comms slot is used on the receiver as follows. The scheme implicitly
;; assumes that all messages are exactly the same duration.  At
;; broadcast time, a delivery thunk is placed in the agenda to be executed 
;; message-delay units later.

;; At delivery time the status of primitive-message-event is inspected.  If it
;; is signaled, then the delivery thunk reschedules itself for delivery at a 
;; later time.  If primitive-message-event is not signaled, then the delivery
;; thunk removes the first element from the internal message queue and signals
;; primitive-message-event with it.

;; This communications model requires an agenda based scheduler
;; (e.g. simrun2)

(define message-delay 100)
(define delivery-poll-delay 10)

(define (%broadcast cont fuel store message)
  (let* ((my-id (store.index store))
	 (sim   (store.simulation store))
	 (neighbours (vector-ref (simulation.neighbours sim) my-id)))

    (define-integrable (add-message! rcv-comms-state msg)
      (queue/add! rcv-comms-state msg))

    (define-integrable (remove-message! rcv-comms-state)
      (queue/remove! rcv-comms-state))

    (define (delivery)
      ;; Use interrupt/1 as a reference/assignment cache
      (if (not (simulation.interrupt/1 sim))
	  (set-simulation.interrupt/1!
	   sim
	   (make-cache 'PRIMITIVE-MESSAGE-EVENT)))
      (let ((event-cache (simulation.interrupt/1 sim)))
	(define (transmission-ended index)
	  (let ((store
		 (processor.store
		  (vector-ref (simulation.processors sim) index))))
	    (let ((event (global-ref store event-cache))
		  (comms-state (vector-ref store store:comms)))
	      (define (try-to-dequeue)
		(if (event.signalled? event)
		    (simulation.enqueue+ sim delivery-poll-delay
					 try-to-dequeue)
		    (let ((msg (remove-message! comms-state)))
		      (notify-delivery sim my-id msg)
		      (event.signal! event msg))))
	      (try-to-dequeue))))
	
	(for-each-vector-element neighbours transmission-ended)))

    (notify-broadcast sim my-id message)

    (for-each-vector-element
     neighbours
     (lambda (index)
       (let ((store
	      (processor.store
	       (vector-ref (simulation.processors sim) index))))
	 (let ((comms-state (vector-ref store store:comms)))
	   (add-message! comms-state message)))))

    (simulation.enqueue+ sim message-delay delivery)
      
    (cont unspecific (- fuel 1) store)))

;;; See above for useful definition of initial-comms-slot-value
(define (initial-comms-slot-value index)
  index					; ignored
  (make-empty-queue 'messages))

(define (%channel-available? store)
  #t)

(define (notify-broadcast sim my-id message)
  sim ; ignored
  (pp `(broadcast ,my-id ,message)))

(define (notify-delivery sim my-id message)
  sim ; ignored
  (pp `(delivery ,my-id ,message)))
