#lang racket/base

(provide (struct-out ty-union)
         (struct-out ty-unit)
         (struct-out ty-value)
         (struct-out ty-record)
         (struct-out ty-array)
         (struct-out ty-set)
         (struct-out ty-dictionary)

         (struct-out ty-variant)
         (struct-out ty-field)

         definition-ty
         unwrap
         escape
         module-path-prefix)

(require preserves/record)
(require preserves/order)
(require racket/match)
(require (only-in racket/syntax format-symbol))
(require (only-in racket/string string-join))

(require "gen/schema.rkt")

(struct ty-union (variants) #:transparent)
(struct ty-unit () #:transparent)
(struct ty-value () #:transparent)
(struct ty-record (fields) #:transparent)
(struct ty-array (type) #:transparent)
(struct ty-set (type) #:transparent)
(struct ty-dictionary (key-type value-type) #:transparent)

(struct ty-variant (name type pattern) #:transparent)
(struct ty-field (name type pattern) #:transparent)

(define (definition-ty d)
  (match d
    [(Definition-or p0 p1 pN)
     (ty-union (map (match-lambda [(NamedAlternative variant-label-str pattern)
                                   (ty-variant (string->symbol variant-label-str)
                                               (pattern-ty pattern)
                                               pattern)])
                    (list* p0 p1 pN)))]
    [(Definition-and p0 p1 pN) (product-ty (list* p0 p1 pN))]
    [(Definition-Pattern pattern) (pattern-ty pattern)]))

(define (product-ty named-pats)
  (match (gather-fields* named-pats '())
    ['() (ty-unit)]
    [fields (ty-record fields)]))

(define (unwrap p)
  (match p
    [(Pattern-SimplePattern p) (unwrap p)]
    [(Pattern-CompoundPattern p) (unwrap p)]
    [(NamedPattern-named p) (unwrap p)]
    [(NamedSimplePattern-named p) (unwrap p)]
    [(NamedPattern-anonymous p) (unwrap p)]
    [(NamedSimplePattern-anonymous p) (unwrap p)]
    [_ p]))

(define (gather-fields* named-pats acc)
  (foldr gather-fields acc named-pats))

(define (gather-fields named-pat acc)
  (match (unwrap named-pat)
    [(Binding n p)
     (match (pattern-ty p)
       [(ty-unit) acc]
       [ty (cons (ty-field n ty p) acc)])]
    [(? SimplePattern?) acc]
    [(CompoundPattern-rec label-named-pat fields-named-pat)
     (gather-fields label-named-pat (gather-fields fields-named-pat acc))]
    [(CompoundPattern-tuple named-pats)
     (gather-fields* named-pats acc)]
    [(CompoundPattern-tuplePrefix fixed-named-pats variable-named-pat)
     (gather-fields* fixed-named-pats (gather-fields variable-named-pat acc))]
    [(CompoundPattern-dict (DictionaryEntries entries))
     (gather-fields* (map cdr (sorted-dict-entries entries)) acc)]))

(define (pattern-ty p)
  (match (unwrap p)
    [(SimplePattern-any) (ty-value)]
    [(SimplePattern-atom _atomKind) (ty-value)]
    [(SimplePattern-embedded _interface) (ty-value)]
    [(SimplePattern-lit _value) (ty-unit)]
    [(SimplePattern-seqof pat) (ty-array (pattern-ty pat))]
    [(SimplePattern-setof pat) (ty-set (pattern-ty pat))]
    [(SimplePattern-dictof kp vp) (ty-dictionary (pattern-ty kp) (pattern-ty vp))]
    [(SimplePattern-Ref _r) (ty-value)]
    [(? CompoundPattern?) (product-ty (list p))]))

(define (escape s)
  (format-symbol "?~a" s))

(define (module-path-prefix module-path)
  (if (null? module-path)
      '||
      (format-symbol "~a:" (string-join (map symbol->string module-path) ":"))))
