;;; sellers.scm
;; 1. Defs of serializer primitives.
;; 2. Defs for ticket selling exercise.
;; 3. Procedure to be modified: make-ticket-seller
;; 4. (test) illustrates the problem with the unserialized make-ticket-seller.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; 1. Definition of our parallel-execute and make-serializer primitives


(define (parallel-execute . args)
  (let ((threads (map thread args)))
       (for-each thread-wait threads)))
 
 
;; The serializing procedure
(define (make-serializer)
  (let ((mutex (make-mutex)))
    (lambda (p)
      (define (serialized-p . args)
        (mutex 'acquire)
        (let ((value (apply p args)))
          (mutex 'release)
          value))
      serialized-p)))
 

(define (make-mutex)
  (let ((cell (make-semaphore 1)))
    (define (the-mutex m)
      (cond ((eq? m 'acquire)
             (semaphore-wait cell))
            ((eq? m 'release)
             (semaphore-post cell))))
    the-mutex))

;;; Do not modify any of the above code.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 2.
;; Also do not modify this section.

(define (customer-order seats)
  (if (> seats 0); customer can buy
      (begin (sleep (random 2)) ;customer thinks for a while
             #t)  ;and then says "I'll take one ticket, please"
      #f) ; seller says "sorry, sold out."
)

;; To speed up testing, use this version of sleep rather than built-in one.
(define sleep-factor 2000)  ; make this number larger or smaller as needed
                            ; so that (test) doesn't take too long, but still
                            ; shows concurrency problems before you
                            ; modify make-ticket-seller
(define deep-sleep sleep)
(define (sleep k)
  (define (sleep-iter x) (if (> x 0) (sleep-iter (- x 1))))
  (sleep-iter (* k sleep-factor)) )

(define *SEATS-LEFT* 0)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 3. Create a ticket seller which, when started, continuously processes
;; customer orders until there are no seats left.  
;; Accesses and modifies global variable *SEATS-LEFT*.

(define (make-ticket-seller name)
  (let ((total-tickets-sold 0))
    (define (sell)
      (sleep (random 2)) ;sleep up to two seconds until customer arrives
      (if (customer-order *SEATS-LEFT*)
          (begin (set! *SEATS-LEFT* (- *SEATS-LEFT* 1)) ;record the sale
							;globally
		 (set! total-tickets-sold (+ total-tickets-sold 1)) ; local
								    ; record
                 (sell)) ;continue selling
          (print-tickets-sold name total-tickets-sold)
          ))
    sell))
 
;; Prints out how many ticks the ticket seller sold.
(define (print-tickets-sold name n)
        (display name) (display " sold ")
	(sleep (+ 3 (random 2))) ; show effect of delays during printing
        (display n)
 	(sleep (+ 3 (random 2))) ; show effect of delays during printing
        (display " tickets")
 	(sleep (+ 1 (random 2))) ; show effect of delays during printing
        (newline))
 

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 4.

;; Create 100 threads of ticket sellers
(define (test)
  (set! *SEATS-LEFT* 100)
  (parallel-execute 
    (make-ticket-seller "A") (make-ticket-seller "B")
    (make-ticket-seller "C") (make-ticket-seller "D")
    (make-ticket-seller "E") (make-ticket-seller "F")
    (make-ticket-seller "G") (make-ticket-seller "H")
    (make-ticket-seller "I") (make-ticket-seller "J")
  )
  (display "Total seats sold ")
  (display (- 100 *SEATS-LEFT*)) (newline) )

(display "To run the test: (test) ...try it several times. Note too many tickets usually are sold.")
(newline)

