
(module class-events mzscheme

  (require-for-syntax mzlib/stxparam)
  (require mzlib/stxparam)

  (provide current-class-event-handler
           define-traced
           trace-begin
           trace
           initialize-call-event
           finalize-call-event
           new-event
           inspect-event
           set-event
           get-event
           )

  (define current-class-event-handler
    (make-parameter void))

  ;; ----------------------------------------------------------------------
  ;; Definitions for traced vs untraced functions
  ;; ----------------------------------------------------------------------

  (define-syntax-parameter trace? #f)

  (define-syntax trace
    (make-set!-transformer
     (lambda (stx)
       (raise-syntax-error
        #f
        "trace used outside a trace-begin"
        stx))))

  (define-syntax (trace-begin stx)
    (syntax-case stx (trace)
      [(form (trace expr) ...)
       (raise-syntax-error
        #f
        "traced block has no non-trace code"
        stx)]
      [(form expr0 (trace expr) ...)
       (if (syntax-parameter-value (syntax trace?))
           (syntax/loc stx (begin0 expr0 expr ...))
           (syntax expr0))]
      [(form (trace expr) rest ...)
       (if (syntax-parameter-value (syntax trace?))
           (syntax/loc stx (begin expr (form rest ...)))
           (syntax/loc stx (form rest ...)))]
      [(form expr rest ...)
       (syntax/loc stx (begin expr (form rest ...)))]))

  (define-syntax (define-traced stx)
    (syntax-case stx ()
      [(form (name . args) . body)
       (syntax/loc stx (form name (lambda args . body)))]
      [(form name body ...)
       (with-syntax ([name-traced
                      (datum->syntax-object
                       (syntax name)
                       (string->symbol
                        (string-append
                         (symbol->string (syntax-e (syntax name)))
                         "-traced"))
                       (syntax name))])
         (syntax/loc stx
           (begin
             (define name
               (syntax-parameterize ([trace? #f])
                 body ...))
             (define name-traced
               (syntax-parameterize ([trace? #t])
                 body ...)))))]))

  (define current-class-event-stack
    (make-parameter null))

  (define (initialize-event event . args)
    (current-class-event-stack
     (cons
      (apply (current-class-event-handler) event args)
      (current-class-event-stack))))

  (define (finalize-event event . args)
    (let* ([stack (current-class-event-stack)]
           [head (car stack)]
           [tail (cdr stack)])
      (when (procedure? head) (apply head args))
      (current-class-event-stack tail)))

  (define (new-event class obj fields)
    ((current-class-event-handler) 'new class obj fields))

  (define (initialize-call-event obj method args)
    (initialize-event 'call obj method args))

  (define (finalize-call-event . returned)
    (apply finalize-event 'call returned)
    (apply values returned))

  (define (inspect-event obj)
    ((current-class-event-handler) 'inspect obj))

  (define (set-event obj field value)
    ((current-class-event-handler) 'set obj field value))

  (define (get-event obj field)
    ((current-class-event-handler) 'get obj field))

  )
