#lang typed/racket

(require typed/rackunit)

;; tagged
(begin
  ;; Base struct for all tagged values
  (struct (B) tagged ([value : B]))
  
  ;; (define-tagged a T)
  ;; Use tag-a just for match and (get …)
  (struct (B) tag-a tagged ()) ;; "remember" this one
  ;; (define-tagged #:uninterned a T)
  (struct (B) tag-a-1 tag-a ()) ;; do not "rembmer" the #:uninterned ones
  ;; (define-tagged #:uninterned a T)
  (struct (B) tag-a-2 tag-a ())
  ;; (define-tagged #:uninterned a T)
  (struct (B) tag-a-3 tag-a ())
  ;; (define-tagged b T)
  (struct (B) tag-b tagged ())
  
  ;; instanciation:
  (tag-a "ice")
  
  (define-syntax-rule (define-pred name? tag?)
    (define-match-expander name?
      (λ (stx)
        (syntax-case stx ()
          [(_ v)
           #'(and (? tag?) (app tagged-value v))]))))
  
  (define-pred tagged-a-1? tag-a-1?)
  (define-pred tagged-a-2? tag-a-2?)
  (define-pred tagged-a? tag-a?)
  (define-pred tagged-b? tag-b?)
  
  (check-true (tag-a? (tag-a "water")))
  (check-true (tag-a-1? (tag-a-1 "fire")))
  (check-true (tag-a-1? (tag-a-1 "air")))
  (check-false (tag-a-1? (tag-a-2 "earth")))
  (check-false (tag-a-1? (tag-a "salt")))
  (check-false (tag-b? (tag-a "mercury")))
  (check-false (tag-b? (tag-a-1 "alchemy")))
  
  (λ ([x : (U (tag-a-1 String)
              (tag-a-2 Number)
              (tag-b Symbol))])
    : (U Number String)
    ;; Match should expand to this:
    (match x
      [(tagged-a? v) v]
      [(tagged-b? v) (symbol->string v)]))
  
  (λ ([x : (U (tag-a-1 String)
              (tag-a-2 String)
              (tag-a String)
              (tag-b Symbol))])
    : String
    ;; Match should expand to this
    (match x
      [(tagged-a-1? v) (string-append v "-1")]
      [(tagged-a-2? v) (string-append v "-2")]
      [(tagged-a? v) (string-append v "-base")]
      [(tagged-b? v) (symbol->string v)])))

;; struct
(begin
  ;; The structs seem to work well the way they are currently defined (using
  ;; "remember" to know all the structs with a given field).
  
  ;; (define-struct [x : Number] [y : String])
  (struct (X Y) structure+x+y ([x : X] [y : Y])) ;; "remember"
  (define-type structure+x=number+y=string (structure+x+y Number String))
  
  ;; (define-struct [x : Number] [y : String] [z : Symbol])
  (struct (X Y Z) structure+x+y+z ([x : X] [y : Y] [z : Z])) ;; "remember"
  (define-type structure+x=number+y=string+z=symbol
    (structure+x+y+z Number String Symbol))
  
  ;; (define-struct [z : Symbol])
  (struct (Z) structure+z ([z : Z])) ;; "remember"
  (define-type structure+z=symbol (structure+z Symbol))
  
  ;; (define-struct [x : Symbol])
  (struct (X) structure+x ([x : X])) ;; "remember"
  (define-type structure+x=symbol (structure+x Symbol))
  
  ;; (has-get [x Number])
  (define-type (has-get+x X) (U (structure+x X)
                                (structure+x+y X Any)
                                (structure+x+y+z X Any Any)))
  (define-type has-get+x=number (has-get+x Number))
  
  ;; (has-get [y Number])
  (define-type (has-get+y Y) (U (structure+x+y Any Y)
                                (structure+x+y+z Any Y Any)))
  (define-type has-get+y=number (has-get+y Number))
  
  ;; (has-get [z Number])
  (define-type (has-get+z Z) (U (structure+z Z)
                                (structure+x+y+z Any Any Z)))
  (define-type has-get+z=number (has-get+z Number))
  
  ;; (get v x)
  (define get-x
    (λ #:∀ (X) ([s : (has-get+x X)]) : X
      (cond
        [(structure+x? s) (structure+x-x s)]
        [(structure+x+y? s) (structure+x+y-x s)]
        [(structure+x+y+z? s) (structure+x+y+z-x s)])))
  
  ;; (get v y)
  (define get-y
    (λ #:∀ (Y) ([s : (has-get+y Y)]) : Y
      (cond
        [(structure+x+y? s) (structure+x+y-y s)]
        [(structure+x+y+z? s) (structure+x+y+z-y s)])))
  
  ;; (get v z)
  (define get-z
    (λ #:∀ (Z) ([s : (has-get+z Z)]) : Z
      (cond
        [(structure+z? s) (structure+z-z s)]
        [(structure+x+y+z? s) (structure+x+y+z-z s)]))))

;; graph
(begin
  ;; define-graph
  (define-type g-test (tag-a-1 (structure+x+y String g-test)))
  (define-type g (tag-a-2 (structure+x+y+z String g Number)))
  ;; pass:
  (λ ([root-node : (U g g-test)])
    (ann (match root-node
           [(tagged-a-1? s) (string-length (get-x s))]
           [(tagged-a-2? s) (get-z s)])
         Number)
    (cons (get-x (tagged-value root-node))
          (get-y (tagged-value root-node)))))