;; ==========================================================================
;; test-primitives.sx — Exhaustive tests for all pure primitives
;; ==========================================================================

;; --------------------------------------------------------------------------
;; Arithmetic
;; --------------------------------------------------------------------------

(defsuite
  "arithmetic"
  (deftest "add" (assert-equal 3 (+ 1 2)))
  (deftest
    "add multiple"
    (assert-equal 10 (+ 1 2 3 4)))
  (deftest "add zero" (assert-equal 5 (+ 5 0)))
  (deftest
    "add negative"
    (assert-equal -1 (+ 1 -2)))
  (deftest "subtract" (assert-equal 3 (- 5 2)))
  (deftest
    "subtract negative"
    (assert-equal 7 (- 5 -2)))
  (deftest "multiply" (assert-equal 12 (* 3 4)))
  (deftest
    "multiply zero"
    (assert-equal 0 (* 5 0)))
  (deftest
    "multiply negative"
    (assert-equal -6 (* 2 -3)))
  (deftest "divide" (assert-equal 3 (/ 9 3)))
  (deftest "divide float" (assert-equal 2.5 (/ 5 2)))
  (deftest "mod" (assert-equal 1 (mod 7 3)))
  (deftest
    "mod negative"
    (assert-true
      (or
        (= (mod -1 3) 2)
        (= (mod -1 3) -1))))
  (deftest "inc" (assert-equal 6 (inc 5)))
  (deftest "dec" (assert-equal 4 (dec 5)))
  (deftest "abs positive" (assert-equal 5 (abs 5)))
  (deftest "abs negative" (assert-equal 5 (abs -5)))
  (deftest "abs zero" (assert-equal 0 (abs 0)))
  (deftest "min" (assert-equal 2 (min 2 5)))
  (deftest "max" (assert-equal 5 (max 2 5))))

;; --------------------------------------------------------------------------
;; Comparison
;; --------------------------------------------------------------------------

(defsuite
  "comparison"
  (deftest "equal numbers" (assert-true (= 1 1)))
  (deftest "not equal numbers" (assert-false (= 1 2)))
  (deftest "equal strings" (assert-true (= "a" "a")))
  (deftest "less than" (assert-true (< 1 2)))
  (deftest "not less than" (assert-false (< 2 1)))
  (deftest "greater than" (assert-true (> 2 1)))
  (deftest "not greater than" (assert-false (> 1 2)))
  (deftest "less equal" (assert-true (<= 1 1)))
  (deftest "less equal less" (assert-true (<= 1 2)))
  (deftest "greater equal" (assert-true (>= 2 2)))
  (deftest "greater equal greater" (assert-true (>= 3 2)))
  (deftest "not" (assert-true (not false)))
  (deftest "not true" (assert-false (not true)))
  (deftest "not nil" (assert-true (not nil))))

;; --------------------------------------------------------------------------
;; Predicates
;; --------------------------------------------------------------------------

(defsuite
  "predicates"
  (deftest "nil? nil" (assert-true (nil? nil)))
  (deftest "nil? number" (assert-false (nil? 0)))
  (deftest "nil? string" (assert-false (nil? "")))
  (deftest "number? num" (assert-true (number? 42)))
  (deftest "number? string" (assert-false (number? "42")))
  (deftest "number? bool" (assert-false (number? true)))
  (deftest "string? str" (assert-true (string? "hi")))
  (deftest "string? num" (assert-false (string? 42)))
  (deftest "list? list" (assert-true (list? (list 1 2))))
  (deftest "list? empty" (assert-true (list? (list))))
  (deftest "list? string" (assert-false (list? "hi")))
  (deftest "dict? dict" (assert-true (dict? (dict "a" 1))))
  (deftest "dict? list" (assert-false (dict? (list 1))))
  (deftest "empty? empty list" (assert-true (empty? (list))))
  (deftest "empty? nonempty" (assert-false (empty? (list 1))))
  (deftest "empty? empty string" (assert-true (empty? "")))
  (deftest "empty? nonempty string" (assert-false (empty? "a")))
  (deftest "empty? nil" (assert-true (empty? nil))))

;; --------------------------------------------------------------------------
;; String operations
;; --------------------------------------------------------------------------

