#lang racket/base ;; !

(require racket/match)
(require racket/set)
(require racket/syntax)

(module+ test (require rackunit))

(require syndicate/actor)
(require syndicate/ground)
(require syndicate/trie)
(require syndicate/patch)
(require syndicate/protocol/standard-relay)

(define (fv term)
  (let walk ((term term))
    (match term
      [`(lambda (,var ...) ,body)
       (set-subtract (walk body) (list->seteq var))]
      [`(,rator ,rand ...)
       (apply set-union (seteq) (map walk (cons rator rand)))]
      [(? symbol? x)
       (seteq x)]
      [_
       (seteq)])))

(module+ test
  (check-equal? (fv '(lambda (x) x)) (seteq))
  (check-equal? (fv '(lambda (x) y)) (seteq 'y))
  (check-equal? (fv '((lambda (x) x) z)) (seteq 'z))
  (check-equal? (fv '(lambda (y) ((lambda (x) y) z))) (seteq 'z))
  (check-equal? (fv '(w z)) (seteq 'w 'z))
  (check-equal? (fv '123) (seteq))
  (check-equal? (fv '"hi") (seteq))
  (check-equal? (fv '((lambda (x) x) 123)) (seteq))
  (check-equal? (fv '((lambda (x) (y x)) 123)) (seteq 'y))
  )

(define (compile top-term)
  (define actors '())

  (define (emit-actor! a)
    (set! actors (cons a actors)))

  (define (gensym/intern base)
    (string->symbol (symbol->string (gensym base))))

  (define ($ v)
    (format-symbol "$~a" v))

  (define (compile-lambda-body n fvs vars body)
    `(spawn (during (observe (rpc (list (list ',n ,@(map $ fvs)) ,@(map $ vars)) _))
              (on-start (react ,(compile-term body
                                              (lambda (v)
                                                `(assert (rpc (list (list ',n ,@fvs) ,@vars)
                                                              ,v)))))))))

  (define (compile-term term k)
    (match term
      [`(lambda (,var ...) ,body)
       (define n (gensym/intern 'clo)) ;; could get away with plain gensym here?
       (define fvs (set->list (set-subtract (fv body) (list->seteq var)))) ;; arbitrary order
       (emit-actor! (compile-lambda-body n fvs var body))
       (k `(list ',n ,@fvs))]
      [`(,rator0 ,rand0 ...)
       (compile-term rator0
                     (lambda (rator)
                       (let ca ((rands rand0) (acc-rev '()))
                         (match rands
                           ['()
                            (define v (gensym/intern 'v))
                            `(stop-when (asserted (rpc (list ,rator ,@(reverse acc-rev)) ,($ v)))
                                        (react ,(k v)))]
                           [(cons r rest)
                            (compile-term r (lambda (rv) (ca rest (cons rv acc-rev))))]))))]
      [(? symbol? x)
       (k x)]
      [lit
       (k lit)]))

  (emit-actor! `(spawn ,(compile-term top-term (lambda (v) `(assert (outbound (answer ,v)))))))

  (reverse actors))

(define (primitive-handlers)
  (list `(spawn (during (observe (rpc (list + $a $b) _))
                  (assert (rpc (list + a b) (+ a b)))))))

(define-namespace-anchor ns-anchor)
(define ns (namespace-anchor->namespace ns-anchor))

(define (eval-compiled forms)
  (local-require racket/pretty)
  (struct answer (v) #:prefab)
  (define program `(let ()
                     (struct rpc (q a) #:prefab)
                     (struct answer (v) #:prefab)
                     (values ;;time
                      (run-ground
                       ,@(primitive-handlers)
                       ,@forms))))
  ;; (pretty-print program)
  (trie-project/set/single (eval program ns) (answer (?!))))

(module+ test
  (check-equal? (eval-compiled (compile `((lambda (x) x) 123))) (set 123))
  (check-equal? (eval-compiled (compile `(+ 123 234))) (set 357))
  (check-equal? (eval-compiled (compile `((lambda (x y) (+ x y)) 123 234))) (set 357))
  (check-equal? (eval-compiled (compile `((lambda (x y) (+ x y)) ((lambda (v) (+ v 1)) 122) 234))) (set 357))
  (check-equal? (eval-compiled (compile `((lambda (inc)
                                            (+ (inc 2) (inc 3)))
                                          (lambda (v) (+ v 1)))))
                (set 7))
  )
