#lang racket
(require syntax/free-vars
         (for-template
          racket/base
          racket/serialize)
         "util.rkt")

(define (define-closure! label fvars stx)
  ; Boxes
  (define make-CLOSURE-box
    (syntax-local-lift-expression
     (quasisyntax/loc stx
       (box (lambda (env) (error 'make-CLOSURE "Closure<~.s> not initialized" '#,label))))))
  (define CLOSURE-set-env!-box
    (syntax-local-lift-expression
     (quasisyntax/loc stx
       (box (lambda (clsr new-env) (error 'CLOSURE-set-env! "Closure<~.s> not initialized" '#,label))))))
  (define CLOSURE-env-box
    (syntax-local-lift-expression
     (quasisyntax/loc stx
       (box (lambda (clsr) (error 'CLOSURE-env "Closure<~.s> not initialized" '#,label))))))
  ; Define the deserializer (req closure struct values under lambdas)
  (define CLOSURE:deserialize-info-id
    (syntax-local-lift-expression
     (quasisyntax/loc stx
       (make-deserialize-info
        
        ;; make-proc: value ... -> CLOSURE
        (lambda args
          (apply (#%plain-lambda #,fvars
                                 ((unbox #,make-CLOSURE-box) (#%plain-lambda () (values #,@fvars))))
                 args))
        
        ;; cycle-make-proc: -> (values CLOSURE (CLOSURE -> void))
        (lambda ()
          (let ([new-closure
                 ((unbox #,make-CLOSURE-box)
                  (#%plain-lambda () (error 'deserialize "closure not initialized")))])
            (values
             new-closure
             (#%plain-lambda (clsr)
                             ((unbox #,CLOSURE-set-env!-box) new-closure ((unbox #,CLOSURE-env-box) clsr))))))))))
  ; Define the serializer (req closure struct values + deserializer identifier)
  (define CLOSURE:serialize-info-id
    (syntax-local-lift-expression
     (quasisyntax/loc stx
       (make-serialize-info
        
        ;; to-vector: CLOSURE -> vector
        (#%plain-lambda (clsr)
                        (#%plain-app call-with-values
                                     (#%plain-lambda () (((unbox #,CLOSURE-env-box) clsr)))
                                     vector))
        
        ;; The serializer id: --------------------
        (quote-syntax #,CLOSURE:deserialize-info-id)
        
        ;; can-cycle?
        #t
        
        ;; Directory for last-ditch resolution --------------------
        (or (current-load-relative-directory) (current-directory))))))
  ; Define the closure struct (req serialize info value)
  (define-values
    (make-CLOSURE-id CLOSURE?-id CLOSURE-env-id CLOSURE-set-env!-id)
    (apply
     values
     (syntax-local-lift-values-expression
      4
      (quasisyntax/loc stx
        (letrec-values ([(struct:CLOSURE make-CLOSURE CLOSURE? CLOSURE-ref CLOSURE-set!)
                         (make-struct-type 
                          '#,label ;; the tag goes here
                          #f  ; no super type
                          1
                          0   ; number of auto-fields
                          #f  ; auto-v
                          
                          ; prop-vals:
                          (list (cons prop:serializable #,CLOSURE:serialize-info-id)
                                (cons prop:procedure
                                      (make-keyword-procedure
                                       (lambda (kws kw-vals clsr . rst)
                                         (let-values ([#,fvars ((CLOSURE-ref clsr 0))])
                                          (keyword-apply #,stx
                                                         kws kw-vals
                                                         rst))))))
                          
                          #f  ; inspector
                          
                          ;; the struct apply proc:
                          #f)]
                        [(CLOSURE-env)
                         (#%plain-lambda (clsr) (CLOSURE-ref clsr 0))]
                        [(CLOSURE-set-env!)
                         (#%plain-lambda (clsr new-env) (CLOSURE-set! clsr 0 new-env))])
          (set-box! #,CLOSURE-env-box CLOSURE-env)
          (set-box! #,CLOSURE-set-env!-box CLOSURE-set-env!)
          (set-box! #,make-CLOSURE-box make-CLOSURE)
          (values make-CLOSURE CLOSURE? CLOSURE-env CLOSURE-set-env!))))))
  ; Provide the deserializer (req deserializer identifier)
  (syntax-local-lift-provide
   (quasisyntax/loc stx
     #,CLOSURE:deserialize-info-id))
  (values make-CLOSURE-id CLOSURE?-id CLOSURE-env-id))

(define (make-closure stx)
  (syntax-case stx  ()
    [(_ label lambda-stx)
     (let*-values 
         ([(lambda-fe-stx) (local-expand #'lambda-stx 'expression empty)]
          [(fvars) (free-vars (disarm lambda-fe-stx))]
          ; Define the closure struct (req serialize info value)
          [(make-CLOSURE-id CLOSURE?-id CLOSURE-env-id)
           (define-closure! #'label fvars lambda-fe-stx)])
       ; Instantiate the closure
       (quasisyntax/loc stx
         (#,make-CLOSURE-id (#%plain-lambda () (values #,@fvars)))))]))

(provide
 make-closure
 define-closure!)
