; withdraw an amount from the account 
; balance
(define (withdraw amount)
  (set! balance
        (- balance amount)))

(define (parallel-execute . args)
  (map thread args))

(define x 10)

; executes the two procedures in parallel
(parallel-execute
  (lambda () (set! x (* x x)))
  (lambda () (set! x (+ x 1))))

; if cell is already #t then return #t otherwise
; set it to #t and return #f
(define (test-and-set! cell)
  (if (car cell)
      #t
      (begin (set-car! cell #t) #f)))

; provides a cell that can be used for
; mutual exclusion purposes among processes
(define (make-mutex)
  (let ((cell (list #f)))
    (define (the-mutex m)
      (cond ((eq? m 'acquire)
             (if (test-and-set! cell)
                 (the-mutex ‘acquire))) ;retry
            ((eq? m 'release)
             (clear! cell))
            ((eq? m 'print) cell)))
    the-mutex))

(define (clear! Cell) (set-car! Cell #f))

; make a serializer by creating a mutex
; to be shared by the mutually exclusive
; procedures
(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)))

; create a bank account that is protected and
; allows parallel execution
(define (make-account balance)
  (define (withdraw amount)
    (if (>= balance amount)
        (begin (set! balance (- balance amount))
               balance)
        "Insufficient funds"))
  (define (deposit amount)
    (set! balance (+ balance amount))
    balance)
  (let ((s (make-serializer)))
    (define (dispatch m)
      (cond ((eq? m 'withdraw) (s withdraw))
            ((eq? m 'deposit) (s deposit))
            ((eq? m 'balance) balance)
            (else
             (error "unknown msg-MAKE-ACCOUNT"
                    m))))
    dispatch))

; procedure evens up two accounts by splitting the difference
(define (even-accounts account1 account2)
  (if (< (account1 'balance) (account2 'balance))
      (let ((temp account1))  ; swap accounts
        (set! account1 account2)
        (set! account2 temp)))
  (let ((amount (/ (- (account1 'balance)
                      (account2 'balance))
                   2)))
    ((account1 'withdraw) amount)
    ((account2 'deposit) amount)))


