(module surrogate mzscheme
  (require (lib "class.ss"))

  (provide surrogate)
  
  (define-syntax (surrogate stx)
    
    (define (make-empty-method method-spec)
      (syntax-case method-spec (override augment)
	[(override name argspec ...)
         (identifier? (syntax name))
	 (make-empty-method-from-argspec #'name (syntax (argspec ...)))]
        [(augment def-expr name argspec ...)
         (identifier? (syntax name))
	 (make-empty-method-from-argspec #'name (syntax (argspec ...)))]))

    (define (make-empty-method-from-argspec name argspecs)
      (with-syntax ([(cases ...) (map make-empty-lambda-case
				      (syntax->list argspecs))]
		    [name name])
	(syntax
	 (begin
	   (define/public name
	     (case-lambda cases ...))))))
    
    (define (make-empty-lambda-case spec)
      (syntax-case spec ()
        [(id ...) (syntax [(ths super-call id ...) (super-call id ...)])]
        [id
         (identifier? (syntax id))
         (syntax [(ths super-call . name) (apply super-call name)])]))
    
    (define (make-overriding-method method-spec)
      (syntax-case method-spec (override augment)
	[(override name argspec ...)
	 (identifier? (syntax name))
	 (make-overriding-method-with-inner-default 
	  #'name #f #'(argspec ...))]
        [(augment def-expr name argspec ...)
	 (identifier? (syntax name))
	 (make-overriding-method-with-inner-default 
	  #'name #'def-expr #'(argspec ...))]))

    (define (make-overriding-method-with-inner-default name def-expr argspecs)
      ;; (not def-expr) => normal override
      ;; def-expr => beta override
      (let ([super-call-name
	     (datum->syntax-object 
	      name
	      (string->symbol
	       (string-append
		(if def-expr
		    "inner-proc-"
		    "super-proc-")
		(symbol->string
		 (syntax-object->datum
		  name)))))])
	(with-syntax ([(cases ...) 
		       (map (make-lambda-case name
					      super-call-name)
			    (syntax->list argspecs))]
		      [(super-proc-cases ...)
		       (map (make-super-proc-case name def-expr)
			    (syntax->list argspecs))]
		      [super-call-name super-call-name]
		      [name name]
		      [ren/inn (if def-expr
				   #'inner
				   #'rename)]
		      [define/override/fnl (if def-expr
					       #'define/augment
					       #'define/override)])
	  (syntax
	   (begin
	     (field [super-call-name
		     (case-lambda super-proc-cases ...)])
	     (define/override/fnl name
	       (case-lambda cases ...)))))))
    
    (define ((extract-id stx) method-spec)
      (syntax-case method-spec (override augment)
	[(override name argspec ...)
	 (identifier? #'name)
	 (syntax name)]
	[(augment result-expr name argspec ...)
	 (identifier? #'name)
	 (syntax name)]
	[else (raise-syntax-error 
	       #f
	       "bad method specification"
	       stx
	       method-spec)]))
    
    (define (make-super-proc-case name def-expr)
      (lambda (spec)
        (with-syntax ([name name])
          (syntax-case spec ()
	    ;; Not a rest arg: normal mode
            [(id ...) (quasisyntax [(id ...)
				    (#,@(if def-expr 
					    (list #'inner def-expr)
					    (list #'super))
				     name
				     id ...)])]
	    ;; A rest arg: take args as list
            [id 
             (identifier? (syntax id))
             (quasisyntax [(id) (#,@(if def-expr 
					(list #'inner def-expr)
					(list #'super))
				 name
				 . id)])]))))
    
    (define (make-lambda-case name super-call)
      (with-syntax ([name name]
                    [super-call super-call])
        (lambda (spec)
          (syntax-case spec ()
	    ;; Not a rest arg: normal mode for super-call
            [(id ...) (syntax [(id ...)
                               (if surrogate
                                   (send surrogate name this super-call id ...)
                                   (super-call id ...))])]
	    ;; A rest arg: super-class takes args as a list
            [id
             (identifier? (syntax id))
             (syntax [name 
                      (if surrogate
                          (send surrogate name this (lambda args (super-call args)) . id)
                          (super-call id))])]))))
    
    (syntax-case stx ()
      [(_ method-spec ...)
       (with-syntax ([(ids ...) (map (extract-id stx) (syntax->list (syntax (method-spec ...))))]
                     [(overriding-methods ...)
                      (map make-overriding-method 
                           (syntax->list
                            (syntax (method-spec ...))))]
                     [(empty-methods ...)
                      (map make-empty-method
                           (syntax->list
                            (syntax (method-spec ...))))])
         (syntax/loc stx
          (let ([surrogate<%>
                 (interface ()
                   on-disable-surrogate
                   on-enable-surrogate
                   ids ...)]
		[host<%> 
		 (interface ()
		   set-surrogate
		   get-surrogate
		   ids ...)])
            (values
             (lambda (super%)
               (class* super% (host<%>)
                 (field [surrogate #f])
                 (define/public (set-surrogate d)
                   (when surrogate
                     (send surrogate on-disable-surrogate this))
                   
                   ;; error checking
                   (when d
                     (unless (object? d)
                       (error 'set-surrogate "expected an object, got: ~e" d))
                     (let ([methods-to-impl '(on-enable-surrogate on-disable-surrogate ids ...)]
                           [i (object-interface d)])
                       (for-each (lambda (x) 
                                   (unless (method-in-interface? x i)
                                     (error 'set-surrogate "expected object to implement an ~s method" x)))
                                 methods-to-impl)))
                   
                   (set! surrogate d)
                   (when surrogate
                     (send surrogate on-enable-surrogate this)))
                 (define/public (get-surrogate) surrogate)
                 
                 overriding-methods ...
                 
                 (super-new)))
             host<%>

             (class* object% (surrogate<%>)
               (define/public (on-enable-surrogate x) (void))
               (define/public (on-disable-surrogate x) (void))
               empty-methods ...
               (super-new))
             surrogate<%>))))])))
