#lang racket
(require (for-syntax racket 
                     syntax/parse
                     syntax/strip-context)
         racket/stxparam
         "lib.rkt"
         "syntax.rkt"
         (for-syntax "lib.rkt"))

(define-syntax-parameter =>*
  (λ (stx) (raise-syntax-error '=>* "Only allowed inside formlet*" stx)))

(define (snoc x l) (append l (list x)))

(struct label-formlet (p))

(define (label-formlet-answers labels formlet)
  (label-formlet
   (cross (pure (λ anss
                  (λ (answers) 
                    (for ([label (in-list labels)]
                          [ans (in-list anss)])
                      (hash-update! answers label (curry snoc ans) empty)))))
          formlet)))

(define xexpr-forest->label-formlet
  (match-lambda
    [(list)
     (pure (λ (x) x))]
    [(list-rest xe xf)
     (cross* (pure (lambda (xe-populate-hash! xf-populate-hash!)
                     (lambda (answers)
                       (xe-populate-hash! answers)
                       (xf-populate-hash! answers))))
             (xexpr->label-formlet xe)
             (xexpr-forest->label-formlet xf))]))

(define xexpr->label-formlet
  (match-lambda
    [(#%#-mark l)
     (xexpr-forest->label-formlet l)]
    [(list (? symbol? tag) (list (list (? symbol? attr) (? string? str)) ...) xexpr ...)
     (tag-xexpr tag (map list attr str)
                (xexpr-forest->label-formlet xexpr))]
    [(list (? symbol? tag) xexpr ...)
     (tag-xexpr tag empty
                (xexpr-forest->label-formlet xexpr))]
    [(label-formlet p)
     p]
    [(? string? s)
     (text s)]))

(define (label-formlet-cross handler xexpr/labeled-formlets)
  (cross (pure (λ (populate-hash!)
                 (define ht (make-hasheq))
                 (populate-hash! ht)
                 (handler ht)))
         (xexpr->label-formlet xexpr/labeled-formlets)))

(struct #%#-mark (l))
(define-syntax-rule (inner-#%# e ...) (#%#-mark (list e ...)))

(define-syntax (formlet* stx)
  (syntax-case stx ()
    [(_ q e)
     (local [(define label->name (make-hash))
             (define (this-=>* stx)
               (syntax-parse stx
                             #:literals (values)
                             [(=>* formlet:expr name:id)
                              #'(=>* formlet (values name))]
                             [(_ formlet:expr (values name:id ...))
                              (define names (syntax->list #'(name ...)))
                              (define labels (map (compose gensym syntax->datum) names))
                              (for ([label (in-list labels)]
                                    [name (in-list names)])
                                (hash-set! label->name label (replace-context #'e name)))
                              #`(label-formlet-answers '#,labels formlet)]))
             (define q-raw 
               (local-expand #`(syntax-parameterize ([=>* #,this-=>*]
                                                     [#%# (make-rename-transformer #'inner-#%#)])
                                                    q)
                             'expression empty))]
       (with-syntax ([((label . name) ...)
                      (for/list ([(k v) (in-hash label->name)])
                        (cons k v))])
         (quasisyntax/loc stx
           (label-formlet-cross (lambda (labeled)
                                  (let ([name (hash-ref labeled 'label empty)]
                                        ...)
                                    e))
                                #,q-raw))))]))

(provide formlet* =>*)
