(module to-core scheme/base
  (require syntax/kerncase
           syntax/stx
           mzlib/list
           syntax/boundmap
           (for-syntax scheme/base))

  (provide top-level-to-core)

  ;; `module', `require', and `require-for-syntax' declarations must
  ;;  not be embedded in a `begin' sequence. For `require' and
  ;;  `require-for-syntax', it's a timing issue. For `module', it's
  ;;  because the transformation can only handle a single `module'
  ;;  declaration.
  (define (top-level-to-core stx lookup-stx set-stx safe-vector-ref-stx extract-stx 
			     simple-constant? stop-properties)
    (syntax-case stx (module begin)
      [(module m lang (plain-module-begin decl ...))
       (let-values ([(expr new-decls magic-sym) 
                     (lift-sequence (flatten-decls (syntax->list #'(decl ...)))
                                    lookup-stx set-stx safe-vector-ref-stx extract-stx
                                    #t
				    simple-constant? stop-properties)])
         (values (expand-syntax expr)
                 #`(module m lang (#%plain-module-begin #,@new-decls))
		 magic-sym))]
      [(begin decl ...)
       (let-values ([(expr new-decls magic-sym) 
                     (lift-sequence (flatten-decls (syntax->list #'(decl ...)))
                                    lookup-stx set-stx safe-vector-ref-stx extract-stx
                                    #f
				    simple-constant? stop-properties)])
         (values (expand-syntax expr)
                 #`(begin #,@new-decls)
		 magic-sym))]
      [else
       (top-level-to-core #`(begin #,stx) lookup-stx set-stx safe-vector-ref-stx extract-stx
			  simple-constant? stop-properties)]))
  
  (define (flatten-decls l)
    (apply append
           (map (lambda (stx)
                  (syntax-case stx (begin)
                    [(begin . e)
                     (flatten-decls (syntax->list #'e))]
                    [else (list stx)]))
                l)))
  
  (define-struct lifted-info ([counter #:mutable] id-map slot-map))
  
  (define (make-vars)
    (make-lifted-info 
     0
     (make-module-identifier-mapping)
     (make-hash)))
  
  (define (is-id-ref? v)
    (or (identifier? v)
        (and (stx-pair? v)
             (identifier? (stx-car v))
             (free-identifier=? #'#%top (stx-car v)))))
  
  (define (vars-sequence li)
    (let loop ([i 0])
      (if (= i (lifted-info-counter li))
          null
          (cons (let ([v (hash-ref (lifted-info-slot-map li) i)])
                  (if (is-id-ref? v)
                      #`(#%variable-reference #,v)
                      v))
                (loop (add1 i))))))
  
  (define (extract-vars li vec-id extract-stx)
    (let loop ([i 0])
      (if (= i (lifted-info-counter li))
          null
          (let ([v (hash-ref (lifted-info-slot-map li) i)])
            (if (is-id-ref? v)
                (cons #`(#,extract-stx #,vec-id #,i)
                      (loop (add1 i)))
                (loop (add1 i)))))))
  
  (define (is-run-time? stx)
    (not (and (stx-pair? stx)
              (or (free-identifier=? #'define-syntaxes (stx-car stx))
                  (free-identifier=? #'define-values-for-syntax (stx-car stx))))))

  (define (has-symbol? decl magic-sym table)
    (cond
     [(hash-ref table decl (lambda () #f))
      ;; cycle/graph
      #f]
     [else
      (hash-set! table decl #t)
      (cond
       [(eq? magic-sym decl)
	#t]
       [(pair? decl)
	(or (has-symbol? (car decl) magic-sym table)
	    (has-symbol? (cdr decl) magic-sym table))]
       [(vector? decl)
	(has-symbol? (vector->list decl) magic-sym table)]
       [(box? decl)
	(has-symbol? (unbox decl) magic-sym table)]
       [else
	#f])]))

  (define (generate-magic decls)
    (let ([magic-sym (string->symbol (format "magic~a~a" 
					     (current-seconds)
					     (current-milliseconds)))])
      (if (has-symbol? (map syntax->datum decls) magic-sym (make-hasheq))
	  (generate-magic decls)
	  magic-sym)))

  (define (need-thunk? rhs)
    (not (and (stx-pair? rhs)
	      (or (free-identifier=? #'lambda (stx-car rhs))
		  (free-identifier=? #'case-lambda (stx-car rhs))))))

  (define (lift-sequence decls lookup-stx set-stx safe-vector-ref-stx extract-stx 
			 in-module? simple-constant? stop-properties)
    (let ([ct-vars (make-vars)]
          [rt-vars (make-vars)]
          [compile-time (datum->syntax #f (gensym 'compile-time))]
          [run-time (datum->syntax #f (gensym 'run-time))]
	  [magic-sym (generate-magic decls)]
	  [magic-indirect (gensym)])
      (let ([ct-converted 
             (map (lambda (stx)
                    #`(lambda ()
                        #,(syntax-case stx ()
                            [(def ids rhs)
                             (let ([cvted (convert #'rhs #t 
                                                   lookup-stx set-stx safe-vector-ref-stx
                                                   compile-time ct-vars
                                                   in-module?
						   simple-constant? stop-properties)])
                               (if (and (not in-module?)
                                        (free-identifier=? #'def #'define-syntaxes))
                                   ;; Don't try to name macro procedures, because it
                                   ;;  inteferes with the 0-values hack at the top level
                                   cvted
                                   #`(let-values ([ids #,cvted])
                                       (#%plain-app values . ids))))])))
                  (filter (lambda (x) (not (is-run-time? x))) decls))]
            [rt-converted
             (map (lambda (stx)
		    (syntax-case stx (define-values 
                                       #%provide
                                       #%require)
		      [(#%provide . _)
		       #'void]
		      [(#%require . _)
		       #'void]
		      [(define-values ids rhs)
		       (let ([converted (convert #'rhs #f
						 lookup-stx set-stx safe-vector-ref-stx
						 run-time rt-vars
						 in-module?
						 simple-constant? stop-properties)])
			 (if (need-thunk? #'rhs)
			     #`(lambda () #,converted)
			     #`(let-values ([ids #,converted])
				 (#%plain-app values . ids))))]
		      [else
		       #`(lambda ()
			   #,(convert stx #f 
				      lookup-stx set-stx safe-vector-ref-stx
				      run-time rt-vars
				      in-module?
				      simple-constant? stop-properties))]))
		  (filter is-run-time? decls))]
            [ct-rhs #`(#%plain-app
                       (let-values ([(magic) (#%plain-app car (#%plain-app cons '#,magic-sym 2))])
                         (if (#%plain-app symbol? magic) 
                             (#%plain-lambda (x) 
                               (#%plain-app
                                vector
                                #,@(map (lambda (stx)
                                          (syntax-case stx ()
                                            [(def (id) . _)
                                             #'void]
                                            [(def (id ...) . _)
                                             (with-syntax ([(v ...) (map (lambda (x) #f)
                                                                         (syntax->list #'(id ...)))])
                                               
                                               #`(#%plain-lambda () (#%plain-app values v ...)))]))
                                        (filter (lambda (x) (not (is-run-time? x))) decls))))
                             (#%plain-app car magic)))
                       (#%plain-app vector #,@(vars-sequence ct-vars)))]
            [rt-rhs #`(#%plain-app (#%plain-app cdr '#,magic-sym)
                                   (#%plain-app vector #,@(vars-sequence rt-vars)))]
            [just-one-ct? (>= 1 (apply +
                                       (map (lambda (decl)
                                              (syntax-case decl (define-syntaxes define-values-for-syntax)
                                                [(define-values-for-syntax . _) 1]
                                                [(define-syntaxes . _) 1]
                                                [_else 0]))
                                            decls)))]
            [just-one-rt? (>= 1 (apply +
                                       (map (lambda (decl)
                                              (syntax-case decl (define-values #%provide #%require 
                                                                  define-syntaxes define-values-for-syntax)
                                                [(#%provide . _) 0]
                                                [(#%require . _) 0]
                                                [(define-values-for-syntax . _) 0]
                                                [(define-syntaxes . _) 0]
                                                [_else 1]))
                                            decls)))])
        (values
         #`(#%plain-app
            cons (#%plain-lambda (#,compile-time)
                   #,@(extract-vars ct-vars compile-time extract-stx)
                   (#%plain-app vector #,@ct-converted))
            (#%plain-lambda (#,run-time)
              #,@(extract-vars rt-vars run-time extract-stx)
              (#%plain-app vector #,@rt-converted)))
         #`(;; Lift require and require-for-syntaxes to the front, so they're ready for
	    ;;  variable references
	    #,@(filter (lambda (decl)
			 (syntax-case decl (#%require)
			   [(#%require . _) #t]
			   [_else #f]))
		       decls)
	    ;; Lift define-for-values binding to front, so they can be referenced
            ;;  in compile-time definition
            #,@(let ([ids (apply
                           append
                           (map (lambda (stx)
                                  (syntax-case stx (define-values-for-syntax)
                                    [(define-values-for-syntax ids . _)
                                     (syntax->list #'ids)]
                                    [_else null]))
                                decls))])
                 (if (null? ids)
                     null
                     #`((define-values-for-syntax #,ids
                          (values #,@(map (lambda (x) #'#f) ids))))))
            #,@(if just-one-ct?
                   null
                   #`((define-values-for-syntax (#,compile-time) #,ct-rhs)))
            #,@(if just-one-rt?
                   null
                   #`((define-values (#,run-time) #,rt-rhs)))
            #,@(let loop ([decls decls][ct-pos 0][rt-pos 0])
                 (cond
                   [(null? decls) null]
                   [(is-run-time? (car decls))
                    (cons (syntax-case (car decls) (define-values #%provide #%require)
                            [(#%provide . _)
                             (car decls)]
                            [(#%require . _)
                             #'(#%plain-app void)]
                            [(define-values (id ...) rhs)
			     #`(define-values (id ...)
				 #,(let ([lookup #`(#%plain-app vector-ref #,(if just-one-rt? rt-rhs run-time) #,rt-pos)])
				     (if (need-thunk? #'rhs)
					 #`(#%plain-app #,lookup)
					 lookup)))]
                            [else
                             #`(#%plain-app (#%plain-app vector-ref #,(if just-one-rt? rt-rhs run-time) #,rt-pos))])
                          (loop (cdr decls) ct-pos (add1 rt-pos)))]
                   [else
                    (cons (syntax-case (car decls) (define-syntaxes define-values-for-syntax)
                            [(define-syntaxes (id ...) . rhs)
                             #`(define-syntaxes (id ...)
                                 (#%plain-app (#%plain-app vector-ref #,(if just-one-ct? ct-rhs compile-time) #,ct-pos)))]
                            [(define-values-for-syntax (id ...) . rhs)
                             #`(define-values-for-syntax ()
                                 (begin
                                   (set!-values (id ...) 
                                                (#%plain-app
                                                 (#%plain-app vector-ref #,(if just-one-ct? ct-rhs compile-time) 
                                                              #,ct-pos)))
                                   (#%plain-app values)))])
                          (loop (cdr decls) (add1 ct-pos) rt-pos))])))
	 magic-sym))))

  (define (local-identifier? stx trans?)
    (eq? 'lexical ((if trans?
		      identifier-transformer-binding
		      identifier-binding)
		   stx)))

  (define (simple-identifier stx trans?)
    (let ([b ((if trans?
                  identifier-transformer-binding
                  identifier-binding)
              stx)])
      (cond
       [(eq? b 'lexical) stx]
       [(and (pair? b)
	     (eq? '#%kernel (car b)))
	;; Generate a syntax object that has the right run-time binding:
	(datum->syntax #'here (cadr b) stx stx)]
       [else #f])))

  (define (add-literal/pos stx li)
    (let ([pos (lifted-info-counter li)])
      (hash-set! (lifted-info-slot-map li) pos stx)
      (set-lifted-info-counter! li (add1 pos))
      pos))
  
  (define (add-literal stx li safe-vector-ref-stx id)
    #`(#,safe-vector-ref-stx #,id #,(add-literal/pos stx li)))
  
  (define (add-identifier/pos stx li trans?)
    (if (identifier? stx)
        ;; id :
        (or (module-identifier-mapping-get (lifted-info-id-map li)
                                           stx 
                                           (lambda () #f))
            (let ([pos (add-literal/pos (if (not ((if trans?
                                                      identifier-transformer-binding
                                                      identifier-binding)
                                                  stx))
                                            #`(#%top . #,stx)
                                            stx)
                                        li)])
              (module-identifier-mapping-put! (lifted-info-id-map li) stx pos)
              pos))
        ;; (#%top . id) :
        (add-literal/pos stx li)))

  (define (add-identifier stx li trans? lookup-stx id)
    #`(#,lookup-stx #,id #,(add-identifier/pos stx li trans?)))

  (define-syntax quasisyntax/loc+props
    (syntax-rules ()
      [(_ stx e) (let ([old-s stx]
		       [new-s (quasisyntax e)])
                   (datum->syntax new-s
                                  (syntax-e new-s)
                                  old-s
                                  old-s))]))
  (define code-insp (current-code-inspector))

  (define (convert stx trans? lookup-stx set-stx safe-vector-ref-stx id li in-module? 
		   simple-constant? stop-properties)
    (define (loop stx)
      (if (ormap (lambda (prop)
                   (syntax-property stx prop))
                 stop-properties)
          stx
          (kernel-syntax-case (syntax-disarm stx code-insp) trans?
            [_
             (identifier? stx)
             (or (simple-identifier stx trans?)
                 (add-identifier stx li trans? lookup-stx id))]
            [(#%provide . _)
             stx]
            [(#%plain-lambda formals e ...)
             (quasisyntax/loc+props 
              stx
              (#%plain-lambda formals #,@(map loop (syntax->list #'(e ...)))))]
            [(case-lambda [formals e ...] ...)
             (with-syntax ([((e ...) ...)
                            (map (lambda (l)
                                   (map loop (syntax->list l)))
                                 (syntax->list #'((e ...) ...)))])
               (quasisyntax/loc+props 
                stx
                (case-lambda [formals e ...] ...)))]
            [(let-values ([(id ...) rhs] ...) e ...)
             (with-syntax ([(rhs ...)
                            (map loop (syntax->list #'(rhs ...)))])
               (quasisyntax/loc+props 
                stx
                (let-values ([(id ...) rhs] ...) #,@(map loop (syntax->list #'(e ...))))))]
            [(letrec-values ([(id ...) rhs] ...) e ...)
             (with-syntax ([(rhs ...)
                            (map loop (syntax->list #'(rhs ...)))])
               (quasisyntax/loc+props 
                stx
                (letrec-values ([(id ...) rhs] ...) #,@(map loop (syntax->list #'(e ...))))))]
            [(quote e)
             (if (simple-constant? #'e)
                 #'(quote e)
                 (add-literal stx li safe-vector-ref-stx id))]
            [(quote-syntax e)
             (add-literal stx li safe-vector-ref-stx id)]
            [(#%top . tid)
             (let ([target (let ([b ((if trans?
                                         identifier-transformer-binding
                                         identifier-binding)
                                     #'tid)])
                             (if (or (eq? b 'lexical) 
                                     (and (not in-module?)
                                          b))
                                 #`(#%top . tid)
                                 #'tid))])
               (add-identifier target li trans? lookup-stx id))]
            [(set! x e)
             (if (local-identifier? #'x trans?)
                 (quasisyntax/loc+props stx (set! x #,(loop #'e)))
                 (quasisyntax/loc+props 
                  stx
                  (#,set-stx #,id #,(add-identifier/pos #'x li trans?) #,(loop #'e))))]
            [(#%variable-reference e)
             (add-literal stx li safe-vector-ref-stx id)]
            [(if e ...)
             (quasisyntax/loc+props 
              stx
              (if #,@(map loop (syntax->list #'(e ...)))))]
            [(begin e ...)
             (quasisyntax/loc+props 
              stx
              (begin #,@(map loop (syntax->list #'(e ...)))))]
            [(begin0 e ...)
             (quasisyntax/loc+props 
              stx
              (begin0 #,@(map loop (syntax->list #'(e ...)))))]
            [(with-continuation-mark e ...)
             (quasisyntax/loc+props 
              stx
              (with-continuation-mark #,@(map loop (syntax->list #'(e ...)))))]
            [(#%plain-app e ...)
             (quasisyntax/loc+props 
              stx
              (#%plain-app #,@(map loop (syntax->list #'(e ...)))))])))
    (loop stx)))
