(module timer mzscheme
  (require "timer-structs.ss")
  (require (lib "list.ss")
           (lib "async-channel.ss"))
  (provide timer? 
           start-timer reset-timer increment-timer
           cancel-timer!
           start-timer-manager)

  (define timer-ch (make-async-channel))

  ; start-timer-manager : custodian -> void
  ; The timer manager thread   
  (define (start-timer-manager server-custodian)
    (parameterize ([current-custodian server-custodian])
      (thread
       (lambda ()
         (let loop ([timers null])
           ;; Wait for either...
           (apply sync
                  ;; ... a timer-request message ...
                  (handle-evt
                   timer-ch
                   (lambda (req)
                     ;; represent a req as a (timer-list -> timer-list) function:
                     ;; add/remove/change timer evet:
                     (loop (req timers))))
                  ;; ... or a timer
                  (map (lambda (timer)
                         (handle-evt
                          (timer-evt timer)
                          (lambda (_)
                            ;; execute timer
                            ((timer-action timer))
                            (loop (remq timer timers)))))
                       timers))))))
    (void))

  ;; Limitation on this add-timer: thunk cannot make timer
  ;;  requests directly, because it's executed directly by
  ;;  the timer-manager thread
  ;; add-timer : number (-> void) -> timer
  (define (add-timer msecs thunk)
    (let* ([now (current-inexact-milliseconds)]
           [timer
            (make-timer (alarm-evt (+ now msecs))
                        (+ now msecs)
                        thunk)])
      (async-channel-put timer-ch 
                   (lambda (timers)
                     (cons timer timers)))
      timer))

  ; revise-timer! : timer msecs (-> void) -> timer
  ; revise the timer to ring msecs from now
  (define (revise-timer! timer msecs thunk)
    (let ([now (current-inexact-milliseconds)])
      (async-channel-put 
       timer-ch 
       (lambda (timers)
         (set-timer-evt! timer (alarm-evt (+ now msecs)))
         (set-timer-expire-seconds! timer (+ now msecs))
         (set-timer-action! timer thunk)
         timers))))

  (define (cancel-timer! timer)
    (revise-timer! timer 0 void))  

  ; start-timer : num (-> void) -> timer
  ; to make a timer that calls to-do after sec from make-timer's application
  (define (start-timer secs to-do)
    (add-timer (* 1000 secs) to-do))

  ; reset-timer : timer num -> void
  ; to cause timer to expire after sec from the adjust-msec-to-live's application
  (define (reset-timer timer secs)
    (revise-timer! timer (* 1000 secs) (timer-action timer)))
  
  ; increment-timer : timer num -> void
  ; add secs to the timer, rather than replace
  (define (increment-timer timer secs)
    (revise-timer! timer
                   (+ (- (timer-expire-seconds timer) (current-inexact-milliseconds))
                      (* 1000 secs))
                   (timer-action timer))))

; --- timeout plan

; start timeout on connection startup
; for POST requests increase the timeout proportionally when content-length is read
; adjust timeout in read-to-eof
; adjust timeout to starting timeout for next request with persistent connections

; adjust timeout proportionally when responding
; for servlet - make it a day until the output is produced
