;; Ping-pong program — two processes exchange N messages, then signal
;; main via separate `ping_done` / `pong_done` notifications.

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

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

(define pp-ev erlang-eval-ast)

;; Three rounds of ping-pong, then stop. Main receives ping_done and
;; pong_done in arrival order (Ping finishes first because Pong exits
;; only after receiving stop).
(define
  er-pp-program
  "Me = self(),
   Pong = spawn(fun () ->
     Loop = fun () ->
       receive
         {ping, From} -> From ! pong, Loop();
         stop -> Me ! pong_done
       end
     end,
     Loop()
   end),
   Ping = fun (Target, K) ->
     if K =:= 0 -> Target ! stop, Me ! ping_done;
        true -> Target ! {ping, self()}, receive pong -> Ping(Target, K - 1) end
     end
   end,
   spawn(fun () -> Ping(Pong, 3) end),
   receive ping_done -> ok end,
   receive pong_done -> both_done end")

(er-pp-test
  "ping-pong 3 rounds"
  (get (pp-ev er-pp-program) :name)
  "both_done")

;; Count exchanges via io-buffer — each pong trip prints "p".
(er-pp-test
  "ping-pong 5 rounds trace"
  (do
    (er-io-flush!)
    (pp-ev
      "Me = self(),
       Pong = spawn(fun () ->
         Loop = fun () ->
           receive
             {ping, From} -> io:format(\"p\"), From ! pong, Loop();
             stop -> Me ! pong_done
           end
         end,
         Loop()
       end),
       Ping = fun (Target, K) ->
         if K =:= 0 -> Target ! stop, Me ! ping_done;
            true -> Target ! {ping, self()}, receive pong -> Ping(Target, K - 1) end
         end
       end,
       spawn(fun () -> Ping(Pong, 5) end),
       receive ping_done -> ok end,
       receive pong_done -> ok end")
    (er-io-buffer-content))
  "ppppp")

;; Main → Pong directly (no Ping process). Main plays the ping role.
(er-pp-test
  "main-as-pinger 4 rounds"
  (pp-ev
    "Me = self(),
     Pong = spawn(fun () ->
       Loop = fun () ->
         receive
           {ping, From} -> From ! pong, Loop();
           stop -> ok
         end
       end,
       Loop()
     end),
     Go = fun (K) ->
       if K =:= 0 -> Pong ! stop, K;
          true -> Pong ! {ping, Me}, receive pong -> Go(K - 1) end
       end
     end,
     Go(4)")
  0)

;; Ensure the processes really interleave — inject an id into each
;; ping and check we get them all back via trace (the order is
;; deterministic under our sync scheduler).
(er-pp-test
  "ids round-trip"
  (do
    (er-io-flush!)
    (pp-ev
      "Me = self(),
       Pong = spawn(fun () ->
         Loop = fun () ->
           receive
             {ping, From, Id} -> From ! {pong, Id}, Loop();
             stop -> ok
           end
         end,
         Loop()
       end),
       Go = fun (K) ->
         if K =:= 0 -> Pong ! stop, done;
            true -> Pong ! {ping, Me, K}, receive {pong, RId} -> io:format(\"~p \", [RId]), Go(K - 1) end
         end
       end,
       Go(4)")
    (er-io-buffer-content))
  "4 3 2 1 ")

(define
  er-pp-test-summary
  (str "ping-pong " er-pp-test-pass "/" er-pp-test-count))
