(module unit-runtime mzscheme
  (require-for-syntax "unit-syntax.ss")
  (provide define-syntax/err-param
           undefined (rename make-a-unit make-unit) unit-import-sigs unit-export-sigs unit-go unit? unit-deps
           check-unit check-no-imports check-sigs check-deps check-helper)
  
  (define-syntax define-syntax/err-param
    (syntax-rules ()
      ((_ (name arg) body)
       (define-syntax (name arg)
         (parameterize ((error-syntax arg))
           body)))))
  
  ;; initial value
  (define undefined (letrec ([x x]) x))

  ;; for named structures
  (define insp (current-inspector))
  
  ;; (make-unit (listof (cons symbol symbol)) (listof (cons symbol symbol)) (listof nat) thunk)
  ;; Runtime representation of a unit
  (define-struct unit (import-sigs export-sigs deps go))
  
  ;; For units with inferred names, generate a struct that prints using the name:
  (define (make-naming-constructor type name)
    (let-values ([(struct: make- ? -accessor -mutator)
		  (make-struct-type name type 0 0 #f null insp)])
      make-))
  
  ;; Make a unit value (call by the macro expansion of `unit')
  (define (make-a-unit name num-imports exports deps go)
    ((if name 
	 (make-naming-constructor 
	  struct:unit
	  (string->symbol (format "unit:~a" name)))
	 make-unit)
     num-imports exports deps go))

  ;; check-unit : X symbol -> 
  ;; ensure that u is a unit value
  (define (check-unit u name)
    (unless (unit? u)
      (raise
       (make-exn:fail:contract
        (format "~a: result of unit expression was not a unit: ~e" name u)
        (current-continuation-marks)))))
  
  ;; check-helper : (vectorof (cons symbol (vectorof (cons symbol symbol)))))
  ;                 (vectorof (cons symbol (vectorof (cons symbol symbol)))))
  ;;                symbol symbol -> 
  ;; ensure that the unit's signatures match the expected signatures.
  (define (check-helper sub-sig super-sig name import?)
    (define t (make-hash-table 'equal))
    (let loop ([i (sub1 (vector-length sub-sig))])
      (when (>= i 0)
        (let ([v (cdr (vector-ref sub-sig i))])
          (let loop ([j (sub1 (vector-length v))])
            (when (>= j 0)
              (let ([vj (vector-ref v j)])
                (hash-table-put! t vj
                                 (if (hash-table-get t vj #f)
                                     'amb
                                     #t)))
              (loop (sub1 j)))))
        (loop (sub1 i))))
    (let loop ([i (sub1 (vector-length super-sig))])
      (when (>= i 0)
        (let* ([v0 (vector-ref (cdr (vector-ref super-sig i)) 0)]
               [r (hash-table-get t v0 #f)])
          (when (or (eq? r 'amb) (not r))
            (let ([tag (if (pair? v0) (car v0) #f)]
                  [sub-name (car (vector-ref super-sig i))]
                  [err-str (if r
                             "supplies multiple times"
                             "does not supply")])
              (raise
               (make-exn:fail:contract
                (cond
                  [(and import? tag)
                   (format "~a: unit argument expects an import for tag ~a with signature ~a, which this usage context ~a"
                           name
                           tag
                           sub-name
                           err-str)]
                  [import?
                   (format "~a: unit argument expects an untagged import with signature ~a, which this usage context ~a"
                           name
                           sub-name
                           err-str)]
                  [tag
                   (format "~a: this usage context expects a unit with an export for tag ~a with signature ~a, which the given unit ~a"
                           name
                           tag
                           sub-name
                           err-str)]
                  [else
                   (format "~a: this usage context expects a unit with an untagged export with signature ~a, which the given unit ~a"
                           name
                           sub-name
                           err-str)])
                (current-continuation-marks))))))
        (loop (sub1 i)))))
  
  ;; check-deps : (hash-tableof (cons symbol (or symbol #f)) (cons symbol symbol)) unit symbol ->
  ;; The hash table keys are the tag and runtime signature id
  ;; The values are the name of the signature and the linkage
  (define (check-deps dep-table unit name)
    (for-each
     (λ (dep)
        (let ([r (hash-table-get dep-table dep #f)])
          (when r
            (raise
             (make-exn:fail:contract
              (if (car dep)
                  (format "~a: initialization dependent signature ~a with tag ~a is supplied from a later unit with link ~a"
                          name (car r) (car dep) (cdr r))
                  (format "~a: untagged initialization dependent signature ~a is supplied from a later unit with link ~a"
                          name (car r) (cdr r)))
              (current-continuation-marks))))))
     (unit-deps unit)))
  
  ;; check-no-imports : unit symbol ->
  ;; ensures that the unit has no imports
  (define (check-no-imports unit name)
    (check-helper (vector) (unit-import-sigs unit) name #t))
  
  ;; check-sigs : unit
  ;;              (vectorof (cons symbol (vectorof (cons symbol symbol)))))
  ;;              (vectorof (cons symbol (vectorof (cons symbol symbol)))))
  ;;              symbol ->
  ;; ensures that unit has the given signatures
  (define (check-sigs unit expected-imports expected-exports name)
    (check-helper expected-imports (unit-import-sigs unit) name #t)
    (check-helper (unit-export-sigs unit) expected-exports name #f)))

   
