
(module include mzscheme
  (require-for-syntax syntax/stx
		      racket/private/increader
		      "cm-accomplice.rkt")
  (require mzlib/etc)

  (define-for-syntax (resolve-path-spec fn loc stx build-path-stx)
    (let ([file
	   (syntax-case* fn (lib) module-or-top-identifier=?
	     [_
	      (string? (syntax-e fn))
	      (let ([s (syntax-e fn)])
		(unless (or (relative-path? s)
			    (absolute-path? s))
		  (raise-syntax-error
		   #f
		   "bad pathname string"
		   stx
		   fn))
		(string->path s))]
	     [(-build-path elem ...)
              (begin
                (collect-garbage)
                (module-identifier=? #'-build-path build-path-stx)
                (module-or-top-identifier=? #'-build-path build-path-stx))
	      (let ([l (syntax-object->datum (syntax (elem ...)))])
		(when (null? l)
		  (raise-syntax-error
		   #f
		   "`build-path' keyword is not followed by at least one string"
		   stx
		   fn))
		(apply build-path l))]
	     [(lib filename ...)
	      (let ([l (syntax-object->datum (syntax (filename ...)))])
		(unless (or (andmap string? l)
			    (pair? l))
		  (raise-syntax-error
		   #f
		   "`lib' keyword is not followed by a sequence of string datums"
		   stx
		   fn))
                (apply collection-file-path
                       (car l)
                       (if (null? (cdr l))
                           (list "mzlib")
                           (cdr l))))]
	     [else
	      (raise-syntax-error
	       #f
	       "not a pathname string, `build-path' form, or `lib' form for file"
	       stx
	       fn)])])
      (if (complete-path? file)
	  file
	  (path->complete-path
	   file
	   (cond
	    ;; Src of include expression is a path?
	    [(and (path? (syntax-source loc))
		  (complete-path? (syntax-source loc)))
	     (let-values ([(base name dir?) 
			   (split-path (syntax-source loc))])
	       (if dir?
		   (syntax-source loc)
		   base))]
	    ;; Load relative?
	    [(current-load-relative-directory)]
	    ;; Current directory
	    [(current-directory)])))))

  (define-syntax-set (do-include ; private
		      include-at/relative-to
		      include
		      include-at/relative-to/reader
		      include/reader)

    (define (do-include/proc stx)
      (syntax-case stx ()
	[(_ orig-stx ctx loc fn reader)
	 ;; Parse the file name
	 (let ([orig-c-file (resolve-path-spec (syntax fn) (syntax loc) (syntax orig-stx) #'build-path)]
	       [ctx (syntax ctx)]
	       [loc (syntax loc)]
	       [reader (syntax reader)]
	       [orig-stx (syntax orig-stx)]
               [rkt->ss (lambda (p)
                          (let ([b (path->bytes p)])
                            (if (regexp-match? #rx#"[.]rkt$" b)
                                (path-replace-suffix p #".ss")
                                p)))])

           (let ([c-file (if (file-exists? orig-c-file)
                             orig-c-file
                             (let ([p2 (rkt->ss orig-c-file)])
                               (if (file-exists? p2)
                                   p2
                                   orig-c-file)))])
             (register-external-file c-file)

             (let ([read-syntax (if (syntax-e reader)
                                    (reader-val
                                     (let loop ([e (syntax-object->datum
                                                    (local-expand reader 'expression null))])
                                       (cond
                                        [(reader? e) e]
                                        [(pair? e) (or (loop (car e))
                                                       (loop (cdr e)))]
                                        [else #f])))
                                    read-syntax)])
               (unless (and (procedure? read-syntax)
                            (procedure-arity-includes? read-syntax 2))
                 (raise-syntax-error
                  #f
                  "reader is not a procedure of two arguments"
                  orig-stx))

               ;; Open the included file
               (let ([p (with-handlers ([exn:fail?
                                         (lambda (exn)
                                           (raise-syntax-error
                                            #f
                                            (format
                                             "can't open include file (~a)"
                                             (if (exn? exn)
                                                 (exn-message exn)
                                                 exn))
                                            orig-stx
                                            c-file))])
                          (open-input-file c-file))])
                 (port-count-lines! p)
                 ;; Read expressions from file
                 (let ([content
                        (let loop ()
                          (let ([r (with-handlers ([exn:fail?
                                                    (lambda (exn)
                                                      (close-input-port p)
                                                      (raise-syntax-error
                                                       #f
                                                       (format
                                                        "read error (~a)"
                                                        (if (exn? exn)
                                                            (exn-message exn)
                                                            exn))
                                                       orig-stx))])
                                     (read-syntax c-file p))])
                            (if (eof-object? r)
                                null
                                (cons r (loop)))))])
                   (close-input-port p)
                   ;; Preserve src info for content, but set its
                   ;; lexical context to be that of the include expression
                   (let ([lexed-content
                          (let loop ([content content])
                            (cond
                             [(pair? content)
                              (cons (loop (car content))
                                    (loop (cdr content)))]
                             [(null? content) null]
                             [else
                              (let ([v (syntax-e content)])
                                (datum->syntax-object
                                 ctx
                                 (cond
                                  [(pair? v) 
                                   (loop v)]
                                  [(vector? v)
                                   (list->vector (loop (vector->list v)))]
                                  [(box? v)
                                   (box (loop (unbox v)))]
                                  [else
                                   v])
                                 content))]))])
                     (datum->syntax-object
                      (quote-syntax here)
                      `(begin ,@lexed-content)
                      orig-stx)))))))]))
    
    (define (include/proc stx)
      (syntax-case stx ()
	[(_ fn)
	 (with-syntax ([_stx stx])
	   (syntax/loc stx (do-include _stx _stx _stx fn #f)))]))

    (define (include-at/relative-to/proc stx)
      (syntax-case stx ()
	[(_ ctx loc fn)
	 (with-syntax ([_stx stx])
	   (syntax/loc stx (do-include _stx ctx loc fn #f)))]))
      
    (define (include/reader/proc stx)
      (syntax-case stx ()
	[(_ fn reader)
	 ;; Expand to do-include:
	 (with-syntax ([_stx stx])
	   (syntax/loc stx 
	     (do-include _stx _stx _stx fn 
			 (letrec-syntax ([the-reader (lambda (stx)
						       (datum->syntax-object
							#'here
							(make-reader reader)))])
			   the-reader))))]))
    
    (define (include-at/relative-to/reader/proc stx)
      (syntax-case stx ()
	[(_ ctx loc fn reader)
	 (with-syntax ([_stx stx])
	   (syntax/loc stx 
	     (do-include _stx ctx loc fn 
			 (letrec-syntax ([the-reader (lambda (stx)
						       (datum->syntax-object
							#'here
							(make-reader reader)))])
			   the-reader))))])))
  
  (provide include
	   include-at/relative-to
	   include/reader
	   include-at/relative-to/reader))
