#lang racket
(require racket/serialize
         web-server/private/servlet
         web-server/managers/manager
         web-server/private/define-closure
         web-server/lang/web-cells)

;; **********************************************************************
;; **********************************************************************
;; AUXILLIARIES  
(define-struct mark-key ())
(define the-cont-key (make-mark-key))
(define the-save-cm-key (make-mark-key))
(define safe-call? (make-mark-key))
(define web-prompt (make-continuation-prompt-tag 'web)) 

(define empty-hash 
  (make-immutable-hash empty))
(define (with-current-saved-continuation-marks-and key val thnk)
  (call-with-immediate-continuation-mark
   the-save-cm-key
   (lambda (old-cms)
     (with-continuation-mark the-save-cm-key
       (hash-set old-cms key val)
       (thnk)))
   empty-hash))

;; current-continuation-as-list: -> (listof value)
;; check the safety marks and return the list of marks representing the continuation
(define (activation-record-list)
  (let* ([cm (current-continuation-marks web-prompt)]
         ; XXX call this once with a non-#f default
         [sl (continuation-mark-set->list* cm (list safe-call? continuation-of-unsafe-part-mark))])
    (if (calling-context-okay? sl #f)
        (store-unsafe-parts-on-server! (continuation-mark-set->list* cm (list the-cont-key the-save-cm-key continuation-of-unsafe-part-mark)))
        (error "Attempt to capture a continuation from within an unsafe context:" sl))))

;; calling-context-okay? : (listof (vector safe-call? unsafe-continuation-mark)) -> boolean
(define (calling-context-okay? ctxt native-above?)
  (match ctxt
    [(list) #t]
    [(list-rest (vector (or (list-rest safe-call? _)
                            safe-call?)
                        unsafe-part)
                more-ctxt)
     (and (or native-above? safe-call?)
          (calling-context-okay? 
           more-ctxt
           (or unsafe-part native-above?)))]))

;; abort: ( -> alpha) -> alpha
;; erase the stack and apply a thunk
(define (abort thunk)  
  (abort-current-continuation web-prompt thunk))

;; with-continuation-marks : (listof (cons any1 any2)) (-> any3) -> any3
(define (with-continuation-marks cms thnk)
  (match cms
    [(list) (thnk)]
    [(list-rest (cons cm-key cm-val) cms)
     (with-continuation-mark cm-key cm-val
       (with-continuation-marks cms thnk))]))

(define (with-continuation-marks/hash cms thnk)
  (with-continuation-marks 
   (hash-map cms cons)
   thnk))

;; resume*: (listof (value -> value)) value -> value
;; resume a computation given a value and list of frame procedures
(define (resume* frames val)
  #;(printf "~S~n" `(resume ,frames ,val))
  (match frames
    [(list)
     #;(printf "Returning value ~S~n" val)
     (apply values val)]
    [(list-rest frame fs)
     #;(printf "Frame ~S~n" frame)
     (match frame
       [(vector #f #f #f)
        ; XXX Perhaps I should err?
        #;(error 'resume "Empty frame")
        (resume* fs val)]
       [(vector f #f #f)
        (call-with-values (lambda () (with-continuation-mark the-cont-key f (resume* fs val)))
                          f)]
       [(vector #f cms #f)
        (with-continuation-mark the-save-cm-key cms
          (with-continuation-marks/hash cms (lambda () (resume* fs val))))]
       [(vector #f #f nkpt-label)
        (serial->native
         ((get-unsafe-part-from-server nkpt-label)
          (with-continuation-mark continuation-of-unsafe-part-mark nkpt-label
            (resume* fs val))))]
       [(vector f cms nkpt-label)
        (resume* (list* (vector f #f #f)
                       (vector #f cms #f)
                       (if nkpt-label
                           (list* (vector #f #f nkpt-label)
                                  fs)
                           fs))
                val)])]))

(define (resume frames val)
  (resume* (reverse frames) val))

;; rebuild-cms : frames (-> value) -> value
(define (rebuild-cms frames thunk)
  #;(printf "~S~n" `(rebuild-cms ,frames ,thunk))
  (match frames
    [(list) 
     (thunk)]
    [(list-rest frame fs)
     (match (vector-ref frame 1)
       [#f
        (rebuild-cms fs thunk)]
       [cms
        (with-continuation-marks/hash cms (lambda () (rebuild-cms fs thunk)))])]))

(define (call-with-web-prompt thunk)
  (call-with-continuation-prompt
   thunk
   web-prompt))

;; a serializable undefined value
(define-serializable-struct undef ())
(define the-undef (make-undef))    

;; **********************************************************************
;; **********************************************************************
;; "SERVLET" INTERFACE  

(define-closure kont x (wcs current-marks)
  (abort (lambda ()
           ; Restoring the web-cells is separate from the continuation
           (restore-web-cell-set! wcs)
           (resume current-marks x))))

(define (kont-append-fun k f)
  (define-values (wcs current-marks) ((kont-env k)))
  (make-kont 
   (lambda ()
     (values wcs (list* (vector f #f #f) current-marks)))))

;; send/suspend: (continuation -> response) -> request
;; produce the current response and wait for the next request
(define (call-with-serializable-current-continuation response-maker)
  (with-continuation-mark safe-call? '(#t send/suspend)
    (let* ([current-marks (activation-record-list)]
           [wcs (capture-web-cell-set)]
           [k (make-kont (lambda () (values wcs current-marks)))])
      (abort (lambda ()
               ; Since we escaped from the previous context, we need to re-install the user's continuation-marks
               (rebuild-cms (reverse current-marks) (lambda () (response-maker k))))))))

;; combining native and transformed continuations
(define unsafe-barrier-prompt-tag (make-continuation-prompt-tag 'unsafe))
(define continuation-of-unsafe-part-mark (make-mark-key))

(define (store-unsafe-part-on-server! k)
  ((manager-continuation-store! (current-servlet-manager))
   (current-servlet-instance-id) k #f))
(define (get-unsafe-part-from-server k-label)
  (apply (manager-continuation-lookup (current-servlet-manager))
         (current-servlet-instance-id) k-label))

(define store-unsafe-parts-on-server!
  (match-lambda
    [(list) empty]
    [(list-rest (vector f cms unsafe-part) ctxt)
     (list* (vector f cms
                    (if unsafe-part
                        (store-unsafe-part-on-server! unsafe-part)
                        #f))
            (store-unsafe-parts-on-server! ctxt))]))

(define-syntax-rule (serial->native f)
  (serial->native* (lambda () f)))
(define-syntax-rule (native->serial f)
  (native->serial* (lambda () f)))

(define (serial->native* thnk)
  (call-with-continuation-prompt thnk unsafe-barrier-prompt-tag))
(define (native->serial* thnk)
  (call-with-composable-continuation 
   (lambda (unsafe-continuation-portion)
     (with-continuation-mark
         continuation-of-unsafe-part-mark unsafe-continuation-portion
       (thnk)))
   unsafe-barrier-prompt-tag))

;; **********************************************************************
;; **********************************************************************
;; "CLIENT" INTERFACE

;; dispatch-start: (request -> response) request -> reponse
;; pass the initial request to the starting interaction point
(define (dispatch-start start req0)
  (call-with-web-prompt 
   (lambda ()
     (with-continuation-mark safe-call? '(#t start)
       (start
        (with-continuation-mark the-cont-key start
          req0))))))

;; dispatch: (request -> (request -> response)) request -> response
;; lookup the continuation for this request and invoke it
(define (dispatch decode-continuation req)   
  (call-with-web-prompt
   (lambda ()
     (cond
       [(decode-continuation req)
        => (lambda (k) (k req))]
       [else
        (error 'dispatch "no continuation associated with the provided request: ~S" req)]))))

;; **********************************************************************
;; **********************************************************************

; XXX These should really be from web-server/private, but it interferes with testing
(define request? any/c)
(define response? any/c)

(define cms? (and/c hash? immutable?))

(define saved-context?
  (listof (vector/c (or/c false/c procedure?)
                    (or/c false/c cms?)
                    (or/c false/c symbol?))))

(provide/contract
 ;; AUXILLIARIES
 [abort ((-> any) . -> . any)]
 [call-with-web-prompt ((-> any) . -> . any)]
 [resume (saved-context? any/c . -> . any)]
 [the-cont-key mark-key?]
 [the-save-cm-key mark-key?]
 [safe-call? mark-key?]
 [the-undef undef?]
 [activation-record-list (-> saved-context?)]
 [with-current-saved-continuation-marks-and (any/c any/c (-> any/c) . -> . any/c)]
 [kont-append-fun (kont? procedure? . -> . kont?)]
 
 ;; "CLIENT" INTERFACE
 [dispatch ((request? . -> . (request? . -> . response?))
            request?
            . -> .
            response?)]
 [dispatch-start ((request? . -> . response?)
                  request?
                  . -> .
                  response?)])
(provide
 ;; "SERVLET" INTERFACE
 ; A contract would interfere with the safe-call? key
 native->serial
 serial->native
 call-with-serializable-current-continuation)
