;; lib/relations/tests/path.sx — Phase 3: typed relations, path, distance.

(define relations-pt-pass 0)
(define relations-pt-fail 0)
(define relations-pt-failures (list))

(define
  relations-pt-check!
  (fn
    (name got expected)
    (if
      (= got expected)
      (set! relations-pt-pass (+ relations-pt-pass 1))
      (do
        (set! relations-pt-fail (+ relations-pt-fail 1))
        (append!
          relations-pt-failures
          (str name "\n    expected: " expected "\n    got:      " got))))))

(define
  relations-pt-subset?
  (fn
    (xs ys)
    (cond
      ((= (len xs) 0) true)
      ((relations-member? (first xs) ys)
        (relations-pt-subset? (rest xs) ys))
      (else false))))

(define
  relations-pt-set=?
  (fn
    (xs ys)
    (and
      (= (len xs) (len ys))
      (relations-pt-subset? xs ys)
      (relations-pt-subset? ys xs))))

;; Two kinds coexisting in one db.
;;   parent: a->b, b->c, c->d, a->c (shortcut), x->y (disconnected)
;;   member: c->m, m->n (crosses into a different kind)
(define
  relations-pt-fixture
  (fn
    ()
    (relations-build-db
      (list
        (relations-rel (quote a) (quote b) (quote parent))
        (relations-rel (quote b) (quote c) (quote parent))
        (relations-rel (quote c) (quote d) (quote parent))
        (relations-rel (quote a) (quote c) (quote parent))
        (relations-rel (quote x) (quote y) (quote parent))
        (relations-rel (quote c) (quote m) (quote member))
        (relations-rel (quote m) (quote n) (quote member))))))

;; A cycle with an exit: u->v->w->u, w->exit.
(define
  relations-pt-cyc-fixture
  (fn
    ()
    (relations-build-db
      (list
        (relations-rel (quote u) (quote v) (quote parent))
        (relations-rel (quote v) (quote w) (quote parent))
        (relations-rel (quote w) (quote u) (quote parent))
        (relations-rel (quote w) (quote exit) (quote parent))))))

(define
  relations-pt-run-all!
  (fn
    ()
    (let
      ((db (relations-pt-fixture)) (cyc (relations-pt-cyc-fixture)))
      (do
        (relations-pt-check!
          "shortest path a->d"
          (relations-path db (quote a) (quote d) (quote parent))
          (list (quote a) (quote c) (quote d)))
        (relations-pt-check!
          "distance a->d is 2"
          (relations-distance db (quote a) (quote d) (quote parent))
          2)
        (relations-pt-check!
          "direct edge path a->c"
          (relations-path db (quote a) (quote c) (quote parent))
          (list (quote a) (quote c)))
        (relations-pt-check!
          "distance a->c is 1"
          (relations-distance db (quote a) (quote c) (quote parent))
          1)
        (relations-pt-check!
          "path b->d"
          (relations-path db (quote b) (quote d) (quote parent))
          (list (quote b) (quote c) (quote d)))
        (relations-pt-check!
          "self path"
          (relations-path db (quote a) (quote a) (quote parent))
          (list (quote a)))
        (relations-pt-check!
          "self distance is 0"
          (relations-distance db (quote a) (quote a) (quote parent))
          0)
        (relations-pt-check!
          "unknown target -> nil path"
          (relations-path db (quote a) (quote zzz) (quote parent))
          nil)
        (relations-pt-check!
          "unknown target -> nil distance"
          (relations-distance db (quote a) (quote zzz) (quote parent))
          nil)
        (relations-pt-check!
          "disconnected -> nil path"
          (relations-path db (quote a) (quote y) (quote parent))
          nil)
        (relations-pt-check!
          "no parent path crosses into member edge"
          (relations-path db (quote a) (quote m) (quote parent))
          nil)
        (relations-pt-check!
          "member path c->m"
          (relations-path db (quote c) (quote m) (quote member))
          (list (quote c) (quote m)))
        (relations-pt-check!
          "member path c->n"
          (relations-path db (quote c) (quote n) (quote member))
          (list (quote c) (quote m) (quote n)))
        (relations-pt-check!
          "mixed-kind reachable a->m"
          (relations-reachable-any? db (quote a) (quote m))
          true)
        (relations-pt-check!
          "mixed-kind reachable a->n"
          (relations-reachable-any? db (quote a) (quote n))
          true)
        (relations-pt-check!
          "single-kind a->m not reachable under parent"
          (relations-reachable? db (quote a) (quote m) (quote parent))
          false)
        (relations-pt-check!
          "mixed-kind descendants of a include cross-kind nodes"
          (relations-pt-set=?
            (relations-descendants-any db (quote a))
            (list (quote b) (quote c) (quote d) (quote m) (quote n)))
          true)
        (relations-pt-check!
          "single-kind descendants of a under parent only"
          (relations-pt-set=?
            (relations-descendants db (quote a) (quote parent))
            (list (quote b) (quote c) (quote d)))
          true)
        (relations-pt-check!
          "path out of a cycle"
          (relations-path cyc (quote u) (quote exit) (quote parent))
          (list (quote u) (quote v) (quote w) (quote exit)))
        (relations-pt-check!
          "distance out of a cycle is 3"
          (relations-distance cyc (quote u) (quote exit) (quote parent))
          3)
        (do
          (relations/load!
            (list
              (relations-rel (quote r1) (quote r2) (quote parent))
              (relations-rel (quote r2) (quote r3) (quote parent))
              (relations-rel (quote r3) (quote r4) (quote link))))
          (relations-pt-check!
            "api path"
            (relations/path (quote r1) (quote r3) (quote parent))
            (list (quote r1) (quote r2) (quote r3)))
          (relations-pt-check!
            "api distance"
            (relations/distance (quote r1) (quote r3) (quote parent))
            2)
          (relations-pt-check!
            "api mixed-kind reachable across parent+link"
            (relations/reachable-any? (quote r1) (quote r4))
            true)
          (relations-pt-check!
            "api single-kind not reachable across kinds"
            (relations/reachable? (quote r1) (quote r4) (quote parent))
            false)
          (relations/load! (list)))))))

(define
  relations-path-tests-run!
  (fn
    ()
    (do
      (set! relations-pt-pass 0)
      (set! relations-pt-fail 0)
      (set! relations-pt-failures (list))
      (relations-pt-run-all!)
      {:failures relations-pt-failures :total (+ relations-pt-pass relations-pt-fail) :passed relations-pt-pass :failed relations-pt-fail})))
