;; Bank account server — stateful process, balance threaded through
;; recursive loop. Handles {deposit, Amt, From}, {withdraw, Amt, From},
;; {balance, From}, stop. Tests stateful process patterns.

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

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

(define bank-ev erlang-eval-ast)

;; Server fun shared by all tests — threaded via the program string.
(define
  er-bank-server-src
  "Server = fun (Balance) ->
     receive
       {deposit, Amt, From} -> From ! ok, Server(Balance + Amt);
       {withdraw, Amt, From} ->
         if Amt > Balance -> From ! insufficient, Server(Balance);
            true -> From ! ok, Server(Balance - Amt)
         end;
       {balance, From} -> From ! Balance, Server(Balance);
       stop -> ok
     end
   end")

;; Open account, deposit, check balance.
(er-bank-test
  "deposit 100 -> balance 100"
  (bank-ev
    (str
      er-bank-server-src
      ", Me = self(),
       Bank = spawn(fun () -> Server(0) end),
       Bank ! {deposit, 100, Me},
       receive ok -> ok end,
       Bank ! {balance, Me},
       receive B -> Bank ! stop, B end"))
  100)

;; Multiple deposits accumulate.
(er-bank-test
  "deposits accumulate"
  (bank-ev
    (str
      er-bank-server-src
      ", Me = self(),
       Bank = spawn(fun () -> Server(0) end),
       Bank ! {deposit, 50, Me}, receive ok -> ok end,
       Bank ! {deposit, 25, Me}, receive ok -> ok end,
       Bank ! {deposit, 10, Me}, receive ok -> ok end,
       Bank ! {balance, Me},
       receive B -> Bank ! stop, B end"))
  85)

;; Withdraw within balance succeeds; insufficient gets rejected.
(er-bank-test
  "withdraw within balance"
  (bank-ev
    (str
      er-bank-server-src
      ", Me = self(),
       Bank = spawn(fun () -> Server(100) end),
       Bank ! {withdraw, 30, Me}, receive ok -> ok end,
       Bank ! {balance, Me},
       receive B -> Bank ! stop, B end"))
  70)

(er-bank-test
  "withdraw insufficient"
  (get
    (bank-ev
      (str
        er-bank-server-src
        ", Me = self(),
         Bank = spawn(fun () -> Server(20) end),
         Bank ! {withdraw, 100, Me},
         receive R -> Bank ! stop, R end"))
    :name)
  "insufficient")

;; State preserved across an insufficient withdrawal.
(er-bank-test
  "state preserved on rejection"
  (bank-ev
    (str
      er-bank-server-src
      ", Me = self(),
       Bank = spawn(fun () -> Server(50) end),
       Bank ! {withdraw, 1000, Me}, receive _ -> ok end,
       Bank ! {balance, Me},
       receive B -> Bank ! stop, B end"))
  50)

;; Mixed deposits and withdrawals.
(er-bank-test
  "mixed transactions"
  (bank-ev
    (str
      er-bank-server-src
      ", Me = self(),
       Bank = spawn(fun () -> Server(100) end),
       Bank ! {deposit, 50, Me}, receive ok -> ok end,
       Bank ! {withdraw, 30, Me}, receive ok -> ok end,
       Bank ! {deposit, 10, Me}, receive ok -> ok end,
       Bank ! {withdraw, 5, Me}, receive ok -> ok end,
       Bank ! {balance, Me},
       receive B -> Bank ! stop, B end"))
  125)

;; Server.stop terminates the bank cleanly — main can verify by
;; sending stop and then exiting normally.
(er-bank-test
  "server stops cleanly"
  (get
    (bank-ev
      (str
        er-bank-server-src
        ", Me = self(),
         Bank = spawn(fun () -> Server(0) end),
         Bank ! stop,
         done"))
    :name)
  "done")

;; Two clients sharing one bank — interleaved transactions.
(er-bank-test
  "two clients share bank"
  (bank-ev
    (str
      er-bank-server-src
      ", Me = self(),
       Bank = spawn(fun () -> Server(0) end),
       Client = fun (Amt) ->
         spawn(fun () ->
           Bank ! {deposit, Amt, self()},
           receive ok -> Me ! deposited end
         end)
       end,
       Client(40),
       Client(60),
       receive deposited -> ok end,
       receive deposited -> ok end,
       Bank ! {balance, Me},
       receive B -> Bank ! stop, B end"))
  100)

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