#lang imperative-syndicate
;; Re-assert an assertion when one of a set of triggering events is seen, after a delay.
;; Building block for building reconnection strategies.

(provide reassert-on
         (struct-out fixed-retry))

(require/activate imperative-syndicate/drivers/timer)

(struct fixed-retry (delay-ms) #:transparent
  #:property prop:procedure
  (lambda (f) (values (fixed-retry-delay-ms f) f)))

(define-logger syndicate/reassert)

(define-syntax reassert-on
  (syntax-rules ()
    [(_ assertion #:strategy strategy reset-event ...)
     (reassert-on* assertion
                   #:strategy strategy
                   (list (lambda (k) (stop-when reset-event (k))) ...))]
    [(_ assertion reset-event ...)
     (reassert-on assertion #:strategy (fixed-retry 1000) reset-event ...)]))

(begin-for-declarations
  (define (reassert-on* assertion #:strategy strategy event-fns)
    (on-start (let reassert ((strategy strategy))
                (react (log-syndicate/reassert-debug "~v: Asserting" assertion)
                       (assert assertion)
                       (define (reset)
                         (log-syndicate/reassert-debug "~v: Resetting with ~v" assertion strategy)
                         (define-values (delay-ms next-strategy) (strategy))
                         (sleep (/ delay-ms 1000.0))
                         (reassert next-strategy))
                       (for-each (lambda (f) (f reset)) event-fns))))))