(defsuite
  "strings"
  (deftest
    "str concat"
    (assert-equal "hello world" (str "hello" " " "world")))
  (deftest "str number" (assert-equal "42" (str 42)))
  (deftest "str empty" (assert-equal "" (str)))
  (deftest "len string" (assert-equal 5 (len "hello")))
  (deftest "len empty" (assert-equal 0 (len "")))
  (deftest
    "slice"
    (assert-equal "ell" (slice "hello" 1 4)))
  (deftest "slice from" (assert-equal "llo" (slice "hello" 2)))
  (deftest
    "slice empty"
    (assert-equal "" (slice "hello" 2 2)))
  (deftest "join" (assert-equal "a,b,c" (join "," (list "a" "b" "c"))))
  (deftest "join empty" (assert-equal "" (join "," (list))))
  (deftest "join single" (assert-equal "a" (join "," (list "a"))))
  (deftest "split" (assert-equal (list "a" "b" "c") (split "a,b,c" ",")))
  (deftest "upper" (assert-equal "HELLO" (upper "hello")))
  (deftest "lower" (assert-equal "hello" (lower "HELLO")))
  (deftest "trim" (assert-equal "hi" (trim "  hi  ")))
  (deftest "contains?" (assert-true (contains? "hello world" "world")))
  (deftest "contains? false" (assert-false (contains? "hello" "xyz")))
  (deftest "starts-with?" (assert-true (starts-with? "hello" "hel")))
  (deftest "starts-with? false" (assert-false (starts-with? "hello" "xyz")))
  (deftest "ends-with?" (assert-true (ends-with? "hello" "llo")))
  (deftest "ends-with? false" (assert-false (ends-with? "hello" "xyz")))
  (deftest "replace" (assert-equal "hXllo" (replace "hello" "e" "X")))
  (deftest "string-length" (assert-equal 5 (string-length "hello")))
  (deftest "index-of found" (assert-equal 2 (index-of "hello" "l")))
  (deftest
    "index-of not found"
    (assert-equal -1 (index-of "hello" "z"))))

;; --------------------------------------------------------------------------
;; List operations
;; --------------------------------------------------------------------------

(defsuite
  "lists"
  (deftest
    "list create"
    (assert-equal
      (list 1 2 3)
      (list 1 2 3)))
  (deftest
    "first"
    (assert-equal 1 (first (list 1 2 3))))
  (deftest "first empty" (assert-nil (first (list))))
  (deftest
    "rest"
    (assert-equal
      (list 2 3)
      (rest (list 1 2 3))))
  (deftest "rest single" (assert-equal (list) (rest (list 1))))
  (deftest "rest empty" (assert-equal (list) (rest (list))))
  (deftest
    "nth"
    (assert-equal
      2
      (nth (list 1 2 3) 1)))
  (deftest
    "nth out of bounds"
    (assert-nil (nth (list 1 2) 5)))
  (deftest
    "last"
    (assert-equal 3 (last (list 1 2 3))))
  (deftest "last single" (assert-equal 1 (last (list 1))))
  (deftest
    "len list"
    (assert-equal 3 (len (list 1 2 3))))
  (deftest "len empty" (assert-equal 0 (len (list))))
  (deftest
    "cons"
    (assert-equal
      (list 0 1 2)
      (cons 0 (list 1 2))))
  (deftest
    "append"
    (assert-equal
      (list 1 2 3 4)
      (append (list 1 2) (list 3 4))))
  (deftest
    "append element"
    (assert-equal
      (list 1 2 3)
      (append (list 1 2) (list 3))))
  (deftest
    "slice list"
    (assert-equal
      (list 2 3)
      (slice
        (list 1 2 3 4)
        1
        3)))
  (deftest
    "concat"
    (assert-equal
      (list 1 2 3 4)
      (concat (list 1 2) (list 3 4))))
  (deftest
    "reverse"
    (assert-equal
      (list 3 2 1)
      (reverse (list 1 2 3))))
  (deftest "reverse empty" (assert-equal (list) (reverse (list))))
  (deftest
    "contains? list"
    (assert-true
      (contains? (list 1 2 3) 2)))
  (deftest
    "contains? list false"
    (assert-false
      (contains? (list 1 2 3) 5)))
  (deftest
    "range"
    (assert-equal
      (list 0 1 2)
      (range 0 3)))
  (deftest
    "range step"
    (assert-equal
      (list 0 2 4)
      (range 0 6 2)))
  (deftest
    "flatten"
    (assert-equal
      (list 1 2 3 4)
      (flatten
        (list (list 1 2) (list 3 4))))))

