;; Ring program — N processes in a ring, token passes M times.
;;
;; Each process waits for {setup, Next} so main can tie the knot
;; (can't reference a pid before spawning it). Once wired, main
;; injects the first token; each process forwards decrementing K
;; until it hits 0, at which point it signals `done` to main.

(define er-ring-test-count 0)
(define er-ring-test-pass 0)
(define er-ring-test-fails (list))

(define
  er-ring-test
  (fn
    (name actual expected)
    (set! er-ring-test-count (+ er-ring-test-count 1))
    (if
      (= actual expected)
      (set! er-ring-test-pass (+ er-ring-test-pass 1))
      (append! er-ring-test-fails {:actual actual :expected expected :name name}))))

(define ring-ev erlang-eval-ast)

(define
  er-ring-program-3-6
  "Me = self(),
   Spawner = fun () ->
     receive {setup, Next} ->
       Loop = fun () ->
         receive
           {token, 0, Parent} -> Parent ! done;
           {token, K, Parent} -> Next ! {token, K-1, Parent}, Loop()
         end
       end,
       Loop()
     end
   end,
   P1 = spawn(Spawner),
   P2 = spawn(Spawner),
   P3 = spawn(Spawner),
   P1 ! {setup, P2},
   P2 ! {setup, P3},
   P3 ! {setup, P1},
   P1 ! {token, 5, Me},
   receive done -> finished end")

(er-ring-test
  "ring N=3 M=6"
  (get (ring-ev er-ring-program-3-6) :name)
  "finished")

;; Two-node ring — token bounces twice between P1 and P2.
(er-ring-test
  "ring N=2 M=4"
  (get (ring-ev
    "Me = self(),
     Spawner = fun () ->
       receive {setup, Next} ->
         Loop = fun () ->
           receive
             {token, 0, Parent} -> Parent ! done;
             {token, K, Parent} -> Next ! {token, K-1, Parent}, Loop()
           end
         end,
         Loop()
       end
     end,
     P1 = spawn(Spawner),
     P2 = spawn(Spawner),
     P1 ! {setup, P2},
     P2 ! {setup, P1},
     P1 ! {token, 3, Me},
     receive done -> done end") :name)
  "done")

;; Single-node "ring" — P sends to itself M times.
(er-ring-test
  "ring N=1 M=5"
  (get (ring-ev
    "Me = self(),
     Spawner = fun () ->
       receive {setup, Next} ->
         Loop = fun () ->
           receive
             {token, 0, Parent} -> Parent ! finished_loop;
             {token, K, Parent} -> Next ! {token, K-1, Parent}, Loop()
           end
         end,
         Loop()
       end
     end,
     P = spawn(Spawner),
     P ! {setup, P},
     P ! {token, 4, Me},
     receive finished_loop -> ok end") :name)
  "ok")

;; Confirm the token really went around — count hops via io-buffer.
(er-ring-test
  "ring N=3 M=9 hop count"
  (do
    (er-io-flush!)
    (ring-ev
      "Me = self(),
       Spawner = fun () ->
         receive {setup, Next} ->
           Loop = fun () ->
             receive
               {token, 0, Parent} -> Parent ! done;
               {token, K, Parent} ->
                 io:format(\"~p \", [K]),
                 Next ! {token, K-1, Parent},
                 Loop()
             end
           end,
           Loop()
         end
       end,
       P1 = spawn(Spawner),
       P2 = spawn(Spawner),
       P3 = spawn(Spawner),
       P1 ! {setup, P2},
       P2 ! {setup, P3},
       P3 ! {setup, P1},
       P1 ! {token, 8, Me},
       receive done -> done end")
    (er-io-buffer-content))
  "8 7 6 5 4 3 2 1 ")

(define
  er-ring-test-summary
  (str "ring " er-ring-test-pass "/" er-ring-test-count))
