;; Tests for mutable hash tables (Phase 10)

(defsuite
  "hash-table"
  (deftest
    "make-hash-table returns a hash table"
    (assert (hash-table? (make-hash-table))))
  (deftest
    "hash-table? false for dict"
    (assert= false (hash-table? {:a 1})))
  (deftest "hash-table? false for nil" (assert= false (hash-table? nil)))
  (deftest
    "hash-table? false for list"
    (assert= false (hash-table? (list 1 2))))
  (deftest
    "empty table has size 0"
    (assert= 0 (hash-table-size (make-hash-table))))
  (deftest
    "size after one set"
    (let
      ((ht (make-hash-table)))
      (hash-table-set! ht "a" 1)
      (assert= 1 (hash-table-size ht))))
  (deftest
    "size after multiple sets"
    (let
      ((ht (make-hash-table)))
      (hash-table-set! ht "a" 1)
      (hash-table-set! ht "b" 2)
      (hash-table-set! ht "c" 3)
      (assert= 3 (hash-table-size ht))))
  (deftest
    "set same key does not grow size"
    (let
      ((ht (make-hash-table)))
      (hash-table-set! ht "a" 1)
      (hash-table-set! ht "a" 2)
      (assert= 1 (hash-table-size ht))))
  (deftest
    "ref returns set value"
    (let
      ((ht (make-hash-table)))
      (hash-table-set! ht "k" 42)
      (assert= 42 (hash-table-ref ht "k"))))
  (deftest
    "ref returns updated value after overwrite"
    (let
      ((ht (make-hash-table)))
      (hash-table-set! ht "k" 1)
      (hash-table-set! ht "k" 99)
      (assert= 99 (hash-table-ref ht "k"))))
  (deftest
    "ref with default returns default for missing key"
    (assert=
      "fallback"
      (hash-table-ref (make-hash-table) "missing" "fallback")))
  (deftest
    "ref with default returns value when key exists"
    (let
      ((ht (make-hash-table)))
      (hash-table-set! ht "x" 7)
      (assert= 7 (hash-table-ref ht "x" 0))))
  (deftest
    "keyword keys work"
    (let
      ((ht (make-hash-table)))
      (hash-table-set! ht :foo "bar")
      (assert= "bar" (hash-table-ref ht :foo))))
  (deftest
    "number keys work"
    (let
      ((ht (make-hash-table)))
      (hash-table-set! ht 0 "zero")
      (assert= "zero" (hash-table-ref ht 0))))
  (deftest
    "delete removes key"
    (let
      ((ht (make-hash-table)))
      (hash-table-set! ht "x" 1)
      (hash-table-delete! ht "x")
      (assert= "gone" (hash-table-ref ht "x" "gone"))))
  (deftest
    "delete reduces size"
    (let
      ((ht (make-hash-table)))
      (hash-table-set! ht "a" 1)
      (hash-table-set! ht "b" 2)
      (hash-table-delete! ht "a")
      (assert= 1 (hash-table-size ht))))
  (deftest
    "delete missing key is no-op"
    (let
      ((ht (make-hash-table)))
      (hash-table-delete! ht "absent")
      (assert= 0 (hash-table-size ht))))
  (deftest
    "keys of empty table is empty"
    (assert (empty? (hash-table-keys (make-hash-table)))))
  (deftest
    "keys has correct count"
    (let
      ((ht (make-hash-table)))
      (hash-table-set! ht "a" 1)
      (hash-table-set! ht "b" 2)
      (assert= 2 (len (hash-table-keys ht)))))
  (deftest
    "values has correct count"
    (let
      ((ht (make-hash-table)))
      (hash-table-set! ht "a" 10)
      (hash-table-set! ht "b" 20)
      (assert= 2 (len (hash-table-values ht)))))
  (deftest
    "alist of empty table is empty"
    (assert (empty? (hash-table->alist (make-hash-table)))))
  (deftest
    "alist has correct length"
    (let
      ((ht (make-hash-table)))
      (hash-table-set! ht "x" 1)
      (hash-table-set! ht "y" 2)
      (assert= 2 (len (hash-table->alist ht)))))
  (deftest
    "for-each visits all entries"
    (let
      ((ht (make-hash-table)) (count 0))
      (hash-table-set! ht "a" 1)
      (hash-table-set! ht "b" 2)
      (hash-table-set! ht "c" 3)
      (hash-table-for-each ht (fn (k v) (set! count (+ count 1))))
      (assert= 3 count)))
  (deftest
    "for-each sums values"
    (let
      ((ht (make-hash-table)) (total 0))
      (hash-table-set! ht "a" 10)
      (hash-table-set! ht "b" 20)
      (hash-table-set! ht "c" 30)
      (hash-table-for-each ht (fn (k v) (set! total (+ total v))))
      (assert= 60 total)))
  (deftest
    "merge copies entries from src to dst"
    (let
      ((dst (make-hash-table)) (src (make-hash-table)))
      (hash-table-set! src "x" 1)
      (hash-table-set! src "y" 2)
      (hash-table-merge! dst src)
      (assert= 2 (hash-table-size dst))))
  (deftest
    "merge overwrites existing keys in dst"
    (let
      ((dst (make-hash-table)) (src (make-hash-table)))
      (hash-table-set! dst "k" "old")
      (hash-table-set! src "k" "new")
      (hash-table-merge! dst src)
      (assert= "new" (hash-table-ref dst "k"))))
  (deftest
    "merge does not modify src"
    (let
      ((dst (make-hash-table)) (src (make-hash-table)))
      (hash-table-set! src "a" 1)
      (hash-table-merge! dst src)
      (assert= 1 (hash-table-size src))))
  (deftest
    "type-of returns hash-table"
    (assert= "hash-table" (type-of (make-hash-table)))))