;; lib/prolog/tests/programs/reverse.sx — naive reverse/2 via append/3.
;;
;; Mirrors reverse.pl (embedded as a string here).

(define pl-rv-test-count 0)
(define pl-rv-test-pass 0)
(define pl-rv-test-fail 0)
(define pl-rv-test-failures (list))

(define
  pl-rv-test!
  (fn
    (name got expected)
    (begin
      (set! pl-rv-test-count (+ pl-rv-test-count 1))
      (if
        (= got expected)
        (set! pl-rv-test-pass (+ pl-rv-test-pass 1))
        (begin
          (set! pl-rv-test-fail (+ pl-rv-test-fail 1))
          (append!
            pl-rv-test-failures
            (str name "\n    expected: " expected "\n    got:      " got)))))))

(define
  pl-rv-term-to-sx
  (fn
    (t)
    (cond
      ((pl-num? t) (pl-num-val t))
      ((pl-atom? t) (pl-atom-name t))
      (true (list :complex)))))

(define
  pl-rv-list-walked
  (fn
    (w)
    (cond
      ((and (pl-atom? w) (= (pl-atom-name w) "[]")) (list))
      ((and (pl-compound? w) (= (pl-fun w) ".") (= (len (pl-args w)) 2))
        (cons
          (pl-rv-term-to-sx (first (pl-args w)))
          (pl-rv-list-walked (nth (pl-args w) 1))))
      (true (list :not-list)))))

(define pl-rv-list-to-sx (fn (t) (pl-rv-list-walked (pl-walk-deep t))))

(define
  pl-rv-goal
  (fn
    (src env)
    (pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))

(define
  pl-rv-prog-src
  "reverse([], []). reverse([H|T], R) :- reverse(T, RT), append(RT, [H], R). append([], L, L). append([H|T], L, [H|R]) :- append(T, L, R).")

(define pl-rv-db (pl-mk-db))
(pl-db-load! pl-rv-db (pl-parse pl-rv-prog-src))

(define pl-rv-env-1 {})
(define pl-rv-goal-1 (pl-rv-goal "reverse([], X)" pl-rv-env-1))
(pl-solve-once! pl-rv-db pl-rv-goal-1 (pl-mk-trail))

(pl-rv-test!
  "reverse([], X) → X = []"
  (pl-rv-list-to-sx (dict-get pl-rv-env-1 "X"))
  (list))

(define pl-rv-env-2 {})
(define pl-rv-goal-2 (pl-rv-goal "reverse([1], X)" pl-rv-env-2))
(pl-solve-once! pl-rv-db pl-rv-goal-2 (pl-mk-trail))

(pl-rv-test!
  "reverse([1], X) → X = [1]"
  (pl-rv-list-to-sx (dict-get pl-rv-env-2 "X"))
  (list 1))

(define pl-rv-env-3 {})
(define pl-rv-goal-3 (pl-rv-goal "reverse([1, 2, 3], X)" pl-rv-env-3))
(pl-solve-once! pl-rv-db pl-rv-goal-3 (pl-mk-trail))

(pl-rv-test!
  "reverse([1, 2, 3], X) → X = [3, 2, 1]"
  (pl-rv-list-to-sx (dict-get pl-rv-env-3 "X"))
  (list 3 2 1))

(define pl-rv-env-4 {})
(define pl-rv-goal-4 (pl-rv-goal "reverse([a, b, c, d], X)" pl-rv-env-4))
(pl-solve-once! pl-rv-db pl-rv-goal-4 (pl-mk-trail))

(pl-rv-test!
  "reverse([a, b, c, d], X) → X = [d, c, b, a]"
  (pl-rv-list-to-sx (dict-get pl-rv-env-4 "X"))
  (list "d" "c" "b" "a"))

(pl-rv-test!
  "reverse([1, 2, 3], [3, 2, 1]) succeeds"
  (pl-solve-once!
    pl-rv-db
    (pl-rv-goal "reverse([1, 2, 3], [3, 2, 1])" {})
    (pl-mk-trail))
  true)

(pl-rv-test!
  "reverse([1, 2], [1, 2]) fails"
  (pl-solve-once!
    pl-rv-db
    (pl-rv-goal "reverse([1, 2], [1, 2])" {})
    (pl-mk-trail))
  false)

(define pl-reverse-tests-run! (fn () {:failed pl-rv-test-fail :passed pl-rv-test-pass :total pl-rv-test-count :failures pl-rv-test-failures}))