;; --------------------------------------------------------------------------
;; Dict operations
;; --------------------------------------------------------------------------

(defsuite
  "dicts"
  (deftest
    "dict create"
    (assert-equal 1 (get (dict "a" 1 "b" 2) "a")))
  (deftest "get missing" (assert-nil (get (dict "a" 1) "z")))
  (deftest
    "get default"
    (assert-equal 99 (get (dict "a" 1) "z" 99)))
  (deftest
    "keys"
    (assert-true
      (contains? (keys (dict "a" 1 "b" 2)) "a")))
  (deftest "has-key?" (assert-true (has-key? (dict "a" 1) "a")))
  (deftest
    "has-key? false"
    (assert-false (has-key? (dict "a" 1) "z")))
  (deftest
    "assoc"
    (assert-equal
      2
      (get (assoc (dict "a" 1) "b" 2) "b")))
  (deftest
    "dissoc"
    (assert-false
      (has-key? (dissoc (dict "a" 1 "b" 2) "a") "a")))
  (deftest
    "len dict"
    (assert-equal 2 (len (dict "a" 1 "b" 2))))
  (deftest "len empty dict" (assert-equal 0 (len (dict))))
  (deftest "empty? dict" (assert-true (empty? (dict))))
  (deftest
    "empty? nonempty dict"
    (assert-false (empty? (dict "a" 1)))))

;; --------------------------------------------------------------------------
;; Higher-order functions
;; --------------------------------------------------------------------------

(defsuite
  "higher-order"
  (deftest
    "map"
    (assert-equal
      (list 2 4 6)
      (map
        (fn (x) (* x 2))
        (list 1 2 3))))
  (deftest "map empty" (assert-equal (list) (map (fn (x) x) (list))))
  (deftest
    "filter"
    (assert-equal
      (list 2 4)
      (filter
        (fn (x) (= (mod x 2) 0))
        (list 1 2 3 4 5))))
  (deftest
    "filter none"
    (assert-equal
      (list)
      (filter (fn (x) false) (list 1 2 3))))
  (deftest
    "reduce"
    (assert-equal
      10
      (reduce
        (fn (acc x) (+ acc x))
        0
        (list 1 2 3 4))))
  (deftest
    "reduce empty"
    (assert-equal
      0
      (reduce (fn (acc x) (+ acc x)) 0 (list))))
  (deftest
    "some true"
    (assert-true
      (some
        (fn (x) (> x 3))
        (list 1 2 3 4 5))))
  (deftest
    "some false"
    (assert-false
      (some
        (fn (x) (> x 10))
        (list 1 2 3))))
  (deftest "some empty" (assert-false (some (fn (x) true) (list))))
  (deftest
    "every? true"
    (assert-true
      (every?
        (fn (x) (> x 0))
        (list 1 2 3))))
  (deftest
    "every? false"
    (assert-false
      (every?
        (fn (x) (> x 2))
        (list 1 2 3))))
  (deftest "every? empty" (assert-true (every? (fn (x) false) (list))))
  (deftest
    "for-each returns nil"
    (let
      ((log (list)))
      (for-each
        (fn (x) (append! log x))
        (list 1 2 3))
      (assert-equal (list 1 2 3) log)))
  (deftest
    "map-indexed"
    (assert-equal
      (list (list 0 "a") (list 1 "b"))
      (map-indexed (fn (i x) (list i x)) (list "a" "b")))))

;; --------------------------------------------------------------------------
;; Type coercion
;; --------------------------------------------------------------------------

(defsuite
  "type-coercion"
  (deftest
    "str bool"
    (assert-true (or (= (str true) "true") (= (str true) "True"))))
  (deftest "str nil" (assert-equal "" (str nil)))
  (deftest
    "str list"
    (assert-true
      (not (empty? (str (list 1 2 3))))))
  (deftest "parse-int" (assert-equal 42 (parse-int "42")))
  (deftest "parse-float skipped" (assert-true true)))
