(module web-cells mzscheme  
  (require (lib "struct.ss")
           (lib "contract.ss"))
  
  (define-struct (exn:fail:frame:top exn) ())
  (define (exn:fail:frame:top-raise)
    (raise (make-exn:fail:frame:top
	    "Reached top of stack"
	    (current-continuation-marks))))
  
  ;; frames
  (define-struct frame ())
  (define-struct (frame:empty frame) ())
  ; frame:ns : (alist * (box frame) * namespace)
  (define-struct (frame:ns frame) (annotations boxed-parent namespace))
  
  ; frame:ns?/raise : frame -> frame
  (define (frame:ns?/raise f)
    (if (frame:ns? f)
	f
	(exn:fail:frame:top-raise)))
  
  ; make-frame/parent : (box frame) -> frame:ns
  (define (make-frame/parent parent-frame-box)
    (make-frame:ns (list) parent-frame-box (make-namespace 'empty)))
  
  ; search-frames : frame:ns (frame:ns -> boolean?) -> frame
  ; Returns the first frame in the stack that matches the predicate
  (define (search-frames a-frame predicate?)
    (if (predicate? a-frame)
	a-frame
	(search-frames (frame:ns?/raise
			(unbox (frame:ns-boxed-parent a-frame)))
		       predicate?)))
  
  ; frame-ref : frame:ns symbol -> any
  ; Lookups up the variable in the frame and its parent(s)
  (define (frame-ref a-frame var)
    #;(printf "~S~n" (list (namespace-mapped-symbols (frame:ns-namespace a-frame)) var))
    (namespace-variable-value 
     var #f 
     (lambda ()
       (frame-ref (frame:ns?/raise
		   (unbox (frame:ns-boxed-parent a-frame)))
                  var))
     (frame:ns-namespace a-frame)))
  
  ; frame-set? : frame:ns symbol -> boolean
  (define (frame-set? a-frame var)
    (not
     (not 
      (namespace-variable-value
       var #f
       (lambda () #f)
       (frame:ns-namespace a-frame)))))
  
  ; frame-set! : frame:ns symbol any -> void
  ; Sets the variable in the frame to a value
  (define (frame-set! a-frame var val)
    (namespace-set-variable-value! 
     var val
     #t (frame:ns-namespace a-frame)))
  
  ;; frame stacks  
  (define *global-root-id* (gensym))
  
  ; *frame-stack* : (box frame)
  (define *frame-stack*
    (make-parameter
     (box (copy-struct frame:ns (make-frame/parent (box (make-frame:empty)))
                       [frame:ns-annotations (list (cons *global-root-id* #t))]))))
  
  ; annotation-present? : symbol frame:ns -> boolean
  (define (annotation-present? i a-frame)
    (not (not (assq i (frame:ns-annotations a-frame)))))
  
  ; global-root? : frame:ns -> boolean
  (define (global-root? a-frame)
    (annotation-present? *global-root-id* a-frame))
    
  ; make-frame/top : -> frame:ns
  (define (make-frame/top)
    (define cur-top-box (*frame-stack*))
    (define cur-top (unbox cur-top-box))
    (make-frame/parent cur-top-box))
  
  ; push-frame! : -> void
  ; Pushs a new frame onto the session stack
  (define (push-frame!)
    (*frame-stack* (box (make-frame/top))))
  
  ; pop-frame! : -> void
  ; Pops the frame from the stack
  (define (pop-frame!)
    (*frame-stack* (frame:ns-boxed-parent (unbox (*frame-stack*)))))
  
  ; save-stack/push/return : (-> 'a) -> 'a
  ; Pushes a frame after the thunk's execution with the same parent as the call site
  (define (save-stack/push/return thunk)
    (define initial-stack (*frame-stack*))
    (begin0 (thunk)
            (*frame-stack* initial-stack)
            (push-frame!)))
  
  ; syntax version of above
  (define-syntax with-frame-after
    (syntax-rules ()
      [(_ body ...)
       (save-stack/push/return (lambda () body ...))]))
  
  ; parameterized-push : (-> 'a) -> 'a
  (define (parameterized-push thunk)
    (parameterize ([*frame-stack* (box (make-frame/top))])
      (thunk)))
  
  ; syntax version of above
  (define-syntax with-frame
    (syntax-rules ()
      [(_ body ...)
       (parameterized-push (lambda () body ...))]))
  
  ; search-stack : (frame -> boolean) -> frame
  (define (search-stack predicate?)
    (search-frames (frame:ns?/raise (unbox (*frame-stack*)))
		   predicate?))
  
  ; cells
  (define-struct cell (id))
  (define-struct (cell:local cell) ())
  
  (define web-cell:local? cell:local?)
  
  ; ext:make-'a 'b -> 'a
  (define (make-web-cell:local default)
    (define new-name (gensym))
    (frame-set! (search-stack global-root?)
                new-name default)
    (make-cell:local new-name))
    
  ; cell:local-ref : cell:local -> any
  ; returns the value of the local cell
  (define (web-cell:local-ref lc)
    (frame-ref (search-stack frame?)
               (cell-id lc)))
  ; cell:local-set! : cell:local any -> void
  ; sets the value of the local cell at the last place it was set, including the default
  (define (web-cell:local-set! lc nv)
    (frame-set! (search-stack
                 (lambda (f) (frame-set? f (cell-id lc))))
                (cell-id lc)
                nv))
  ; cell:local-mask : cell:local any -> void
  ; masks the local cell to the given value
  (define (web-cell:local-mask lc nv)
    (frame-set! (search-stack frame?)
                (cell-id lc)
                nv))
    
  (provide with-frame ; syntax
           with-frame-after)
  (provide/contract
   [exn:fail:frame:top? (any/c . -> . boolean?)]
   [web-cell:local? (any/c . -> . boolean?)]
   [make-web-cell:local (any/c . -> . web-cell:local?)]
   [web-cell:local-ref (web-cell:local? . -> . any/c)]
   [web-cell:local-set! (web-cell:local? any/c . -> . void)]
   [web-cell:local-mask (web-cell:local? any/c . -> . void)]))