(require (for-syntax (lib "unit-compiletime.ss" "mzlib" "private")
                     (lib "unit-syntax.ss" "mzlib" "private")))
(require "test-harness.ss"
         ;unit
         scheme/unit)

(define-syntax (lookup-sig-mac stx)
  (parameterize ((error-syntax stx))
    (syntax-case stx ()
      ((_ id)
       #`#'#,(let ((s (lookup-signature #'id)))
               (list (map syntax-local-introduce (signature-vars s))
                     (map (lambda (def)
                            (cons (map syntax-local-introduce (car def))
                                  (syntax-local-introduce (cdr def))))
                          (signature-val-defs s))
                     (map (lambda (def)
                          (cons (map syntax-local-introduce (car def))
                                (syntax-local-introduce (cdr def))))
                          (signature-stx-defs s))))))))

(define-signature x-sig (x))
(define-signature x-sig2 (x))
(define-signature y-sig (y))
(define-signature z-sig (z))

(define-signature yz-sig (y z))
(define-signature xy-sig (x y))
(define-signature empty-sig ())
(define-signature b-sig (b))

(define-signature empty-sub extends empty-sig ())

(define-signature x-sub extends x-sig (xx))
(define-signature y-sub extends y-sig (yy))
(define-signature x-sub2 extends x-sig (x2))

 

;; Keyword errors
(test-syntax-error "misuse of import"
  import)
(test-syntax-error "misuse of export"
  export)
(test-syntax-error "misuse of init-depend"
  init-depend)
(test-syntax-error "misuse of link"
  link)
(test-syntax-error "misuse of only"
  only)
(test-syntax-error "misuse of except"
  except)
(test-syntax-error "misuse of prefix"
  prefix)
(test-syntax-error "misuse of rename"
  rename)
(test-syntax-error "misuse of tag"
  tag)

;; define-signature-forms syntax errors
(test-syntax-error "define-signature-form: missing arguments"
  (define-signature-form))
(test-syntax-error "define-signature-form: missing arguments"
  (define-signature-form (a b)))
(test-syntax-error "define-signature-form: too many arguments"
  (define-signature-form (a b c) 1 2))
(test-syntax-error "define-signature-form: dot"
  (define-signature-form (a b) . c))
(test-syntax-error "define-signature-form: set!"
  (let ()
    (define-signature-form (a b) b)
    (set! a 1)))

(test-syntax-error "define-signature-form: bad params"
  (define-signature-form 1 2))
(test-syntax-error "define-signature-form: bad params"
  (define-signature-form a 2))
(test-syntax-error "define-signature-form: name not id"
  (define-signature-form (1 a) 1))
(test-syntax-error "define-signature-form: param not id"
  (define-signature-form (a 1) 1))
(test-syntax-error "define-signature-form: param dot"
  (define-signature-form (a . b) 1))


;; define-signature syntax-errors
(test-syntax-error "define-signature: missing name"
  (define-signature))
(test-syntax-error "define-signature: missing sig"
  (define-signature x))
(test-syntax-error "define-signature: too many args"
  (define-signature x (a b) 1))
(test-syntax-error "define-signature: bad name"
  (define-signature 1 (a b)))
(test-syntax-error "define-signature: bad name"
  (define-signature x extends 1 (a b)))
(test-syntax-error "define-signature: not a signature"
  (define-signature x extends y12 (a b)))
(test-syntax-error "define-signature: not a signature"
  (let () (define-signature x extends x (a b))))
(test-syntax-error "define-signature: bad name"
  (define-signature (a . b) (a b)))
(test-syntax-error "define-signature: dot"
  (define-signature b . (a b)))
(test-syntax-error "define-signature: dot"
  (define-signature b (a b) . 2))
(test-syntax-error "define-signature: set!"
  (let ()
    (define-signature a (a))
    (set! a 1)))
(test-syntax-error "define-signature: bad sig"
  (define-signature x y))
(test-syntax-error "define-signature: bad sig"
  (define-signature x (1)))
(test-syntax-error "define-signature: bad sig"
  (define-signature x (a . b)))
(test-syntax-error "define-signature: bad signature form"
  (define-signature x ((a))))
(test-syntax-error "define-signature: bad signature form"
  (define-signature x ((define-signature))))
(test-syntax-error "define-values: malformed (in define-signature)"
  (define-signature x ((define-values 1 2))))
(test-syntax-error "define-signature: bad form (does not return list)"
  (let ()
    (define-signature-form (a b) 1)
    (define-signature x ((a 1)))))
