;; lib/ruby/tests/runtime.sx — Tests for lib/ruby/runtime.sx

(define rb-test-pass 0)
(define rb-test-fail 0)
(define rb-test-fails (list))

(define
  (rb-test name got expected)
  (if
    (= got expected)
    (set! rb-test-pass (+ rb-test-pass 1))
    (begin
      (set! rb-test-fail (+ rb-test-fail 1))
      (set! rb-test-fails (append rb-test-fails (list {:got got :expected expected :name name}))))))

;; ---------------------------------------------------------------------------
;; 1. Hash
;; ---------------------------------------------------------------------------

(define h1 (rb-hash-new))
(rb-test "hash? new" (rb-hash? h1) true)
(rb-test "hash? non-hash" (rb-hash? 42) false)
(rb-test "hash size empty" (rb-hash-size h1) 0)
(rb-hash-at-put! h1 "a" 1)
(rb-hash-at-put! h1 "b" 2)
(rb-hash-at-put! h1 "c" 3)
(rb-test "hash at a" (rb-hash-at h1 "a") 1)
(rb-test "hash at b" (rb-hash-at h1 "b") 2)
(rb-test "hash at missing" (rb-hash-at h1 "z") nil)
(rb-test "hash at-or default" (rb-hash-at-or h1 "z" 99) 99)
(rb-test "hash has-key yes" (rb-hash-has-key? h1 "a") true)
(rb-test "hash has-key no" (rb-hash-has-key? h1 "z") false)
(rb-test "hash size after inserts" (rb-hash-size h1) 3)
(rb-hash-at-put! h1 "a" 10)
(rb-test "hash at-put update" (rb-hash-at h1 "a") 10)
(rb-test "hash size unchanged after update" (rb-hash-size h1) 3)
(rb-hash-delete! h1 "b")
(rb-test "hash delete" (rb-hash-has-key? h1 "b") false)
(rb-test "hash size after delete" (rb-hash-size h1) 2)
(rb-test "hash keys" (rb-hash-keys h1) (list "a" "c"))
(rb-test "hash values" (rb-hash-values h1) (list 10 3))

(define
  h2
  (rb-list->hash (list (list "x" 7) (list "y" 8))))
(rb-test "list->hash x" (rb-hash-at h2 "x") 7)
(rb-test "list->hash y" (rb-hash-at h2 "y") 8)

(define h3 (rb-hash-merge h1 h2))
(rb-test "hash-merge a" (rb-hash-at h3 "a") 10)
(rb-test "hash-merge x" (rb-hash-at h3 "x") 7)
(rb-test "hash-merge size" (rb-hash-size h3) 4)

;; ---------------------------------------------------------------------------
;; 2. Set
;; ---------------------------------------------------------------------------

(define s1 (rb-set-new))
(rb-test "set? new" (rb-set? s1) true)
(rb-test "set? non-set" (rb-set? "hello") false)
(rb-test "set size empty" (rb-set-size s1) 0)
(rb-set-add! s1 1)
(rb-set-add! s1 2)
(rb-set-add! s1 3)
(rb-set-add! s1 2)
(rb-test "set include yes" (rb-set-include? s1 1) true)
(rb-test "set include no" (rb-set-include? s1 9) false)
(rb-test "set size dedup" (rb-set-size s1) 3)
(rb-set-delete! s1 2)
(rb-test "set delete" (rb-set-include? s1 2) false)
(rb-test "set size after delete" (rb-set-size s1) 2)

(define s2 (rb-set-new))
(rb-set-add! s2 2)
(rb-set-add! s2 3)
(rb-set-add! s2 4)

(define su (rb-set-union s1 s2))
(rb-test "set union includes 1" (rb-set-include? su 1) true)
(rb-test "set union includes 4" (rb-set-include? su 4) true)
(rb-test "set union size" (rb-set-size su) 4)

(define si (rb-set-intersection s1 s2))
(rb-test "set intersection includes 3" (rb-set-include? si 3) true)
(rb-test "set intersection excludes 1" (rb-set-include? si 1) false)
(rb-test "set intersection size" (rb-set-size si) 1)

(define sd (rb-set-difference s1 s2))
(rb-test "set difference includes 1" (rb-set-include? sd 1) true)
(rb-test "set difference excludes 3" (rb-set-include? sd 3) false)

;; ---------------------------------------------------------------------------
;; 3. Regexp
;; ---------------------------------------------------------------------------

(define rx1 (rb-regexp-new "hel+" ""))
(rb-test "regexp?" (rb-regexp? rx1) true)
(rb-test "regexp match? yes" (rb-regexp-match? rx1 "say hello") true)
(rb-test "regexp match? no" (rb-regexp-match? rx1 "goodbye") false)

(define m1 (rb-regexp-match rx1 "say hello world"))
(rb-test "regexp match :match" (get m1 "match") "hell")

