;; HashedCollection / Set / Dictionary / IdentityDictionary tests.
;; These are user classes implemented in `runtime.sx` with array-backed
;; storage. Set: single ivar `array`. Dictionary: parallel `keys`/`values`.

(set! st-test-pass 0)
(set! st-test-fail 0)
(set! st-test-fails (list))

(st-bootstrap-classes!)
(define ev (fn (src) (smalltalk-eval src)))
(define evp (fn (src) (smalltalk-eval-program src)))

;; ── 1. Class hierarchy ──
(st-test "Set < HashedCollection" (st-class-inherits-from? "Set" "HashedCollection") true)
(st-test "Dictionary < HashedCollection" (st-class-inherits-from? "Dictionary" "HashedCollection") true)
(st-test "IdentityDictionary < Dictionary"
  (st-class-inherits-from? "IdentityDictionary" "Dictionary") true)

;; ── 2. Set basics ──
(st-test "fresh Set is empty"
  (evp "^ Set new isEmpty") true)

(st-test "Set add: + size"
  (evp
    "| s |
     s := Set new.
     s add: 1. s add: 2. s add: 3.
     ^ s size")
  3)

(st-test "Set add: deduplicates"
  (evp
    "| s |
     s := Set new.
     s add: 1. s add: 1. s add: 1.
     ^ s size")
  1)

(st-test "Set includes: found"
  (evp
    "| s | s := Set new. s add: #a. s add: #b. ^ s includes: #a")
  true)

(st-test "Set includes: missing"
  (evp
    "| s | s := Set new. s add: #a. ^ s includes: #z")
  false)

(st-test "Set remove: drops the element"
  (evp
    "| s |
     s := Set new.
     s add: 1. s add: 2. s add: 3.
     s remove: 2.
     ^ s includes: 2")
  false)

(st-test "Set remove: keeps the others"
  (evp
    "| s |
     s := Set new.
     s add: 1. s add: 2. s add: 3.
     s remove: 2.
     ^ s size")
  2)

(st-test "Set do: iterates"
  (evp
    "| s sum |
     s := Set new.
     s add: 1. s add: 2. s add: 3.
     sum := 0.
     s do: [:e | sum := sum + e].
     ^ sum")
  6)

(st-test "Set addAll: with an Array"
  (evp
    "| s |
     s := Set new.
     s addAll: #(1 2 3 2 1).
     ^ s size")
  3)

;; ── 3. Dictionary basics ──
(st-test "fresh Dictionary is empty"
  (evp "^ Dictionary new isEmpty") true)

(st-test "Dictionary at:put: + at:"
  (evp
    "| d |
     d := Dictionary new.
     d at: #a put: 1.
     d at: #b put: 2.
     ^ d at: #a")
  1)

(st-test "Dictionary at: missing key returns nil"
  (evp "^ Dictionary new at: #nope") nil)

(st-test "Dictionary at:ifAbsent: invokes block"
  (evp "^ Dictionary new at: #nope ifAbsent: [#absent]")
  (make-symbol "absent"))

(st-test "Dictionary at:put: overwrite"
  (evp
    "| d |
     d := Dictionary new.
     d at: #x put: 1.
     d at: #x put: 99.
     ^ d at: #x")
  99)

(st-test "Dictionary size after several puts"
  (evp
    "| d |
     d := Dictionary new.
     d at: #a put: 1. d at: #b put: 2. d at: #c put: 3.
     ^ d size")
  3)

(st-test "Dictionary includesKey: found"
  (evp
    "| d | d := Dictionary new. d at: #a put: 1. ^ d includesKey: #a")
  true)

(st-test "Dictionary includesKey: missing"
  (evp
    "| d | d := Dictionary new. d at: #a put: 1. ^ d includesKey: #z")
  false)

(st-test "Dictionary removeKey:"
  (evp
    "| d |
     d := Dictionary new.
     d at: #a put: 1. d at: #b put: 2. d at: #c put: 3.
     d removeKey: #b.
     ^ d size")
  2)

(st-test "Dictionary removeKey: drops only that key"
  (evp
    "| d |
     d := Dictionary new.
     d at: #a put: 1. d at: #b put: 2. d at: #c put: 3.
     d removeKey: #b.
     ^ d at: #a")
  1)

;; ── 4. Dictionary iteration ──
(st-test "Dictionary do: yields values"
  (evp
    "| d sum |
     d := Dictionary new.
     d at: #a put: 1. d at: #b put: 2. d at: #c put: 3.
     sum := 0.
     d do: [:v | sum := sum + v].
     ^ sum")
  6)

(st-test "Dictionary keysDo: yields keys"
  (evp
    "| d log |
     d := Dictionary new.
     d at: #a put: 1. d at: #b put: 2.
     log := #().
     d keysDo: [:k | log := log , (Array with: k)].
     ^ log size")
  2)

(st-test "Dictionary keysAndValuesDo:"
  (evp
    "| d total |
     d := Dictionary new.
     d at: #a put: 10. d at: #b put: 20.
     total := 0.
     d keysAndValuesDo: [:k :v | total := total + v].
     ^ total")
  30)

;; Helper used by some tests above:
(st-class-add-class-method! "Array" "with:"
  (st-parse-method "with: x | a | a := Array new: 1. a at: 1 put: x. ^ a"))

(st-test "Dictionary keys returns Array"
  (sort
    (evp
      "| d | d := Dictionary new.
       d at: #x put: 1. d at: #y put: 2. d at: #z put: 3.
       ^ d keys"))
  (sort (list (make-symbol "x") (make-symbol "y") (make-symbol "z"))))

(st-test "Dictionary values returns Array"
  (sort
    (evp
      "| d | d := Dictionary new.
       d at: #x put: 100. d at: #y put: 200.
       ^ d values"))
  (sort (list 100 200)))

;; ── 5. Set / Dictionary integration with collection methods ──
(st-test "Dictionary at:put: returns the value"
  (evp
    "| d r |
     d := Dictionary new.
     r := d at: #a put: 42.
     ^ r")
  42)

(st-test "Set has its class"
  (evp "^ Set new class name") "Set")

(st-test "Dictionary has its class"
  (evp "^ Dictionary new class name") "Dictionary")

(list st-test-pass st-test-fail)