(test-syntax-error "define-signature: unknown form"
  (let ()
    (define-signature-form (a b) (list #'(c d)))
    (define-signature x ((a 1)))
    1))
(test-syntax-error "define-signature: duplicate name"
  (define-signature x (a a)))
(test-syntax-error "define-signature: duplicate values"
  (define-signature x (a (define-values (a) 1))))
(test-syntax-error "define-signature: duplicate values"
  (define-signature x (a (define-values (b b) 1))))
(test-syntax-error "define-signature: duplicate values"
  (define-signature x (a (define-values (b) 1) (define-syntaxes (b) 1))))
(test-syntax-error "define-signature: duplicate values"
  (let ()
    (define-signature test (y))
    (define-signature x extends test ((define-values (y) 1)))))

;; define-signature
(test stx-bound-id=? #'((a b) () ())
      (let ()
        (define-signature x (a b))
        (lookup-sig-mac x)))
(let ()
  (define-signature super (s1 (define-values (s2 s3) s4) (define-syntaxes (s5 s6) s7)))
  (test stx-bound-id=? #'((s1 a b f) (((s2 s3) . s4) ((c d) . e) ((i) . j)) (((s5 s6) . s7) ((g) . h)))
        (let ()
          (define-signature x extends super (a b (define-values (c d) e) f
                                               (define-syntaxes (g) h)
                                               (define-values (i) j)))
          (lookup-sig-mac x))))
(let ()
  (define-signature super (s1 (define-values (s2 s3) s4) (define-syntaxes (s5 s6) s7)))
  (let ((a 1) (g 2) (j 3) (s1 4) (s2 5) (s7 6))
    (test stx-bound-id=? #'(((#f . s1) a b f) ((((#f . s2) s3) . s4) ((c d) . e) ((i) . j)) (((s5 s6) . (#f . s7)) ((g) . h)))
          (let ()
            (define-signature x extends super (a b (define-values (c d) e) f
                                                 (define-syntaxes (g) h)
                                                 (define-values (i) j)))
            (lookup-sig-mac x)))))
(let ()
  (define-signature super (s1 (define-values (s2 s3) s4) (define-syntaxes (s5 s6) s7)))
  (test stx-bound-id=? #'((s1 a b f) (((s2 s3) . s4) ((c d) . e) ((i) . j)) (((s5 s6) . s7) ((g) . h)))
        (let ()
          (define-signature x extends super (a b (define-values (c d) e) f
                                               (define-syntaxes (g) h)
                                               (define-values (i) j)))
          (let ((a 1) (g 2) (j 3))
            (lookup-sig-mac x)))))
(test stx-bound-id=? #'(((#f . a) b f) (((c d) . e) ((i) . (#f . j))) ((((#f . g)) . h)))
      (let ((a 1) (g 2) (j 3))
        (define-signature x (a b (define-values (c d) e) f
                               (define-syntaxes (g) h)
                               (define-values (i) j)))
        (lookup-sig-mac x)))
(let ()
  (define-signature-form (x y)
                         (list (cdr (syntax-e y))))
  (test stx-bound-id=? #'((a)
                          ()
                          ())
        (let ()
          (define-signature z ((x . a)))
          (lookup-sig-mac z))))


;; unit syntax errors (without sub-signatures)
(test-syntax-error "unit: bad sig import"
  (unit (import 1) (export)))
(test-syntax-error "unit: bad sig export"
  (unit (import) (export 1)))
(test-syntax-error "unit: unknown sig import"
  (unit (import a) (export)))
(test-syntax-error "unit: unknown sig export"
  (unit (import) (export a)))
(test-syntax-error "unit: bad tag (not identifier)"
  (unit (import (tag 1 empty-sig)) (export)))
(test-syntax-error "unit: bad tag (not identifier)"
  (unit (import) (export (tag 'a empty-sig))))
(test-syntax-error "define-values: bad syntax (in unit)"
  (unit (import) (export) (define-values)))
(test-syntax-error "unit: multiple definition"
  (unit (import) (export) (define-values (x x) (values 1 2))))
(test-syntax-error "unit: multiple definition"
  (unit (import) (export) (define-syntaxes (x x) (values 1 2))))
(test-syntax-error "unit: multiple definition"
  (unit (import) (export) (define x 1) (define x 2)))
(test-syntax-error "unit: multiple definition"
  (unit (import) (export) (define-syntax x 1) (define-syntax x 2)))
(test-syntax-error "unit: multiple definition"
  (unit (import) (export) (define x 1) (define-syntax x 2)))
(test-syntax-error "unit: re-export"
  (unit (import x-sig) (export x-sig) (define x 1)))
(test-syntax-error "unit: redefine import"
  (unit (import x-sig) (export) (define x 1)))
(test-syntax-error "unit: set! import"
  (unit (import x-sig) (export) (set! x 1)))
(test-syntax-error "unit: set! export"
  (unit (import) (export x-sig) (define x 1) (set! x 1)))
(test-syntax-error "unit: undefined export"
  (unit (import) (export x-sig)))
(test-syntax-error "unit: undefined export"
  (unit (import) (export (prefix x: x-sig)) (define x 1)))
(test-syntax-error "unit: syntax export"
  (unit (import) (export x-sig) (define-syntax x 1)))
(test-syntax-error "unit: duplicate import"
  (unit (import x-sig x-sig2) (export)))
(test-syntax-error "unit: duplicate export"
  (unit (import) (export x-sig x-sig2) (define x 12)))
(test-syntax-error "unit: duplicate import signature"
  (unit (import x-sig (prefix a x-sig)) (export)))
(test-syntax-error "unit: duplicate export signature"
  (unit (import) (export x-sig (prefix a x-sig))
        (define x 1) (define ax 2)))
(test-syntax-error "unit: duplicate import signature"
  (unit (import (tag t x-sig) (tag t (prefix a x-sig))) (export)))
(test-syntax-error "unit: duplicate export signature"
  (unit (import) (export (tag t x-sig) (tag t (prefix a x-sig)))
        (define x 1) (define ax 2)))
(test-syntax-error "unit: duplicate export signature"
  (unit (import) (export x-sig x-sig)
        (define x 1)))


;; compound-unit syntax errors (without sub-signatures)
(test-syntax-error "compound-unit: bad import clause"
  (compound-unit (import (a empty-sig)) (export) (link)))
(test-syntax-error "compound-unit: import clause bad link id"
  (compound-unit (import (1 : empty-sig)) (export) (link)))
(test-syntax-error "compound-unit: import clause unknown sig"
  (compound-unit (import (a : empty-si)) (export) (link)))
(test-syntax-error "compound-unit: export bad link id"
  (compound-unit (import) (export a 1 b) (link)))
(test-syntax-error "compound-unit: link line bad link id"
  (compound-unit (import) (export) (link (((a : empty-sig)) b 1))))
(test-syntax-error "compound-unit: import clause bad sig id"
  (compound-unit (import (a : ())) (export) (link)))
(test-syntax-error "compound-unit: link line clause bad sig id"
  (compound-unit (import) (export) (link (((a : "")) b))))
(test-syntax-error "compound-unit: link line clause bad"
  (compound-unit (import) (export) (link (((a empty-sig)) b))))
(test-syntax-error "compound-unit: link line clause unknown"
  (compound-unit (import) (export) (link (((a : b)) b))))
(test-syntax-error "compound-unit: duplicate link ids"
  (compound-unit (import (x : x-sig) (x : y-sig)) (export) (link)))
(test-syntax-error "compound-unit: duplicate link ids"
  (compound-unit (import) (export) (link (((x : x-sig) (x : y-sig)) u))))
(test-syntax-error "compound-unit: duplicate link ids"
  (compound-unit (import (x : x-sig)) (export) (link (((x : x-sig)) u))))
(test-syntax-error "export: unbound link id"
  (compound-unit (import) (export a) (link)))
(test-syntax-error "link link: unbound link id"
  (compound-unit (import) (export) (link (() u a))))
(test-syntax-error "compound-unit: re-export"
  (compound-unit (import (S : x-sig)) (export S) (link)))
(test-syntax-error "compound-unit: re-export"
  (compound-unit (import (tag s (S : x-sig)))  (export (tag t S)) (link)))
(test-syntax-error "compound-unit: duplicate export signature"
  (compound-unit (import) (export X1 X2)
                 (link (((X1 : x-sig)) (unit (import) (export x-sig) (define x 1)))
                       (((X2 : x-sig)) (unit (import) (export x-sig) (define x 1))))))
(test-syntax-error "compound-unit: duplicate export signature"
  (compound-unit (import) (export (tag t X1) (tag t X2))
                 (link (((X1 : x-sig)) (unit (import) (export x-sig) (define x 1)))
                       (((X2 : x-sig)) (unit (import) (export x-sig) (define x 1))))))

;; define-values/invoke-unit syntax errors
(test-syntax-error "define-values/invoke-unit: no unit"
  (define-values/invoke-unit))
(test-syntax-error "define-values/invoke-unit: dot"
  (define-values/invoke-unit x y . x))
(test-syntax-error "define-values/invoke-unit: bad sig"
  (define-values/invoke-unit 1 1))
(test-syntax-error "define-values/invoke-unit: duplicate exports"
  (define-values/invoke-unit (unit (import) (export (prefix x: x-sig) x-sig2)
                               (define x 1)
                               (define x:x 2))
    x-sig x-sig2))


;; simple units, compound-units, and invoke-units (no subtypes, no tags, no prefix/rename/etc, no fancy signatures)
(test 12
  (invoke-unit (unit (import) (export) 12)))

(test 3
 (invoke-unit
  (compound-unit (import) (export)
                 (link (((X : x-sig) (Y : y-sig)) (unit (import empty-sig z-sig)
                                                        (export y-sig x-sig)
                                                        (define x 1)
                                                        (define y 2))
                                                  Z E)
                       (((Z : z-sig) (E : empty-sig)) (unit (import x-sig y-sig)
                                                            (export empty-sig z-sig)
                                                            (define z 3)
                                                            3) X Y)))))
                                                                   

;; Test compound export with signatures containing overlapping names
(test (list 10 11 12)
  (let ((un (compound-unit (import) (export S U)
              (link (((S : x-sig)) (unit (import) (export x-sig) (define x 10)))
                    (((U : xy-sig)) (unit (import) (export xy-sig) (define x 11) (define y 12)))))))
    (invoke-unit
     (compound-unit (import) (export)
                    (link (((S : x-sig) (U : xy-sig)) un)
                          (((B : b-sig)) (unit (import x-sig) (export b-sig) (define b x)) S)
                          (() (unit (import b-sig xy-sig) (export) (list b x y)) B U))))))

(define-signature even-sig (even))
(define-signature odd-sig (odd))

(define even-unit
  (unit (import odd-sig)
        (export even-sig)
    (define (even x)
      (or (= 0 x) (odd (sub1 x))))))

(define odd-unit
  (unit (import even-sig)
        (export odd-sig)
    (define (odd x)
      (and (> x 0) (even (sub1 x))))
    (define x (odd 11))
    x))

(define run-unit
  (compound-unit (import)
                 (export)
    (link (((EVEN : even-sig)) even-unit ODD)
          (((ODD : odd-sig)) odd-unit EVEN))))

(test #t (invoke-unit run-unit))

(define-signature is-3x-sig (is-3x))
(define-signature is-3x+1-sig (is-3x+1))
(define-signature is-3x+2-sig (is-3x+2))

(define is-3x-unit
  (unit (import is-3x+2-sig)
        (export is-3x-sig)
    (define (is-3x x)
      (or (= 0 x) (is-3x+2 (sub1 x))))))

(define is-3x+2-unit
  (unit (import is-3x+1-sig)
        (export is-3x+2-sig)
    (define (is-3x+2 x)
      (and (> x 0) (is-3x+1 (sub1 x))))))

(define is-3x+1-unit
  (unit (import is-3x-sig)
        (export is-3x+1-sig)
    (define (is-3x+1 x)
      (and (> x 0) (is-3x (sub1 x))))))

(define 3x-compound1
  (compound-unit (import (IS-3X : is-3x-sig))
                 (export IS-3X+1 IS-3X+2)
    (link (((IS-3X+1 : is-3x+1-sig)) is-3x+1-unit IS-3X)
          (((IS-3X+2 : is-3x+2-sig)) is-3x+2-unit IS-3X+1))))

(define 3x-compound2
  (compound-unit (import)
                 (export IS-3X)
    (link (((IS-3X : is-3x-sig)) is-3x-unit IS-3X+2)
          (((IS-3X+1 : is-3x+1-sig)
            (IS-3X+2 : is-3x+2-sig)) 3x-compound1 IS-3X))))

(define 3x-run-unit
  (unit (import is-3x-sig is-3x+1-sig is-3x+2-sig)
        (export)
    (list (is-3x 1)
          (is-3x 3)
          (is-3x+1 5)
          (is-3x+1 7)
          (is-3x+2 4)
          (is-3x+2 8))))

(define 3x-compound3
  (compound-unit (import)
                 (export IS-3X IS-3X+1 IS-3X+2)
    (link (((IS-3X : is-3x-sig)) 3x-compound2)
          (((IS-3X+1 : is-3x+1-sig)
            (IS-3X+2 : is-3x+2-sig)) 3x-compound1 IS-3X)
          (() 3x-run-unit IS-3X IS-3X+1 IS-3X+2))))

(test (list #f #t #f #t #f #t)
  (invoke-unit 3x-compound3))

(test (list #t #t #t)
  (let ()
    (define-values/invoke-unit 3x-compound3 (import) (export is-3x-sig is-3x+1-sig is-3x+2-sig))
    (list (is-3x+2 8)
          (is-3x+1 7)
          (is-3x 6))))
(test (list #t #t #t)
  (let ()
    (define-values/invoke-unit 3x-compound3 (import) (export (only is-3x-sig is-3x) (except is-3x+1-sig) (prefix x: is-3x+2-sig)))
    (list (x:is-3x+2 8)
          (is-3x+1 7)
          (is-3x 6))))
(test (list #t #t #t)
  (let ()
    (define-values/invoke-unit 3x-compound3 (import) (export is-3x-sig is-3x+1-sig (rename is-3x+2-sig (y is-3x+2))))
    (list (y 8)
          (is-3x+1 7)
          (is-3x 6))))

;; Tags
(let ()
  (define u
    (unit (import x-sig (tag t (prefix t: x-sig)) (tag u (prefix u: x-sig)))
          (export)
          (list x t:x u:x)))
  (define v
    (unit (import)
          (export x-sig (tag t (prefix t: x-sig)) (tag u (prefix u: x-sig)))
          (define x 1)
          (define t:x 2)
          (define u:x 3)))
  (test '(3 1 2)
    (invoke-unit
     (compound-unit (import) (export)
                    (link (((X1 : x-sig) (X2 : (tag u x-sig)) (X3 : (tag t x-sig))) v)
                          (() u (tag t X1) X2 (tag u X3))))))
  (test '(3 1 2)
    (invoke-unit
     (compound-unit (import) (export)
       (link (((L1 : (tag a x-sig)) (L2 : (tag b x-sig)) (L3 : (tag c x-sig)))
              (compound-unit (import) (export (tag a X1) (tag b X2) (tag c X3))
                (link (((X1 : x-sig) (X2 : (tag u x-sig)) (X3 : (tag t x-sig))) v))))
             (()
              (compound-unit (import (X1 : x-sig) (X2 : (tag u x-sig)) (X3 : (tag t x-sig))) (export)
                (link (() u (tag t X1) X2 (tag u X3))))
              L1 (tag u L2) (tag t L3)))))))

(let ()
  (define-values/invoke-unit (unit (import) (export (tag t x-sig)) (define x 1)) (import) (export (tag t x-sig)))
  (test 1 x))

;; simple runtime errors (no subtyping, no deps)
(test-runtime-error exn:fail:contract? "compound-unit: not a unit"
  (compound-unit (import) (export) (link (() 1))))
(test-runtime-error exn:fail:contract? "compound-unit: missing import"
  (compound-unit (import) (export)
                 (link (() (unit (import x-sig) (export))))))
(test-runtime-error exn:fail:contract? "compound-unit: missing import"
  (compound-unit (import (X : x-sig)) (export)
                 (link (() (unit (import x-sig) (export))
                           (tag u X)))))
(test-runtime-error exn:fail:contract? "compound-unit: missing import"
  (compound-unit (import (X : x-sig)) (export)
                 (link (() (unit (import (tag u x-sig)) (export))
                           X))))
(test-runtime-error exn:fail:contract? "compound-unit: missing export"
  (compound-unit (import) (export)
                 (link (((X : x-sig)) (unit (import) (export))))))
(test-runtime-error exn:fail:contract? "compound-unit: missing export"
  (compound-unit (import) (export)
                 (link (((X : (tag u x-sig))) (unit (import) (export x-sig) (define x 1))))))
(test-runtime-error exn:fail:contract? "compound-unit: missing export"
  (compound-unit (import) (export)
                 (link (((X : x-sig)) (unit (import (tag u x-sig)) (export))))))

(test-runtime-error exn:fail:contract? "invoke-unit: not a unit"
  (invoke-unit 1))
(test-runtime-error exn:fail:contract? "invoke-unit: unit has imports"
  (invoke-unit (unit (import x-sig) (export) x)))

(test-runtime-error exn:fail:contract? "define-values/invoke-unit: not a unit"
  (define-values/invoke-unit 1 (import) (export)))
(test-runtime-error exn:fail:contract? "define-values/invoke-unit: has imports"
  (define-values/invoke-unit (unit (import x-sig) (export) x) (import) (export)))
(test-runtime-error exn:fail:contract? "define-values/invoke-unit: signature mismatch"
  (define-values/invoke-unit (unit (import) (export)) (import) (export x-sig)))

;; unit creation w/o signatures (including macros and prefixes/renames).

;; free vars
(let ((y 1)
      (z 10))
  (define u (unit (import) (export yz-sig)
                  (define y 2)
                  (define z 3)))
  (define u1 (unit (import) (export)
                   y))
  (define u2 (unit (import (only yz-sig z)) (export)
                   y))
  (define u3 (unit (import (except yz-sig y)) (export)
                   y))
  (define u4 (unit (import (prefix s: yz-sig)) (export)
                   y))
  (define u5 (unit (import (rename yz-sig (r y))) (export)
                   y))
  (define u6 (unit (import yz-sig) (export)
                   y))
  (define (l x)
    (invoke-unit
     (compound-unit (import) (export)
                    (link (((YZ : yz-sig)) u)
                          (() x YZ)))))
  (test 1 (invoke-unit u1))
  (test 1 (l u2))
  (test 1 (l u3))
  (test 1 (l u4))
  (test 1 (l u5))
  (test 2 (l u6))
  (test (letrec ((x x)) x)
    (let ()
      (define-values/invoke-unit (unit-from-context yz-sig) (import) (export yz-sig))
      y))
  (test 1
    (let ()
      (let ((u (unit-from-context yz-sig)))
        (define-values/invoke-unit u (import) (export (prefix x: yz-sig)))
        x:y)))
  ;; Exporting and prefix don't work right because the shadower doesn't see the shadowed
  ;; bindings, I think.
  #;(test 1
      (let ((x:y 12)
            (x:z 10))
        (let ((u (unit-from-context (prefix x: yz-sig))))
          (define-values/invoke-unit u yz-sig)
          y)))
  #;(test 1
    (let ((x:y 12)
          (x:z 10))
      (define-signature t (y z))
      (let ((u (unit-from-context (prefix x: t))))
        (define-values/invoke-unit u t)
        y)))
  (test 12
    (let ((x:y 12)
          (x:z 10))
      (define-values/invoke-unit (unit-from-context (rename yz-sig (x:y y) (x:z z)))
        (import) (export yz-sig))
      y))
  (test 12
    (let ((x:y 12)
          (x:z 10))
      (define-signature t (y z))
      (let ()
        (define-values/invoke-unit (unit-from-context (rename t (x:y y) (x:z z))) (import) (export t))
        y))))

;; Test that a define-values can define both internal and exported vars
(test '(1 2)
  (invoke-unit
   (compound-unit (import) (export)
                  (link (((T : yz-sig)) (unit (import x-sig) (export yz-sig)
                                              (define-values (y a) (values 1 2))
                                              (define-values (b z) (values y a)))
                                        S)
                        (((S : x-sig)) (unit (import yz-sig) (export x-sig) (define x 3) (list y z)) T)))))


;; Test that internal macros can define exports
(test 1
  (invoke-unit
   (unit (import) (export x-sig)
         (define-syntax (y stx)
           (syntax-case stx ()
             ((_ x) #'(define x 1))))
         (y x)
         x)))
  
(define-signature fact-sig (fact n))

;; Test renaming, self-recursion, only, and except
(test 24
  (invoke-unit
   (compound-unit (import) (export)
     (link (((F : fact-sig)) (unit (import (except (rename fact-sig (f-in fact)) n))
                                   (export (rename fact-sig (f-out fact)))
                               (define n 1)
                               (define (f-out x) (if (= 0 x)
                                                     1
                                                     (* x (f-in (sub1 x))))))
                             F)
           (() (unit (import (only fact-sig fact)) (export)
                 (define n 2)
                 (fact 4))
               F)))))


;; Test import prefix
(test 1
  (invoke-unit
   (compound-unit (import) (export)
     (link (((S : x-sig)) (unit (import) (export x-sig) (define x 1)))
           (() (unit (import (prefix s: x-sig)) (export) s:x) S)))))

(define-signature sx (x))
(define-signature sy (y))
(define-signature sz (z))

;; Test separate signatures with overlapping bindings, and export renaming and prefix
(test '(1 2 3)
  (invoke-unit
   (compound-unit (import) (export)
     (link (((S : x-sig) (T : yz-sig) (U : xy-sig)) (unit (import) (export (rename x-sig (s:x x))
                                                                           (rename yz-sig (t:y y) (t:z z))
                                                                           (prefix u: xy-sig))
                                                      (define x 1) (define y 2) (define z 3)
                                                      (define s:x x) (define t:y y) (define t:z z) (define u:x x) (define u:y y)))
           (((SX : sx)) (unit (import (prefix s: x-sig)) (export sx) (define x s:x)) S)
           (((SY : sy)) (unit (import (prefix u: xy-sig)) (export sy) (define y u:y)) U)
           (((SZ : sz)) (unit (import (prefix t: yz-sig)) (export sz) (define z t:z)) T)
           (() (unit (import sx sy sz) (export) (list x y z)) SX SY SZ)))))


;; Test units importing and exporting b, where lexical definition of b shadows
;; the b identifier in the signature
(test 2
  (let ((b 1))
    (define u1 (unit (import) (export b-sig) (define b 2)))
    (define u2 (unit (import b-sig) (export) b))
    (invoke-unit (compound-unit (import) (export)
                                (link (((B : b-sig)) u1)
                                      (() u2 B))))))
(test 1
  (let ((b 1))
    (define u1 (unit-from-context b-sig))
    (let ((b 2))
      (define-values/invoke-unit u1 (import) (export b-sig))
      b)))
  


(let ((x 1)
      (v 2))
  (let-syntax ((s (syntax-rules () ((_) (list x v)))))
    (define-signature t (x (define-syntaxes (s)
                                                (syntax-rules ()
                                                  ((_) (list x v))))
                           (define-values (v) (add1 x))))
    (define-signature t2 (x (define-syntaxes (s)
                                                (syntax-rules ()
                                                  ((_) (list x v))))
                           (define-values (v) (add1 x))))
    (define u3 (unit (import) (export t)
                     (define x 3)))
    (define u4 (unit (import) (export t2)
                     (define x 4)))
    (define (i u)
      (invoke-unit
       (compound-unit (import) (export)
                      (link (((T3 : t)) u3)
                            (((T4 : t2)) u4)
                            (() u T3 T4)))))
    ;; prefix
    (let ((x 5)
          (v 6))
      (let-syntax ((s (syntax-rules () ((_) (list x v)))))
        (test '(7 8 (7 8) 3 4 (3 4) 4 5 (4 5))
          (i (unit (import (prefix p: t) (prefix q: t2)) (export)
                   (define x 7)
                   (define v 8)
                   (define-syntax s (syntax-rules () ((_) (list x v))))
                   (list x v (s) p:x p:v (p:s) q:x q:v (q:s)))))
        (test '(5 6 (5 6) 3 4 (3 4) 4 5 (4 5))
          (i (unit (import (prefix p: t) (prefix q: t2)) (export)
                   (list x v (s) p:x p:v (p:s) q:x q:v (q:s)))))))
    (test '(7 8 (7 8) 3 4 (3 4) 4 5 (4 5))
      (i (unit (import (prefix p: t) (prefix q: t2)) (export)
               (define x 7)
               (define v 8)
               (define-syntax s (syntax-rules () ((_) (list x v))))
               (list x v (s) p:x p:v (p:s) q:x q:v (q:s)))))
    (test '(1 2 (1 2) 3 4 (3 4) 4 5 (4 5))
      (i (unit (import (prefix p: t) (prefix q: t2)) (export)
               (list x v (s) p:x p:v (p:s) q:x q:v (q:s)))))
    ;; only
    (let ((x 5)
          (v 6))
      (let-syntax ((s (syntax-rules () ((_) (list x v)))))
        (test '(7 8 (7 8) (3 4) (4 5))
          (i (unit (import (prefix p: (only t s)) (only (prefix q: t2) q:s)) (export)
                   (define x 7)
                   (define v 8)
                   (define-syntax s (syntax-rules () ((_) (list x v))))
                   (list x v (s) (p:s) (q:s)))))
        (test '(5 6 (5 6) (3 4) (4 5))
          (i (unit (import (prefix p: (only t s)) (prefix q: (only t2 s))) (export)
                   (list x v (s) (p:s) (q:s)))))))
    (test '(7 8 (7 8) (3 4) (4 5))
      (i (unit (import (only (prefix p: t) p:s) (only (prefix q: t2) q:s)) (export)
               (define x 7)
               (define v 8)
               (define-syntax s (syntax-rules () ((_) (list x v))))
               (list x v (s) (p:s) (q:s)))))
    (test '(1 2 (1 2) (3 4) (4 5))
      (i (unit (import (prefix p: (only t s)) (prefix q: (only t2 s))) (export)
               (list x v (s) (p:s) (q:s)))))
    ;;rename
    (let ((x 5)
          (v 6))
      (let-syntax ((s (syntax-rules () ((_) (list x v)))))
        (test '(7 8 (7 8) 3 4 (3 4) 4 5 (4 5))
          (i (unit (import (rename t (p:x x) (p:v v) (p:s s))
                           (rename t2 (q:x x) (q:v v) (q:s s)))
                   (export)
                   (define x 7)
                   (define v 8)
                   (define-syntax s (syntax-rules () ((_) (list x v))))
                   (list x v (s) p:x p:v (p:s) q:x q:v (q:s)))))
        (test '(5 6 (5 6) 3 4 (3 4) 4 5 (4 5))
          (i (unit (import (rename t (p:x x) (p:v v) (p:s s))
                           (rename t2 (q:x x) (q:v v) (q:s s)))
                   (export)
                   (list x v (s) p:x p:v (p:s) q:x q:v (q:s)))))))
    (test '(7 8 (7 8) 3 4 (3 4) 4 5 (4 5))
      (i (unit (import (rename t (p:x x) (p:v v) (p:s s))
                       (rename t2 (q:x x) (q:v v) (q:s s)))
               (export)
               (define x 7)
               (define v 8)
               (define-syntax s (syntax-rules () ((_) (list x v))))
               (list x v (s) p:x p:v (p:s) q:x q:v (q:s)))))
    (test '(1 2 (1 2) 3 4 (3 4) 4 5 (4 5))
      (i (unit (import (rename t (p:x x) (p:v v) (p:s s))
                       (rename t2 (q:x x) (q:v v) (q:s s)))
               (export)
               (list x v (s) p:x p:v (p:s) q:x q:v (q:s))))))
  )

(let ()
  (define-signature x ((define-syntaxes (m)
                         (syntax-rules ()
                           ((_ x) (define-syntax x
                                    (syntax-rules ()
                                      ((_ y) y))))))
                       (define-values (v)
                         (let ()
                           (m a)
                           (a 1)))))
  (test 1
    (invoke-unit
      (compound-unit (import) (export)
                     (link (((X : x)) (unit (import) (export x)))
                           (() (unit (import x) (export) v) X))))))
(let ()
  (define-signature x ((define-syntaxes (m)
                         (syntax-rules ()
                           ((_ x) (define-syntax x #'1))))
                       (define-syntaxes (m2)
                         (lambda (stx)
                           (syntax-case stx ()
                             ((_ x) (syntax-local-value #'x)))))
                       (define-values (v)
                         (let ()
                           (m a)
                           (m2 a)))))
  (test 1
    (invoke-unit
      (compound-unit (import) (export)
                     (link (((X : x)) (unit (import) (export x)))
                           (() (unit (import x) (export) v) X))))))

(let ()
  (define-signature x ((define-syntaxes (m) #'1)
                       (define-syntaxes (m2)
                         (lambda (stx)
                           (syntax-case stx ()
                             ((_ x) (syntax-local-value #'x)))))
                       (define-values (v)
                         (let ()
                           (m2 m)))))
  (test 1
    (invoke-unit
      (compound-unit (import) (export)
                     (link (((X : x)) (unit (import) (export x)))
                           (() (unit (import x) (export) v) X))))))


(let ()
  (define-signature s1 (a (define-values (x y) (values 1 2))))
  (define-signature s2 extends s1 ((define-values (z) (list a x))))
  (define u1 (unit (import s2) (export) (cons y z)))
  (define u2 (unit (import) (export s2) (define a 123)))
  (test (list 2 123 1) (invoke-unit (compound-unit (import) (export)
                                      (link (((a : s2)) u2)
                                            (() u1 a))))))
(let ()
  (define-signature s1 (a (define-values (x y) (values 1 2))))
  (let ((x 12))
    (define-signature s2 extends s1 ((define-values (z) (list a x))))
    (define u1 (unit (import s2) (export) (cons y z)))
    (define u2 (unit (import) (export s2) (define a 123)))
    (test (list 2 123 12) (invoke-unit (compound-unit (import) (export)
                                                      (link (((a : s2)) u2)
                                                            (() u1 a)))))))

(let ()
  (define-signature s1 (a (define-values (x y) (values c 2))))
  (define-signature s2 extends s1 (c (define-values (z) (list a x))))
  (define u1 (unit (import s2) (export) (cons y z)))
  (define u2 (unit (import) (export s2) (define a 123) (define c 43)))
  (test (list 2 123 43) (invoke-unit (compound-unit (import) (export)
                                                    (link (((a : s2)) u2)
                                                          (() u1 a))))))

;; Test define-syntaxes and define-values, without except, only, prefix and rename
;; Check the scoping
(let ((a 'abad)
      (b 'bbad)
      (c 'cbad)
      (v1 'v1bad)
      (v2 'v2bad)
      (s1 's1bad)
      (s2 's2bad)
      (strange-fact 'sfbad)
      (z 'zbad))
  (define z 1)
  (define a 'abad2)
  (define c 'cbad2)
  (define strange-fact 'sfbad4)
  (define-signature macro (a b c
                             (define-values (v1) (list a b c z 2))
                             (define-values (v2) (s2 a b c))
                             (define-values (strange-fact)
                                                (lambda (x)
                                                  (if (= x 0) (list z a b c) (cons x (strange-fact (sub1 x))))))
                             (define-syntaxes (s1 s2)
                                                  (values
                                                   (syntax-rules ()
                                                     ((_ a1 b1 c1) (list a b c v1 a1 b1 c1 z)))
                                                   (syntax-rules ()
                                                     ((_ a1 b1 c1) (s1 a1 b1 c1)))))))
  (let ((b 'bbad2)
        (c 'cbad3))
    (define z 3)
    (define u1
      (unit (import macro) (export)
        (define z 4)
        (list a b c v1 v2 (strange-fact 5) (s1 6 7 8) (s2 9 10 11))))
    (define u2
      (unit (import) (export macro)
         (define a 12)
         (define b 13)
         (define c 14)))
    (test '(12 13 14
               (12 13 14 1 2)
               (12 13 14 (12 13 14 1 2) 12 13 14 1)
               (5 4 3 2 1 1 12 13 14)
               (12 13 14 (12 13 14 1 2) 6 7 8 1)
               (12 13 14 (12 13 14 1 2) 9 10 11 1))
      (invoke-unit
       (compound-unit (import) (export)
         (link (((U2 : macro)) u2)
               (() u1 U2)))))))
  

;; We can re-define imported values
(let ()
  (define-signature s ((define-values (y) 1)))
  (define-signature t (z))
    (test 3
      (invoke-unit
       (compound-unit (import) (export)
                      (link (((T : t)) (unit (import s) (export t) (define y 3) (define z y)) S)
                            (((S : s)) (unit (import) (export s) (define y 1)))
                            (() (unit (import t) (export) z) T))))))

;; Can't use imports as pattern variables
#;(let ()
  (define-signature s (y (define-syntaxes (m) (syntax-rules (y) ((_ y) 1)))))
  (unit (import s) (export)
        (m y)))


(test '(2 3)
  (let ()
    (define-signature sig (y (define-values (v) (add1 y))))
    (let ()
      (define-values/invoke-unit
       (unit (import) (export sig) (define y 2))
       (import)
       (export sig))
      (list y v))))


;; I'm not sure that this should work.
#;(test '(2 3)
  (let ()
    (define-signature sig (y (define-values (v) (add1 y))))
    (define-values/invoke-unit
     (unit (import) (export sig) (define y 2))
     sig)
    (list y v)))
  
  

;; subtyping

(let ()
  (define u1 (unit (import x-sig) (export y-sub) (define y (add1 x)) (define yy 2) (list x y yy)))
  (define u2 (unit (import y-sig) (export x-sub) (define x 3) (define xx 44)))
  (define u3 (compound-unit (import (S1 : x-sig)) (export S4)
                            (link (((S4 : y-sub)) u1 S1))))
  (define u4 (compound-unit (import (S3 : y-sig)) (export S2)
                            (link (((S2 : x-sub)) u2 S3))))
  (define u5 (compound-unit (import (S2 : y-sub)) (export S1)
                            (link (((S1 : x-sig)) u2 S2))))
  (test '(3 4 2)
        (invoke-unit
         (compound-unit (import) (export)
                        (link (((S1 : x-sig)) u2 S2)
                              (((S2 : y-sig)) u1 S1)))))
  (test '(3 4 2)
        (invoke-unit
         (compound-unit (import) (export)
                        (link (((S1 : x-sub)) u2 S2)
                              (((S2 : y-sub)) u1 S1)))))
  
  
  (test '(3 4 2)
        (invoke-unit
         (compound-unit (import) (export)
                        (link (((S1 : x-sub)) u4 S4)
                              (((S4 : y-sub)) u3 S1)))))
  (test '(3 4 2)
        (invoke-unit
         (compound-unit (import) (export)
                        (link (((S1 : x-sig)) u4 S4)
                              (((S4 : y-sig)) u3 S1)))))
  
  
  (test '(3 4 2)
        (invoke-unit
         (compound-unit (import) (export)
                        (link (((S1 : x-sig)) u5 S2)
                              (((S2 : y-sub)) u1 S1))))))
(let ()  
  (define u1 (unit (import) (export x-sig) (define x 1)))
  (define u2 (unit (import x-sub) (export)))
  
  (test-runtime-error exn:fail:contract? "compound-unit: not a subtype"
                      (compound-unit (import) (export)
                                     (link (((S : x-sub)) u1))))
  
  (test-runtime-error exn:fail:contract? "compound-unit: not a subtype"
                      (compound-unit (import) (export)
                                     (link (((S : x-sig)) u1)
                                           (() u2 S))))
  
  (test-runtime-error exn:fail:contract? "compound-unit: not a subtype"
                      (compound-unit (import (S : x-sig)) (export)
                                     (link (() u2 S)))))

(let ()
  (define u1 (unit (import) (export x-sub y-sub) (define x 1) (define xx 2) (define y 3) (define yy 4)))
  (define-values/invoke-unit u1 (import) (export x-sig))
  (test 1 x)
  (test-runtime-error exn? "unbound identifier" xx)
  (test-runtime-error exn? "unbound identifier" y)
  (test-runtime-error exn? "unbound identifier" yy))

(let ()
  (define u1 (unit (import) (export x-sig) (define x 1)))
  (test-runtime-error exn:fail:contract? "define-values/invoke-unit: not a subtype"
                      (define-values/invoke-unit u1 (import) (export x-sub))))

;; export-subtyping
(test-syntax-error "duplicate exports (subtypes)"
                   (unit (import) (export x-sig x-sub)
                         (define x 1)
                         (define xx 1)))
(test-syntax-error "duplicate exports (subtypes)"
                   (unit (import) (export x-sub x-sig)
                         (define x 1)
                         (define xx 1)))
(let ()
  (define u (unit (import) (export x-sub) (define x 1) (define xx 1)))
  (test-syntax-error "duplicate exports (subtypes)"
                     (compound-unit (import) (export l1 l2)
                                    (link (((l1 : s1)) u)
                                          (((l2 : s2)) u)))))
(let ()
  (define u (unit (import) (export x-sub (prefix x: x-sub2))
                  (define x 1)
                  (define xx 2)
                  (define x:x 3)
                  (define x:x2 4)))
  (define u2 (unit (import x-sig) (export)))
  (define v (unit (import x-sub) (export)
                  (+ x xx)))
  (define w (unit (import x-sub2) (export)
                  (+ x x2)))
  (define u3 (unit (import x-sub (prefix m: x-sub2)) (export)
                   (+ x xx m:x m:x2)))
  (test 3
        (invoke-unit
         (compound-unit (import) (export)
                        (link (((S2 : x-sub)) u)
                              (() v S2)))))
  (test 7
        (invoke-unit
         (compound-unit (import) (export)
                        (link (((S3 : x-sub2)) u)
                              (() w S3)))))
  (test 10
        (invoke-unit
         (compound-unit (import) (export)
                        (link (((S3 : x-sub2) (S2 : x-sub)) u)
                              (() u3 S3 S2)))))
  (test-runtime-error exn:fail:contract? "ambiguous export"
                      (compound-unit (import) (export)
                                     (link (((S1 : x-sig)) u)))))
  (test-runtime-error exn:fail:contract? "ambiguous import"
                      (compound-unit (import (S1 : x-sub) (S2 : x-sub2)) (export)
                                     (link (() u2 S1 S2))))

(test-syntax-error "duplicate links (subtype)"
                    (compound-unit (import) (export)
                                     (link (((S1 : x-sig)) u3)
                                           (() u1 S2 S1)
                                           (((S2 : x-sig)) u3))))
;; tags
(let ()
  (define-signature s1 (a))
  (define-signature s2 extends s1 (b))
  (define-signature s3 extends s2 ())
  (define-signature s4 extends s3 ())
  (define u1
    (unit (import (prefix s1: s1) 
                  (tag t (prefix s2: s2))
                  (prefix bs1: s2) 
                  (prefix bs2: s3))
          (export)
          (list s1:a s2:a s2:b bs1:a bs2:b)))
  (define u2
    (unit (import) (export s3)
          (define a 1) (define b 2)))
  (define u3
    (unit (import) (export s2)
          (define a 3) (define b 4)))
  (test '(1 3 4 1 2)
        (invoke-unit
         (compound-unit (import) (export)
                        (link (((S2a : s3)) u2)
                              (((S2b : s2)) u3)
                              (() u1 S2a (tag t S2b))))))
  (test-runtime-error exn:fail:contract? "compound-unit: signature mismatch"
        (invoke-unit
         (compound-unit (import) (export)
                        (link (((S1 : s1)) u2)
                              (((S2 : s2)) u3)
                              (() u1 (tag t S1) S2))))))
(let ()
  (define u1
    (unit (import) (export (prefix a: x-sig) (tag t (prefix c: x-sig)))
          (define a:x 1)
          (define c:x 4)))
  (define u2
    (unit (import x-sig) (export)
          x))
  (define u3
    (unit (import x-sub) (export)
          (list x xx)))

  (test 4
        (invoke-unit
         (compound-unit (import) (export)
                        (link (((S1 : (tag t x-sig)) (S2 : x-sig)) u1)
                              (() u2 S1)))))
  (test-runtime-error exn:fail:contract? "compound-unit: signature mismatch"
                      (invoke-unit
                       (compound-unit (import) (export)
                                      (link (((S1 : (tag t x-sub)) (S2 : x-sub)) u1)
                                            (() u2 S1)))))
  )

(let ()
  (define u1 (unit (import) (export (tag t1 x-sig) (prefix : x-sig))
                   (define x 10)
                   (define :x 11)))
  (define-values/invoke-unit u1 (import) (export x-sig (tag t1 (prefix m x-sig))))
  (test '(11 10)
        (list x mx)))


(define-signature s1 (x))
(define-signature s2 (a x z))


(test-syntax-error "unit-from-context: no sigs"
  (unit-from-context))
(test-syntax-error "unit-from-context: too many sigs"
  (unit-from-context s1 s2))
(test-syntax-error "unit-from-context: too many sigs"
  (unit-from-context s1 . s2))
(test-syntax-error "unit-from-context: bad sig"
  (unit-from-context 1))

(test-syntax-error "unit-from-context: no name"
  (define-unit-from-context))
(test-syntax-error "unit-from-context: no sigs"
  (define-unit-from-context s1))
(test-syntax-error "unit-from-context: no sigs"
  (define-unit-from-context n))
(test-syntax-error "unit-from-context: too many sigs"
  (define-unit-from-context n s1 s2))
(test-syntax-error "unit-from-context: too many sigs"
  (define-unit-from-context n s1 . s2))
(test-syntax-error "unit-from-context: bad sig"
  (define-unit-from-context n 1))



;; Test the struct form
(test-syntax-error "struct: missing name and fields"
  (define-signature x ((struct))))
(test-syntax-error "struct: missing name"
  (define-signature x ((struct n))))
(test-syntax-error "struct: bad name"
  (define-signature x ((struct 1 ()))))
(test-syntax-error "struct: bad fields (dot)"
  (define-signature x ((struct n (x . y)))))
(test-syntax-error "struct: bad fields"
  (define-signature x ((struct n 1))))
(test-syntax-error "struct: bad omission"
  (define-signature x ((struct n () t))))
(test-syntax-error "struct: bad omission (dot)"
  (define-signature x ((struct n () . -selectors))))
(test-syntax-error "struct: bad omission"
  (define-signature x ((struct n () x))))

(let ()
  (define-signature sig ((struct s (x y))))
  (test 3
    (invoke-unit
     (compound-unit (import) (export)
                    (link (((S : sig)) (unit (import) (export sig)
                                             (define-struct s (x y))))
                          (() (unit (import sig) (export)
                                    (match (make-s 1 2)
                                      ((struct s (a b)) (+ a b))))
                              S)))))
  (let ()
    (define-values/invoke-unit (unit (import) (export sig) (define-struct s (x y)))
      (import)
      (export sig))
    (test 3
      (match (make-s 1 2)
        ((struct s (a b)) (+ a b)))))
  (let ()
    (define u
      (unit (import) (export (rename sig (make-s/defaults make-s)))
            (define-struct s (x y))
            (define (make-s/defaults x)
              (make-s x 'default))))
    (define-values/invoke-unit u (import) (export sig))
    (test #t (s? (make-s 1))))

  (let ((set-s-x! 1))
    (define-signature sig ((struct s (x y))))
    (test 1
      (invoke-unit
       (compound-unit (import) (export)
                      (link (((S : sig)) (unit (import) (export sig) (define-struct s (x y))))
                            (() (unit (import sig) (export)
                                      set-s-x!) S))))))
  (let ((make-s 1))
    (define-signature sig ((struct s (x y) #:omit-constructor)))
    (test 1
      (invoke-unit
       (compound-unit (import) (export)
                      (link (((S : sig)) (unit (import) (export sig) (define-struct s (x y))))
                            (() (unit (import sig) (export)
                                      make-s) S)))))))

;; Dependencies

(define-signature s1 (a))
(define-signature s2 extends s1 ())

(define u1 (unit (import s1) (export) (init-depend s1)
                 a))
(define u2 (unit (import) (export s1)
                 (define a 12)))
(define u3 (unit (import (tag t s1)) (export) (init-depend (tag t s1))
                 a))

(define u4 (compound-unit (import (L : s2)) (export)
                          (link (() u1 L))))
(define u5 (unit (import) (export s2)
                 (define a 12)))
(test-syntax-error "unit: bad dependency"
                   (unit (import (tag t s1)) (export) (init-depend s1)))
(test-syntax-error "unit: bad dependency"
                   (unit (import s1) (export) (init-depend (tag t s1))))

(test 12 (invoke-unit (compound-unit (import) (export)
                                     (link (((S1 : s1)) u2)
                                           (() u1 S1)))))

(test-runtime-error exn:fail:contract? "Dependency violation"
                    (compound-unit (import) (export)
                                     (link (() u1 S1)
                                           (((S1 : s1)) u2))))

(test-runtime-error exn:fail:contract? "Dependency violation"
                    (compound-unit (import) (export)
                                     (link (() u3 (tag t S1))
                                           (((S1 : s1)) u2))))



(test-runtime-error exn:fail:contract? "Dependency violation"
                    (compound-unit (import) (export)
                                     (link (() u4 S2)
                                           (((S2 : s2)) u5))))
;; Inference


(define u (unit (import x-sig) (export y-sig) (define y 0) x))
(define v (unit (import) (export x-sig y-sig) (define x 9) (define y 10)))

(test 9
      (let ()
        (define-unit-binding u2 u (import x-sig y-sig) (export))
        (invoke-unit
         (compound-unit (import) (export)
                        (link (((A : x-sig) (B : y-sig)) v)
                              (() u A B))))))

(test-runtime-error exn:fail:contract? "not subunit"
                    (let () (define-unit-binding u2 u (import x-sig) (export x-sig)) 1))
(test-runtime-error exn:fail:contract? "not subunit"
                    (let () (define-unit-binding u2 u (import) (export)) 1))
(test-runtime-error exn:fail:contract? "not a unit"
                    (let () (define-unit-binding u2 1 (import) (export)) 1))

(test-syntax-error "define-unit-binding: duplicate import"
                   (define-unit-binding u 1 (import x-sig x-sig) (export)))
(test-syntax-error "define-unit-binding: export subtypes"
                   (define-unit-binding u 1 (import) (export x-sig x-sub)))
(test-syntax-error "define-unit-binding: export subtypes"
                   (define-unit-binding u 1 (import) (export x-sub x-sig)))
(test-syntax-error "define-unit-binding: bad dependency"
                   (define-unit-binding u 1 (import x-sig) (export) (init-depend x-sub)))
(test-syntax-error "define-unit-binding: bad dependency"
                   (define-unit-binding u 1 (import x-sub) (export) (init-depend x-sig)))


(test-syntax-error "define-unit: missing name, import, export"
  (define-unit))
(test-syntax-error "define-unit: missing import, export"
  (define-unit a))
(test-syntax-error "define-unit: missing export"
  (define-unit a (import)))
(test-syntax-error "define-unit: missing name"
  (define-unit (import) (export)))
(test-syntax-error "define-unit: bad name"
  (define-unit "x" (import) (export)))
(test-syntax-error "define-unit: bad syntax"
  (define-unit x (unit (import) (export))))
(test-runtime-error exn:fail:contract? "define-unit: bad set!"
  (let ()
    (define-signature s ())
    (define-unit x (import) (export) 1)
    (set! x (unit (import s) (export) 1))))
(test-runtime-error exn:fail:contract? "define-unit: bad set!"
  (let ()
    (define-signature s ())
    (define-unit x (import) (export s) 1)
    (set! x (unit (import) (export) 1))))


(test-syntax-error "define-compound-unit: missing import"
  (define-compound-unit x))
(test-syntax-error "define-compound-unit: missing name"
  (define-compound-unit))
(test-syntax-error "define-compound-unit: missing name"
  (define-compound-unit (import) (link) (export)))
(test-syntax-error "define-compound-unit: bad name"
  (define-compound-unit 1 (import) (link) (export)))

(test-syntax-error "define-values/invoke-unit/infer: no unit"
  (define-values/invoke-unit/infer))
(test-syntax-error "define-values/invoke-unit/infer: not a unit"
  (define-values/invoke-unit/infer 1))
(test-syntax-error "define-values/invoke-unit/infer: not a unit"
  (let ((x 1))
    (define-values/invoke-unit/infer x)))
(test-syntax-error "define-values/invoke-unit/infer: not a unit"
  (let-syntax ((x 1))
    (define-values/invoke-unit/infer x)))
(test-syntax-error "define-values/invoke-unit/infer: too much"
  (define-values/invoke-unit/infer x y))

(define-unit u (import x-sig) (export))
(test-syntax-error "define-values/invoke-unit/infer: bad imports"
  (define-values/invoke-unit/infer u))
(define-unit u (import x-sig y-sig) (export))
(test-syntax-error "define-values/invoke-unit/infer: bad imports"
  (define-values/invoke-unit/infer u))
(define-unit u (import) (export x-sig y-sig)
  (define x 10)
  (define y 20))
(test 30
  (let ()
    (define-values/invoke-unit/infer u)
    (+ y x)))


(test 1
  (let ()
    (define-unit x (import) (export) 1)
    (invoke-unit x)))
(test 1
  (let ()
    (define-unit x (import) (export) 1)
    (let ((u 1))
      (invoke-unit x))))
(test 2
  (let ()
    (define-unit x (import) (export) 1)
    (set! x (unit (import) (export) 2))
    (invoke-unit x)))



(let ()
  (define-signature s1 (a))
  (define-signature s2 extends s1 (b))
  (define-signature s3 (c))
  (define-signature s4 extends s3 (d))
  (define-unit u (import s2) (export s3) (define c (+ a b)))
  (define-unit v (import) (export s2) (define a 1) (define b 3))
  (set! u (unit (import s1) (export s4) (define c (add1 a)) (define d 12)))
  (let ()
    (define-values/invoke-unit (compound-unit/infer (import) (export s2 s3) (link v u)) 
      (import) (export s2 s3))
    (test '(1 3 2) (list a b c))))


(test-syntax-error "compound-unit/infer: missing export"
  (compound-unit/infer (link) (import)))
(test-syntax-error "compound-unit/infer: bad unit"
  (compound-unit/infer (import) (export) (link 1)))
(test-syntax-error "compound-unit/infer: bad import"
  (compound-unit/infer (import (a : b)) (export) (link)))
(test-syntax-error "compound-unit/infer: bad link"
  (compound-unit/infer (import) (export) (link (((A : b)) c))))
(test-syntax-error "compound-unit/infer: unknown sig"
  (compound-unit/infer (import ??) (export) (link)))
(test-syntax-error "compound-unit/infer: unknown sig"
  (compound-unit/infer (import) (export ??) (link)))
(test-syntax-error "compound-unit/infer: unknown sig"
  (compound-unit/infer (import) (export) (link (() u ??))))


(define-unit x
  (import x-sig)
  (export y-sig)
  (define y x)
  y)

(define-unit y
  (import y-sig)
  (export (rename x-sig (x x)))
  (define x y)
  x)

(define-unit z
  (import (prefix : x-sig) y-sig)
  (export)
  (+ :x y))

(define-unit a
  (import)
  (export x-sig y-sig z-sig)
  (define x 1)
  (define y 2)
  (define z 3))

(define-unit b
  (import x-sig y-sig z-sig)
  (export)
  (+ x y z))

(test-syntax-error "compound-unit/infer: re-export"
  (compound-unit/infer (import (l : x-sig)) (export x-sig) (link)))
(test-syntax-error "compound-unit/infer: duplicate def and import"
  (compound-unit/infer (import y-sig x-sig) (export) (link x y)))
(test-syntax-error "compound-unit/infer: unprovided sig"
  (compound-unit/infer (import) (export) (link x)))
(test-syntax-error "compound-unit/infer: unprovided sig"
  (compound-unit/infer (import) (export x-sig) (link)))

(test (letrec ((x x)) x)
  (invoke-unit 
   (compound-unit/infer (import) (export)
                        (link x y))))

(test 3
  (let ()
    (define-signature s (x y))
    (let ((x 1)
          (y 2))
      (define-unit-from-context u1 s)
      (define-unit u2 (import (prefix : s)) (export)
        (+ :x :y))
      (invoke-unit
       (compound-unit/infer (import) (export)
                            (link u1 u2))))))
(test 6
      (invoke-unit
       (compound-unit/infer (import) (export)
                            (link (((L1 : y-sig)) a)
                                  x
                                  (() b L1)))))

(let ()
  (define-unit u1 (import (tag t x-sig)) (export)
    (add1 x))
  (define-unit u2 (import) (export x-sig)
    (define x 2))
  (test 3
        (invoke-unit
         (compound-unit/infer (import) (export)
                              (link u2 u1)))))
(let ()
  (define-unit u1 (import x-sig) (export)
    (add1 x))
  (define-unit u2 (import) (export (tag u x-sig))
    (define x 2))
  (test 3
        (invoke-unit
         (compound-unit/infer (import) (export)
                              (link u2 u1)))))

(let ()
  (define-unit x2 (import) (export x-sig) (define x 44))
  (define-unit x3 (import) (export x-sig) (define x 4400))
  (define-unit z (import x-sig) (export z-sig) (define z (+ 100 x)))
  (define-compound-unit/infer u (import (L : x-sig)) (export L2 y-sig)
    (link x3 (((L2 : z-sig)) z L) (() a L)))
  (test 190
        (invoke-unit
         (compound-unit/infer (import) (export)
                              (link x2 u b)))))

(let ()
  (define-unit u (import x-sig) (export))
  (define-unit v (import) (export x-sub) (define x 12) (define xx 13))
  (define-compound-unit/infer c (import) (export x-sig) (link v u))
  (define-values/invoke-unit/infer c)
  (test 12 x))


(let ()
  (define-unit u (import) (export x-sig)
    (define x 12))
  (define-unit u2 (import) (export x-sig)
    (define x 13))
  (define-unit v (import) (export y-sig)
    (define y 11))
  (define-unit v2 (import) (export y-sig)
    (define y 1))
  (define-unit u3 (import y-sig x-sig) (export)
    (+ y x))
  (test 24
        (invoke-unit
         (compound-unit/infer (import) (export)
                              (link (((l : x-sig)) u)
                                    (((l2 : x-sig)) u2)
                                    (((l3 : y-sig)) v)
                                    (((l4 : y-sig)) v2)
                                    (() u3 l2 l3))))))

;; unit/new-import-export

(test-runtime-error exn:fail:contract? "unit/new-import-export: not a unit"
  (unit/new-import-export (import) (export)
                          (() 1)))

(test-runtime-error exn:fail:contract? "unit/new-import-export: not a subtype"
  (unit/new-import-export (import) (export)
                          ((x-sig) (unit (import) (export)))))


(test-runtime-error exn:fail:contract? "unit/new-import-export: not a subtype"
  (unit/new-import-export (import) (export)
                          (() (unit (import x-sig) (export)))))

(define-unit u (import x-sig) (export y-sig)
  (define y x))

(test-syntax-error "unit/new-import-export: not enough imports"
  (unit/new-import-export (import) (export x-sig)
                          ((y-sig) u x-sig)))

(test-syntax-error "unit/new-import-export: too many exports"
  (unit/new-import-export (import x-sig) (export y-sig z-sig)
                          ((y-sig) u x-sig)))

(let ()
  (define-unit u 
    (import xy-sig) (export z-sig)
    (define z (+ x y)))
  (define-unit v
    (import) (export x-sig y-sig)
    (define x 4)
    (define y 8))
  (define-unit w (import z-sig) (export)
    z)
  (define-unit/new-import-export u2 (import x-sig y-sig) (export z-sig)
    ((z-sig) u xy-sig))
  (test 12
        (invoke-unit (compound-unit/infer (import) (export)
                                          (link v u2 w)))))
      
(let ()
  (define-unit u 
    (import x-sig y-sig) (export z-sig)
    (define z (+ x y)))
  (define-unit v
    (import) (export xy-sig)
    (define x 4)
    (define y 8))
  (define-unit w (import z-sig) (export)
    z)
  (define-unit/new-import-export u2 (import xy-sig) (export z-sig)
    ((z-sig) u y-sig x-sig))
  (test 12
        (invoke-unit (compound-unit/infer (import) (export)
                                          (link v u2 w)))))
      
(let ()
  (define-unit u 
    (import xy-sig) (export z-sig)
    (define z (+ x y)))
  (define-unit v
    (import) (export x-sig y-sig)
    (define x 4)
    (define y 8))
  (define-unit w (import z-sig) (export)
    z)
  (define-unit/new-import-export v2 (import) (export xy-sig)
    ((x-sig y-sig) v))
  (test 12
        (invoke-unit (compound-unit/infer (import) (export)
                                          (link v2 u w)))))
      
(let ()
  (define-unit u 
    (import x-sig y-sig) (export z-sig)
    (define z (+ x y)))
  (define-unit v
    (import) (export xy-sig)
    (define x 4)
    (define y 8))
  (define-unit w (import z-sig) (export)
    z)
  (define-unit/new-import-export v2 (import) (export y-sig x-sig)
    ((xy-sig) v))
  (test 12
        (invoke-unit (compound-unit/infer (import) (export)
                                          (link v2 u w)))))




;; open
(let ()
  (define-signature xzy
    ((open x-sig) (open y-sig) (open z-sig)))
  
  (define-unit u (import xzy) (export)
    (+ x z y))
  
  (define-unit v (import) (export xzy)
    (define x 10)
    (define y 20)
    (define z 30))
  
  (test 60
        (invoke-unit (compound-unit/infer (import) (export) (link v u)))))

(let ([x 1]
      [y 2]
      [z 3])  
  (define-signature xzy
    ((open x-sig) (open y-sig) (open z-sig)))
  
  (define-unit u (import xzy) (export)
    (+ x z y))
  
  (define-unit v (import) (export xzy)
    (define x 10)
    (define y 20)
    (define z 30))
  
  (test 60
        (invoke-unit (compound-unit/infer (import) (export) (link v u)))))

(define-signature s
  (x (define-values (y) (add1 x))))

(let ([x 1]
      [y 10]
      [s:x 100]
      [s:y 1000])
  (define-signature s2
    ((open (prefix s: s)) x (define-values (y) (sub1 x))))
  (define-unit u1 (import s2) (export)
    (list s:x s:y x y))
  (define-unit u2 (import) (export s2)
    (define s:x 3)
    (define x 19))
  (test '(3 4 19 18)
        (invoke-unit (compound-unit/infer (import) (export) (link u2 u1)))))