(define rx2 (rb-regexp-new "[0-9]+" ""))
(define all (rb-regexp-match-all rx2 "a1b22c333"))
(rb-test "regexp match-all count" (len all) 3)
(rb-test "regexp match-all first" (get (first all) "match") "1")

(rb-test "regexp replace" (rb-regexp-replace rx2 "a1b2" "N") "aNb2")
(rb-test "regexp replace-all" (rb-regexp-replace-all rx2 "a1b2" "N") "aNbN")
(rb-test
  "regexp split"
  (rb-regexp-split (rb-regexp-new "," "") "a,b,c")
  (list "a" "b" "c"))

;; ---------------------------------------------------------------------------
;; 4. StringIO
;; ---------------------------------------------------------------------------

(define sio1 (rb-string-io-new))
(rb-test "string-io?" (rb-string-io? sio1) true)
(rb-string-io-write! sio1 "hello")
(rb-string-io-write! sio1 " world")
(rb-test "string-io string" (rb-string-io-string sio1) "hello world")
(rb-string-io-rewind! sio1)
(rb-test "string-io eof? no" (rb-string-io-eof? sio1) false)
(define ch1 (rb-string-io-read-char sio1))
(define ch2 (rb-string-io-read-char sio1))
;; Compare char codepoints since = uses reference equality for chars
(rb-test "string-io read-char h" (char->integer ch1) 104)
(rb-test "string-io read-char e" (char->integer ch2) 101)
(rb-test "string-io read rest" (rb-string-io-read sio1) "llo world")
(rb-test "string-io eof? yes" (rb-string-io-eof? sio1) true)
(rb-test "string-io read at eof" (rb-string-io-read sio1) "")

;; ---------------------------------------------------------------------------
;; 5. Bytevectors
;; ---------------------------------------------------------------------------

(define bv1 (rb-bytes-new 4 0))
(rb-test "bytes?" (rb-bytes? bv1) true)
(rb-test "bytes length" (rb-bytes-length bv1) 4)
(rb-test "bytes get zero" (rb-bytes-get bv1 0) 0)
(rb-bytes-set! bv1 0 65)
(rb-bytes-set! bv1 1 66)
(rb-test "bytes get A" (rb-bytes-get bv1 0) 65)
(rb-test "bytes get B" (rb-bytes-get bv1 1) 66)
(define bv2 (rb-bytes-from-string "hi"))
(rb-test "bytes from-string length" (rb-bytes-length bv2) 2)
(rb-test "bytes to-string" (rb-bytes-to-string bv2) "hi")
(define
  bv3
  (rb-bytes-append (rb-bytes-from-string "foo") (rb-bytes-from-string "bar")))
(rb-test "bytes append" (rb-bytes-to-string bv3) "foobar")
(rb-test
  "bytes->list"
  (rb-bytes->list (rb-bytes-from-string "AB"))
  (list 65 66))
(rb-test
  "list->bytes"
  (rb-bytes-to-string (rb-list->bytes (list 72 105)))
  "Hi")

;; ---------------------------------------------------------------------------
;; 6. Fiber
;;    Note: rb-fiber-yield from inside a letrec (JIT-compiled) doesn't
;;    properly escape via call/cc continuations. Use top-level helper fns
;;    or explicit sequential yields instead of letrec-bound recursion.
;; ---------------------------------------------------------------------------

(define
  fib1
  (rb-fiber-new
    (fn
      ()
      (rb-fiber-yield 10)
      (rb-fiber-yield 20)
      30)))

(rb-test "fiber?" (rb-fiber? fib1) true)
(rb-test "fiber alive? before" (rb-fiber-alive? fib1) true)
(define fr1 (rb-fiber-resume fib1))
(rb-test "fiber resume 1" fr1 10)
(rb-test "fiber alive? mid" (rb-fiber-alive? fib1) true)
(define fr2 (rb-fiber-resume fib1))
(rb-test "fiber resume 2" fr2 20)
(define fr3 (rb-fiber-resume fib1))
(rb-test "fiber resume 3 (completion)" fr3 30)
(rb-test "fiber alive? dead" (rb-fiber-alive? fib1) false)

;; Loop via a top-level helper (avoid letrec — see note above)
(define
  (rb-fiber-loop-helper i)
  (when
    (<= i 3)
    (rb-fiber-yield i)
    (rb-fiber-loop-helper (+ i 1))))

(define
  fib2
  (rb-fiber-new (fn () (rb-fiber-loop-helper 1) "done")))

(rb-test "fiber loop resume 1" (rb-fiber-resume fib2) 1)
(rb-test "fiber loop resume 2" (rb-fiber-resume fib2) 2)
(rb-test "fiber loop resume 3" (rb-fiber-resume fib2) 3)
(rb-test "fiber loop resume done" (rb-fiber-resume fib2) "done")
(rb-test "fiber loop dead" (rb-fiber-alive? fib2) false)
