(module contracts-module-begin mzscheme
  
  (require "contracts.ss")
  
  (require-for-syntax (lib "list.ss")
		      (lib "boundmap.ss" "syntax"))
  
  (provide beginner-module-begin intermediate-module-begin advanced-module-begin)
  
  (define-syntaxes (beginner-module-begin intermediate-module-begin advanced-module-begin
					  beginner-continue intermediate-continue advanced-continue)
    (let ()
      (define (parse-contracts language-level-contract language-level-define-data
			       module-begin-continue-id)
	;; takes a list of syntax objects (the result of syntax-e) and returns all the syntax objects that correspond to
	;; a contract declaration. Syntax: (contract function-name (domain ... -> range))
	(define extract-contracts
	  (lambda (lostx) 
	    (filter contract-stx? lostx)))
	
					; negate previous
	(define extract-not-contracts
	  (lambda (stx-list) 
	    (filter (lambda (x) (not (contract-stx? x))) stx-list)))
	
					; predicate: is this syntax object a contract expr?
	(define contract-stx?
	  (lambda (stx) 
	    (syntax-case stx () 
	      [(contract function cnt) 
	       (and (identifier? #'contract)
		    (module-identifier=? #'contract language-level-contract))]
	      [_ #f])))
	
					; pred: is this syntax obj a define-data?
	(define define-data-stx?
	  (lambda (stx)
	    (syntax-case stx ()
	      [(define-data name e1 e2 ...)
	       (and (identifier? #'define-data)
		    (module-identifier=? #'define-data language-level-define-data))]
	      [_ #f])))
	
	;; takes a list of contract stx and a definitions stx and tells you if there is a contract defined for this function
	(define contract-defined?
	  (lambda (cnt-list item)
	    (cond 
	     [(null? cnt-list) #f]
	     [(fn=? (get-function-from-contract (car cnt-list)) (get-function-from-def item)) #t]
	     [else (contract-defined? (cdr cnt-list) item)])))
	
	;; returns the name of the function in a given contract-syntax
	(define get-function-from-contract
	  (lambda (stx) 
	    (if (contract-stx? stx)
		(syntax-case stx () 
		  [(contract function cnt ...) (syntax function)]
                  [_ (raise-syntax-error 'contract "internal error.1")])
		(raise-syntax-error 'contract "this is not a valid contract" stx))))  
	
	;; used to match up contract definitions with function definitions
					; should just be bound-identifier=?, but since beginner does some funny things
					; with hygiene, we have to do this
	(define (fn=? a b)
	  (string=? (symbol->string (syntax-object->datum a)) 
		    (symbol->string (syntax-object->datum b))))
	
	;; search in the cnt-list for the contract that matches the given definition
	(define get-contract
	  (lambda (cnt-list def-stx)
	    (cond
	     [(null? cnt-list) (error 'get-contract "contract not found")]
	     [(fn=? (get-function-from-contract (car cnt-list)) (get-function-from-def def-stx)) (car cnt-list)]
	     [else (get-contract (cdr cnt-list) def-stx)])))
	
	;; returns the name of the function in a given definition-syntax
	(define get-function-from-def
	  (lambda (stx) 
	    (if (definition-stx? stx)
		(syntax-case stx (begin define-values define-syntaxes) 
		  [(define-values (f) e1 ...) (syntax f)]
                  [_ (raise-syntax-error 'contract "internal error.2")])
		(raise-syntax-error 'defs "this is not a valid definition" stx))))
	
	;; given a syntax object, tells you whether or not this is a definition.
	(define definition-stx? 
	  (lambda (stx) 
	    (syntax-case stx (begin define-values)
	      [(define-values (f) e1 ...) #t]
	      [_ #f])))
	
	;;transform-definiton
	(define (transform-definition def)
	  (syntax-case def (define-values)
	    [(define-values (func) exp)
	     (with-syntax ([new-name (rename-func def)]
			   [expr-infname (syntax-property (syntax exp) 'inferred-name 
							  (syntax-object->datum (syntax func)))])
	       (syntax/loc def (define-values (new-name) expr-infname)))]
            [_ (raise-syntax-error 'contract "internal error.3")]))
	
	(define (rename-func def)
	  (let ([name (get-function-from-def def)])
	    (syntax-case def (define-values)
	      [(define-values (f) e1)
	       (datum->syntax-object (syntax f) 
				     (string->symbol (string-append (symbol->string (syntax-object->datum name)) "-con")))]
              [_ (raise-syntax-error 'contract "internal error.4")])))
	

	;; transform-contract : syntax syntax -> syntax
	;; takes in two syntax objects: one representing a contract, and another representing a definition, 
	;; returns a syntax object that returns the correct language level contract wrapping
	(define transform-contract
	  (lambda (language-level-contract cnt-stx def-stx)
	    (syntax-case cnt-stx ()
	      [(contract function cnt) 
	       (with-syntax ([ll-contract language-level-contract]
			     [name-to-bind (get-function-from-def def-stx)]
			     [func-to-wrap (rename-func def-stx)])
		 (syntax/loc cnt-stx (ll-contract 'name-to-bind 'func-to-wrap cnt)))]
              [_ (raise-syntax-error 'contract "internal error.5")])))
	
	(define local-expand-stop-list 
          (list 'contract 'define-values 'define-syntaxes 'require 'require-for-syntax 
                'provide 'define-data '#%app '#%datum 'define-struct 'begin))
	
	;; parse-contract-expressions 
	;; takes in a list of top level expressions and a list of contracts, and outputs the correct transformation. 
	;; 1. expand until we find a definition or a contract
	;; 2. if its a definition, and it has a contract, transform and output
	;; 3. else just output it
	(define (parse-contract-expressions ll-contract ll-define-data contract-list expressions)

	  (let loop ([cnt-list contract-list]
		     [exprs expressions])

	    (cond
              [(null? exprs)
               (if (null? cnt-list)
                   (syntax (begin ))
                   (raise-syntax-error 'contracts "this contract has no corresponding def" (car cnt-list)))]
              [else
               (let ([expanded (car exprs)])

                 (syntax-case expanded (begin define-values define-data)
                   [(define-values (func) e1 ...)
                    (contract-defined? cnt-list expanded)
                    (let ([cnt (get-contract cnt-list expanded)])
                      (quasisyntax/loc (car exprs)
                        (begin
                          #,(transform-definition expanded)
                          #,(transform-contract ll-contract cnt expanded)
                          #,(loop (remove cnt cnt-list) (cdr exprs)))))]
                   [(define-data name c1 c2 ...)
                    (identifier? #'name)
                    (quasisyntax/loc (car exprs)
                      (begin
                        (#,ll-define-data name c1 c2 ...)
                        #,(loop cnt-list (cdr exprs))))]
                   [(begin e1 ...)
                    (loop cnt-list (append (syntax-e (syntax (e1 ...))) (cdr exprs)))]
		   [_else 
		    (quasisyntax/loc (car exprs)
		      (begin
			#,(car exprs)
			#,(loop cnt-list (cdr exprs))))]))])))
	
	;; contract transformations!
	;; this is the macro, abstracted over which language level we are using. 
	;; parse-contracts : 
	;; given transformers that handle the actual contract parsing (depends on language level.. see contracts.scm and define-data.scm
	;; this returns a big wrapper macro that translates calls to 
	;; (contract f (number -> number)) (define f ...)
	;; ====>>>> (lang-lvl-contract f (number -> number) ...)
	;; where ll-contract is either beginner-contract, intermediate-contract, or advanced-contract
	;; and (define-data name ....) to (lang-lvl-define-data name ...)

	(values
	 ;; module-begin (for a specific language:)
	 (lambda (stx)
	   (syntax-case stx ()
	     [(_ e1 ...)
	      ;; module-begin-continue takes a sequence of expanded
	      ;; exprs and a sequence of to-expand exprs; that way,
	      ;; the module-expansion machinery can be used to handle
	      ;; requires, etc.:
	      #`(#%plain-module-begin
		 (#,module-begin-continue-id () (e1 ...) ()))]))

	 ;; module-continue (for a specific language:)
	 (lambda (stx)
	   (syntax-case stx ()
	     [(_ (e1 ...) () (defined-id ...))
	      ;; Local-expanded all body elements, lifted out requires, etc.
	      ;; Now process the result.
	      (begin
		;; The expansion for contracts breaks the way that beginner-define, etc.,
		;;  check for duplicate definitions, so we have to re-check here.
		;; A better strategy might be to turn every define into a define-syntax
		;;  to redirect the binding, and then the identifier-binding check in
		;;  beginner-define, etc. will work.
		(let ([defined-ids (make-bound-identifier-mapping)])
		  (for-each (lambda (id)
			      (when (bound-identifier-mapping-get defined-ids id (lambda () #f))
				(raise-syntax-error
				 #f
				 "this name was defined previously and cannot be re-defined"
				 id))
			      (bound-identifier-mapping-put! defined-ids id #t))
			    (reverse (syntax->list #'(defined-id ...)))))
		;; Now handle contracts:
		(let* ([top-level (reverse (syntax->list (syntax (e1 ...))))]
		       [cnt-list (extract-contracts top-level)]
		       [expr-list (extract-not-contracts top-level)])
		  (parse-contract-expressions language-level-contract
					      language-level-define-data
					      cnt-list
					      expr-list)))]
	     [(_ e1s (e2 . e3s) def-ids)
	      (let ([e2 (local-expand #'e2 'module local-expand-stop-list)])
		;; Lift out certain forms to make them visible to the module
		;;  expander:
		(syntax-case e2 (require define-syntaxes define-values-for-syntax define-values begin)
		  [(require . __)
		   #`(begin #,e2 (_ e1s e3s def-ids))]
		  [(define-syntaxes (id ...) . __)
		   #`(begin #,e2 (_ e1s e3s (id ... . def-ids)))]
		  [(define-values-for-syntax . __)
		   #`(begin #,e2 (_ e1s e3s def-ids))]
		  [(begin b1 ...)
		   #`(_ e1s (b1 ... . e3s) def-ids)]
		  [(define-values (id ...) . __)
		   #`(_ (#,e2 . e1s) e3s (id ... . def-ids))]
		  [else
		   #`(_ (#,e2 . e1s) e3s def-ids)]))]))))
      
      (define-values (parse-beginner-contract/func continue-beginner-contract/func)
        (parse-contracts #'beginner-contract #'beginner-define-data #'beginner-continue))
      (define-values (parse-intermediate-contract/func continue-intermediate-contract/func)
        (parse-contracts #'intermediate-contract  #'intermediate-define-data #'intermediate-continue))
      (define-values (parse-advanced-contract/func continue-advanced-contract/func)
        (parse-contracts #'advanced-contract #'advanced-define-data #'advanced-continue))
    
      (values parse-beginner-contract/func
              parse-intermediate-contract/func
              parse-advanced-contract/func
	      continue-beginner-contract/func
              continue-intermediate-contract/func
              continue-advanced-contract/func))))
