;;; SPDX-License-Identifier: LGPL-3.0-or-later
;;; SPDX-FileCopyrightText: Copyright © 2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>

#lang racket/base

(provide this-turn
         this-facet
         this-actor

         action

         entity
         actor-system

         with-fresh-turn
         ref
         react
         define-field
         stop-facet
         stop-current-facet
         on-start
         on-stop
         sync!
         send!
         spawn

         begin/dataflow
         define/dataflow
         stop-when-true

         this-target
         at
         assert
         stop-when
         (rename-out [event:when when])
         during
         during*)

(require racket/match)
(require racket/stxparam)
(require (for-syntax racket/base))
(require (for-syntax racket/syntax))

(require "actor.rkt")
(require (prefix-in actor: "actor.rkt"))

(require "event-expander.rkt")
(require "pattern.rkt")

(define-syntax-parameter this-turn
  (lambda (stx)
    (raise-syntax-error #f "Illegal use outside an Actor turn" stx)))

(define-syntax-rule (with-this-turn id expr ...)
  (syntax-parameterize ([this-turn (make-rename-transformer #'id)])
    expr ...))

(define-syntax this-facet
  (syntax-id-rules ()
    [_ (turn-active-facet this-turn)]))

(define-syntax this-actor
  (syntax-id-rules ()
    [_ (facet-actor this-facet)]))

(define-syntax-rule (action formals expr ...)
  (lambda (turn . formals)
    (with-this-turn turn expr ...)))

(define-syntax actor-system
  (syntax-rules ()
    [(_ #:name name expr ...)
     (actor:actor-system #:name name (action () expr ...))]
    [(_ expr ...)
     (actor:actor-system (action () expr ...))]))

(define-syntax-rule (with-fresh-turn expr ...)
  (turn-freshen this-turn (action () expr ...)))

(define-syntax-rule (ref e)
  (turn-ref this-turn e))

(define-syntax-rule (react setup-expr ...)
  (turn-facet! this-turn (action () setup-expr ...)))

(define-syntax-rule (define-field id initial-value)
  (define id (turn-field! this-turn 'id initial-value)))

(define-syntax stop-facet
  (syntax-rules ()
    [(_ f) (turn-stop! this-turn f)]
    [(_ f expr ...) (turn-stop! this-turn f (action () expr ...))]))

(define-syntax-rule (stop-current-facet expr ...)
  (stop-facet this-facet expr ...))

(define-syntax-rule (on-start expr ...)
  ;; TODO: delay to end of turn (?)
  (begin expr ...))

(define-syntax-rule (on-stop expr ...)
  (facet-on-stop! this-facet (action () expr ...)))

(define-syntax-rule (sync! peer expr ...)
  (turn-sync! this-turn peer (action (_reply) expr ...)))

(define-syntax-rule (send! peer assertion)
  (turn-message! this-turn peer (:template assertion)))

(define-syntax spawn
  (syntax-rules ()
    [(_ #:name name setup-expr ...)
     (turn-spawn! #:name name this-turn (action () setup-expr ...))]
    [(_ setup-expr ...)
     (turn-spawn! this-turn (action () setup-expr ...))]))

(define-syntax-rule (begin/dataflow expr ...)
  (turn-dataflow! this-turn (action () expr ...)))

(define-syntax-rule (define/dataflow id expr)
  (begin (define-field id #f)
         (begin/dataflow (id expr))))

(define-syntax-rule (stop-when-true test expr ...)
  (begin/dataflow
    (when test
      (stop-current-facet expr ...))))

;;---------------------------------------------------------------------------

(define-for-syntax orig-insp
  (variable-reference->module-declaration-inspector (#%variable-reference)))

(define-syntax-parameter this-target
  (lambda (stx)
    (raise-syntax-error #f "Illegal use outside an Actor turn" stx)))

(define-syntax (at stx)
  (syntax-case stx ()
    [(_ target-expr items ...)
     #`(let ((target target-expr))
         (syntax-parameterize ([this-target (make-rename-transformer #'target)])
           #,@(for/list [(item-stx (in-list (syntax->list #'(items ...))))]
                (let loop ((item-stx item-stx))
                  (define disarmed-item-stx (syntax-disarm item-stx orig-insp))
                  (syntax-case disarmed-item-stx ()
                    [(expander args ...)
                     (event-expander-id? #'expander)
                     (event-expander-transform disarmed-item-stx
                                               (lambda (r) (loop (syntax-rearm r item-stx))))]
                    [_
                     item-stx])))))]))

(define-event-expander assert
  (syntax-rules ()
    [(_ expr)
     (turn-assert/dataflow! this-turn this-target (action () (:template expr)))]))

(define-event-expander stop-when
  (syntax-rules ()
    [(_ event expr ...)
     (event:when event (stop-current-facet expr ...))]))

(require "schemas/gen/dataspace.rkt")

(define-event-expander event:when
  (lambda (stx)
    (syntax-case stx (message asserted retracted)
      [(_ (message pat) expr ...)
       #`(assert (Observe (:pattern pat)
                          (ref (entity #:message
                                       (action (bindings)
                                         (match-define (list #,@(analyse-pattern-bindings #'pat)) bindings)
                                         expr ...)))))]
      [(_ (asserted pat) expr ...)
       #`(assert (Observe (:pattern pat)
                          (ref (entity #:assert
                                       (action (bindings _handle)
                                         (match-define (list #,@(analyse-pattern-bindings #'pat)) bindings)
                                         expr ...)))))]
      [(_ (retracted pat) expr ...)
       #`(assert (Observe (:pattern pat)
                          (let ((assertion-map (make-hash)))
                            (ref (entity #:assert
                                         (action (bindings handle)
                                           (hash-set! assertion-map handle bindings))
                                         #:retract
                                         (action (handle)
                                           (match-define (list #,@(analyse-pattern-bindings #'pat))
                                             (hash-ref assertion-map handle))
                                           (hash-remove! assertion-map handle)
                                           expr ...))))))]))
  (syntax-rules ()
    [(_ test expr ...)
     (when test expr ...)]))

(define-event-expander during
  (lambda (stx)
    (syntax-case stx ()
      [(_ pat expr ...)
       #`(assert (Observe (:pattern pat)
                          (ref (during* (action (bindings)
                                          (match-define (list #,@(analyse-pattern-bindings #'pat)) bindings)
                                          expr ...)))))])))

(define (during* f)
  (define assertion-map (make-hash))
  (entity #:assert
          (action (value handle)
            (let ((facet (react (facet-prevent-inert-check! this-facet)
                                (f this-turn value))))
              (match (hash-ref assertion-map handle #f)
                [#f
                 (hash-set! assertion-map handle facet)]
                ['dead
                 (hash-remove! assertion-map handle)
                 (stop-facet facet)]
                [_
                 (error 'during "Duplicate assertion handle ~a" handle)])))
          #:retract
          (action (handle)
            (match (hash-ref assertion-map handle #f)
              [#f
               (hash-set! assertion-map handle 'dead)]
              ['dead
               (error 'during "Duplicate retraction handle ~a" handle)]
              [facet
               (hash-remove! assertion-map handle)
               (stop-facet facet)]))))

;;---------------------------------------------------------------------------
;;; Local Variables:
;;; eval: (put 'action 'racket-indent-function 1)
;;; eval: (put 'at 'racket-indent-function 1)
;;; eval: (put 'stop-when 'racket-indent-function 1)
;;; eval: (put 'stop-when-true 'racket-indent-function 1)
;;; eval: (put 'actor-system/dataspace 'racket-indent-function 1)
;;; End:
