
(module helpers mzscheme
  (require (lib "list.ss")
	   "uri.ss")
  (require-for-template mzscheme)

  (provide translate-import
	   translate-export
	   uri?)

  (define (uri? s)
    ;; Need a proper test here!
    #t)

  (define ((check-identifier stx) id)
    (unless (identifier? id)
      (raise-syntax-error
       #f
       "expected an identifier"
       stx
       id)))

  (define (check-present orig-i what nested !not exceptions names)
    (for-each (lambda (en)
		(unless (!not (ormap (lambda (i) (bound-identifier=? (car en) i))
				    names))
		  (raise-syntax-error
		   #f
		   (format "~a in nested ~a" what nested)
		   orig-i
		   (car en))))
	      exceptions))

  (define (add-prefix prefix id)
    (if prefix
	(datum->syntax-object id
			      (string->symbol
			       (format "~a~a" (syntax-e prefix) (syntax-e id)))
			      id)
	id))

  (define (locate-rename id renames)
    (cond
     [(null? renames) #f]
     [(bound-identifier=? id (caar renames)) (cdar renames)]
     [else (locate-rename id (cdr renames))]))

  (define (apply-rename new-names old-names name-pairs rcons)
    (map (lambda (i)
	   (or (ormap (lambda (new old)
			(and (bound-identifier=? (car i) new)
			     (rcons old (cdr i))))
		      new-names old-names)
	       i))
	 name-pairs))

  (define (remove-all-prefixes orig-i name-pairs form prefix)
    (let ([s (symbol->string (syntax-e prefix))])
      (map (lambda (i)
	     (let ([old (symbol->string (syntax-e (car i)))])
	       (unless (and ((string-length old) . >= . (string-length s))
			    (string=? s (substring old 0 (string-length s))))
		 (raise-syntax-error
		  #f
		  (format "~a does not have prefix ~s added by nested `prefix' form"
			  form
			  s)
		  orig-i
		  (car i)))
	       (cons (datum->syntax-object (car i)
					   (string->symbol (substring old (string-length s)))
					   (car i))
		     (cdr i))))
	   name-pairs)))		  

  (define (check-unique-names orig-i what names)
    (let ([dup (check-duplicate-identifier names)])
      (when dup
	(raise-syntax-error
	 #f
	 (format "duplicate ~a identifier" what)
	 orig-i
	 dup))))

  (define (localize i stx)
    (datum->syntax-object i (syntax-e stx)))

  (define (translate-import i)
    (define orig-i #`(import #,i))
    (syntax-case* i (for run expand) (lambda (a b) (eq? (syntax-e a) (syntax-e b)))
      [(for sub run expand)
       (finish-translate-import orig-i #'sub #t #t)]
      [(for sub expand run)
       (finish-translate-import orig-i #'sub #t #t)]
      [(for sub run)
       (finish-translate-import orig-i #'sub #t #f)]
      [(for sub expand)
       (finish-translate-import orig-i #'sub #f #t)]
      [(for . _)
       (raise-syntax-error
	#f
	"bad `for' form"
	orig-i
	i)]
      [_else
       (finish-translate-import orig-i i #t #f)]))

  (define (finish-translate-import orig-i i run? expand?)
    (define (mk-require l)
      (cond
       [(and run? expand?)
	#`(begin (require #,@l) (require-for-syntax #,@l))]
       [run?
	#`(require #,@l)]
       [expand?
	#`(require-for-syntax #,@l)]))
    (translate-impexp
     i orig-i
     (lambda (i exceptions onlys renames extra-prefix)
       ;; Found a base URI?
       (unless (and (string? (syntax-e i))
		    (uri? (syntax-e i)))
	 (raise-syntax-error
	  #f
	  "expected a URI string or an `only', `except', `add-prefix', or `rename' form"
	  orig-i
	  i))
       (let ([name (datum->syntax-object i (uri->module-path (syntax-e i)) i)])
	 (cond
	  [onlys
	   ;; Onlys are implemented with `rename':
	   (mk-require (map (lambda (name-pair)
			      #`(rename #,name #,(cdr name-pair) #,(car name-pair)))
			    onlys))]
	  [(or exceptions (pair? renames))
	   ;; First import non-renamed, then renamed:
	   (mk-require (cons
			(localize i #`(#,(if extra-prefix #'prefix-all-except #'all-except)
				       #,@(if extra-prefix (list extra-prefix) null)
				       #,name
				       #,@(append (map car (or exceptions null))
						  (map car renames))))
			(map (lambda (i)
			       #`(rename #,name #,(cdr i) #,(car i)))
			     renames)))]
	  [extra-prefix
	   (mk-require (list (localize i #`(prefix #,extra-prefix #,name))))]
	  [else
	   (mk-require (list name))])))))

  (define (translate-export i)
    (define orig-i #`(export #,i))
    #`(provide
       #,@(syntax-case* i (rename) (lambda (a b) (eq? (syntax-e a) (syntax-e b)))
	    [(rename ie ...)
	     (begin
	       (for-each (lambda (ie)
			   (syntax-case ie ()
			     [(int ext)
			      (for-each (lambda (i)
					  (unless (identifier? i)
					    (raise-syntax-error
					     #f
					     "expected an identifier for rename"
					     orig-i
					     i)))
					(list #'int #'ext))]))
			 (syntax->list #'(ie ...)))
	       #`((rename . ie) ...))]
	    [(rename . x)
	     (raise-syntax-error
	      #f
	      "bad rename clause"
	      i
	      orig-i)]
	    [_
	     (identifier? i)
	     (list i)]
	    [_else
	     (raise-syntax-error
	      #f
	      "expected an identifier or `rename' form"
	      orig-i
	      i)])))

  (define (translate-impexp i orig-i k)
    (let loop ([i i]
	       [exceptions #f]    ; #f if onlys
	       [onlys #f]         ; #f if exceptions
	       [renames null]     ; null if onlys
	       [extra-prefix #f]) ; #f if onlys, already folded into exceptions & renames
      (syntax-case* i (only except rename add-prefix) (lambda (a b) (eq? (syntax-e a) (syntax-e b)))
	[(only sub name ...)
	 (let ([names (syntax->list #'(name ...))])
	   (for-each (check-identifier i) names)
	   (check-unique-names orig-i "`only'" names)
	   (check-present orig-i "rename not" "`only' list" values renames names)
	   (cond
	    [exceptions
	     (check-present orig-i "except not" "`only' list" values exceptions names)
	     (loop #'sub
		   #f
		   (remove* exceptions (map (lambda (i) 
					      (cons i (or (locate-rename i renames)
							  (add-prefix extra-prefix i))))
					    names)
			    (lambda (a b) (bound-identifier=? (car a) (car b))))
		   null
		   #f)]
	    [onlys
	     (check-present orig-i "only not" "`only' list" values onlys names)
	     (loop #'sub #f onlys null #f)]
	    [else
	     (loop #'sub
		   #f
		   (map (lambda (i)
			  (cons i (or (locate-rename i renames)
				      (add-prefix extra-prefix i))))
			names)
		   null
		   #f)]))]
	[(only . _)
	 (raise-syntax-error
	  #f
	  "bad syntax"
	  i)]
	[(except sub name ...)
	 (let ([names (syntax->list #'(name ...))])
	   (for-each (check-identifier i) names)
	   (check-unique-names orig-i "`except'" names)
	   (check-present orig-i "rename" "`except' list" not renames names)
	   (let ([remove-exceptions
		  (lambda ()
		    (remove* exceptions renames (lambda (a b) (bound-identifier=? (car a) (car b)))))])
	     (cond
	      [(pair? exceptions)
	       (check-present orig-i "except" "`except' list" not exceptions names)
	       ;; union the exceptions
	       (loop #'sub
		     (append
		      (remove* exceptions (map (lambda (i) 
						 (cons i (or (locate-rename i renames)
							     (add-prefix extra-prefix i))))
					       names)
			       (lambda (a b) (bound-identifier=? (car a) (car b))))
		      exceptions)
		     #f
		     (remove-exceptions)
		     extra-prefix)]
	      [(pair? onlys)
	       (check-present orig-i "only" "`except' list" not onlys names)
	       (loop #'sub #f onlys null #f)]
	      [else
	       (loop #'sub
		     (map (lambda (i)
			    (cons i (add-prefix extra-prefix i)))
			  names) 
		     #f
		     (remove-exceptions)
		     extra-prefix)])))]
	[(except . _)
	 (raise-syntax-error
	  #f
	  "bad syntax"
	  i)]
	[(rename sub (new old) ...)
	 (let* ([new-names (syntax->list #'(new ...))]
		[old-names (syntax->list #'(old ...))]
		[name-pairs (map (lambda (old new)
				   (cons old (add-prefix extra-prefix new)))
				 old-names new-names)])
	   (for-each (check-identifier i) (apply append (map list new-names old-names)))
	   (check-unique-names orig-i "`rename' target" new-names)
	   (check-unique-names orig-i "`rename' source" old-names)
	   (let ([combine-renames
		  (lambda ()
		    (let ([renames (apply-rename new-names old-names renames cons)])
		      (append
		       renames
		       (remove* renames name-pairs 
				(lambda (a b)
				  (bound-identifier=? (car a) (car b)))))))])
	     (cond
	      [exceptions
	       (loop #'sub
		     (apply-rename new-names old-names exceptions cons)
		     #f
		     (combine-renames)
		     extra-prefix)]
	      [onlys
	       (loop #'sub
		     #f
		     (apply-rename new-names old-names onlys cons)
		     null
		     #f)]
	      [else
	       (loop #'sub
		     #f
		     #f
		     (combine-renames)
		     extra-prefix)])))]
	[(rename . _)
	 (raise-syntax-error
	  #f
	  "bad syntax"
	  i)]
	[(add-prefix sub prefix)
	 (cond
	  [onlys
	   (loop #'sub
		 #f
		 (remove-all-prefixes orig-i onlys "only" #'prefix)
		 null
		 #f)]
	  [else
	   (loop #'sub
		 (and exceptions
		      (remove-all-prefixes orig-i exceptions "except" #'prefix))
		 #f
		 (remove-all-prefixes orig-i renames "rename" #'prefix)
		 (add-prefix extra-prefix #'prefix))])]
	[(add-prefix . _)
	 (raise-syntax-error
	  #f
	  "bad syntax"
	  i)]
	[_else
	 (k i exceptions onlys renames extra-prefix)]))))
