(module lock mzscheme
  (require (prefix wx: "kernel.ss"))
  (provide (protect as-entry
		    as-exit
		    entry-point
		    mk-param))

  ;; ;;;;;;;;;;;;; Thread Safety ;;;;;;;;;;;;;;;;;;;;
  
  ;; When the user creates an object or calls a method, or when the
  ;; system invokes a callback, many steps may be required to initialize
  ;; or reset fields to maintain invariants. To ensure that other
  ;; threads do not call methods during a time when invariants do not
  ;; hold, we force all of the following code to be executed in a single
  ;; threaded manner, and we temporarily disable breaks. This accompiled
  ;; with a single monitor: all entry points into the code use
  ;; `entry-point' or `as-entry', and all points with this code that
  ;; call back out to user code uses `as-exit'.

  ;; If an exception is raised within an `enter'ed area, control is
  ;; moved back outside by the exception handler, and then the exception
  ;; is re-raised. The user can't tell that the exception was caught an
  ;; re-raised. But without the catch-and-reraise, the user's exception
  ;; handler might try to use GUI elements from a different thread,
  ;; leading to deadlock.

  (define monitor-sema (make-semaphore 1))
  (define monitor-owner #f)

  ;; An exception may be constructed while we're entered:
  (define entered-err-string-handler
    (lambda (s n)
      (as-exit
       (lambda ()
	 ((error-value->string-handler) s n)))))

  (define old-paramz #f)
  (define old-break-paramz #f)

  (define exited-key (gensym 'as-exit))
  (define lock-tag (make-continuation-prompt-tag 'lock))

  (define (as-entry f)
    (cond
     [(eq? monitor-owner (current-thread))
      (f)]
     [else
      (with-continuation-mark 
          exited-key 
          #f
        (call-with-continuation-prompt
         (lambda ()
           (dynamic-wind
               (lambda () 
                 (wx:in-atomic-region monitor-sema)	     
                 (set! monitor-owner (current-thread)))
               (lambda () 
                 (set! old-paramz (current-parameterization))
                 (set! old-break-paramz (current-break-parameterization))
                 (parameterize ([error-value->string-handler entered-err-string-handler])
                   (parameterize-break 
                    #f
                    (call-with-exception-handler
                     (lambda (exn)
                       ;; Get out of atomic region before letting
                       ;;  an exception handler work
                       (if (continuation-mark-set-first #f exited-key)
                           exn ; defer to previous exn handler
                           (abort-current-continuation
                            lock-tag
                            (lambda () (raise exn)))))
                     f))))
               (lambda ()
                 (set! monitor-owner #f)
                 (set! old-paramz #f)
                 (set! old-break-paramz #f)
                 (semaphore-post monitor-sema)
                 (wx:in-atomic-region #f))))
         lock-tag
         (lambda (t) (t))))]))

  (define (as-exit f)
    ;; (unless (eq? monitor-owner (current-thread)) (error 'monitor-exit "not in monitored area"))
    (let ([paramz old-paramz]
	  [break-paramz old-break-paramz])
      (with-continuation-mark 
          exited-key 
          #t ; disables special exception handling
        (call-with-parameterization
         paramz
         (lambda ()
           (call-with-break-parameterization
            break-paramz
            (lambda ()
              (dynamic-wind
                  (lambda ()		
                    (set! monitor-owner #f)	 
                    (semaphore-post monitor-sema)
                    (wx:in-atomic-region #f))
                  f
                  (lambda ()
                    (set! old-paramz paramz)
                    (set! old-break-paramz break-paramz)
                    (wx:in-atomic-region monitor-sema)
                    (set! monitor-owner (current-thread)))))))))))

  (define-syntax entry-point 
    (lambda (stx)
      (syntax-case stx (lambda case-lambda)
	[(_ (lambda args body1 body ...))
	 (syntax (lambda args (as-entry (lambda () body1 body ...))))]
	[(_ (case-lambda [vars body1 body ...] ...))
	 (syntax (case-lambda 
		  [vars (as-entry (lambda () body1 body ...))]
		  ...))])))

  (define-syntax mk-param
    (lambda (stx)
      (syntax-case stx ()
	[(_ val filter check force-redraw)
	 (syntax
	  (case-lambda
	   [() val]
	   [(v) (check v)
	    (let ([v2 (filter v)])
	      (unless (eq? v2 val)
		(set! val v2)
		(force-redraw)))]))]))))

  
  
