 ;;--------------------------------------------------------------------------------
 ;; impl
 
+;;; calculate all permutations for a given list of sets...
+;;; e.g. 
+;;; (permute [[]] [[:a1 :a2][:b1 :b2 :b3][:c1]]) ->
+;;; ([:a1 :b1 :c1] [:a1 :b2 :c1] [:a1 :b3 :c1] [:a2 :b1 :c1] [:a2 :b2 :c1] [:a2 :b3 :c1])
+
+;;; TODO: should use recurse
+(defn- permute [state [head & tail]]
+  (if (empty? head)
+    state
+    (permute (mapcat (fn [i] (map (fn [j] (conj i j)) head)) state) tail)))
+
+;;; consider outer joins
+;; (defn- permute2 [inner state [head & tail]]
+;;   (if (empty? tail)
+;;     state
+;;     (if-let [head (if (and (empty? head) inner) nil [nil])]
+;; 	(permute (mapcat (fn [i] (map (fn [j] (conj i j)) head)) state) tail))))
+
 ;; TODO: return a pair - first is notification fn, second is new-indeces - use swap*! to apply
 
 (defn- derive-joins [v rhs-indeces ks join-fn]

(deftest test-join-model
  (let [as (versioned-optimistic-map-model (str :as) :name :version >)
	bs (versioned-optimistic-map-model (str :bs) :name :version >)
	ds (versioned-optimistic-map-model (str :ds) :name :version >)
	join (join-model "join-model" [[as :b :d][bs :name][ds :name]] join-ace)
	view (test-view "test")
	a (->A :a 0 :b :d)
	b (->B :b 0 :c)
	d (->D :d 0 :e)]
    (is (= [[{}{}]{}{}] (data join)))

    (attach join view)
    (is (= nil (data view)))

    (on-upsert bs b)
    (is (= [[{}{}]{:b [b]} {}] (data join)))

    (on-upsert ds d)
    (is (= [[{}{}]{:b [b]} {:d [d]}] (data join)))

    (on-upsert as a)
    (is (= [[{:b [a]}{:d [a]}]{:b [b]} {:d [d]}] (data join)))

    ;; (on-delete join james)
    ;; (on-upserts join [john steve])
    ;; (on-deletes join [john steve])

    ))

;;--------------------------------------------------------------------------------
;; tests

(defrecord A [name ^int version fk-b fk-c]
  Object
  (^String toString [_] (str name)))

(defrecord B [name ^int version b data]
  Object
  (^String toString [_] (str name)))

(defrecord C [name ^int version c data]
  Object
  (^String toString [_] (str name)))

(defrecord ABC [name ^int version b-data c-data]
  Object
  (^String toString [_] (str name)))

;; an example of an aggressive join-fn
;; a "lazy" join fn would define the same interface, but hold references to the A,B and C...
;; a really clever impl might start lazy and become agressive during serialisation..
(defn- ^ABC join-abc [^A a ^B b ^C c]
  (->ABC
   (keyword (apply str (interpose "-" (list (name (:name a)) (name (:name b)) (name (:name c))))))
   (+ (* 1000 (:version a)) (* 100 (:version b)) (* 10 (:version c)))
   (:data b)
   (:data c)))

(deftest test-join-abc
  (is (= (->ABC :a1-b1-c1 0 "b-data" "c-data") 
	 (join-abc (->A :a1 0 :b :c)(->B :b1 0 :b "b-data")(->C :c1 0 :c "c-data")))))

(deftest test-permute
  (is (= 
