 (defrecord Recall [memory pattern]) ;; memory is one of :known :pretended :remembered :forgotten :known&pretended
 (defrecord Compute [pattern])
 (defrecord Output [memory pattern]) ;; memory is one of :pretended :remembered :forgotten
-;; (defrecord OutputMany [memory vars expr]) ;; memory is one of :pretended :remembered :forgotten
 
 ;; horrible non-relational things
 (deffact Let "Let [name] be the result of [vars] [expr]")
 (deffact When "When [vars] [expr]")
 
-(defn recall->nodes [plan {:keys [memory pattern]}]
-  (let [[plan node] (add-flow plan (FilterMap. (memory->nodes memory) (pattern->deconstructor pattern)))]
-    [plan [node] (pattern->vars pattern)]))
+;; if clause can be calculated somehow then return a new [plan node vars] pair that calculates it
+;; otherwise return nil
+(defn add-clause [plan nodes vars clause]
+  (condp = (type clause)
+    Recall (let [{:keys [memory pattern]} clause
+                 [plan node] (add-flow plan (FilterMap. (memory->nodes memory) (pattern->deconstructor pattern)))]
+             [plan [node] (pattern->vars pattern)])
+    Compute (let [{:keys [pattern]} clause]
+              (condp = (.-shape pattern)
+                Let (when (every? (set vars) (:vars pattern))
+                      (let [let-fun (expr->fun vars (:expr pattern))
+                            filter-map-fun (if (contains? (set vars) (:name pattern))
+                                             ;; filter
+                                             (let [name-ix (ix-of vars (:name pattern))]
+                                               (fn [fact]
+                                                 (let [new-value (.apply let-fun nil (.-values fact))]
+                                                   (when (= new-value (aget (.-values fact) name-ix))
+                                                     fact))))
+                                             ;; define
+                                             (fn [fact]
+                                               (let [new-value (.apply let-fun nil (.-values fact))
+                                                     new-values (aclone (.-values fact))]
+                                                 (apush new-values new-value)
+                                                 (Fact. nil new-values nil))))
+                            [plan node] (add-flow plan (->FilterMap nodes filter-map-fun))]
+                        [plan [node] (conj vars (:name pattern))]))
+                When (when (every? (set vars) (:vars pattern))
+                       (let [when-fun (expr->fun vars (:expr pattern))
+                             filter-map-fun (fn [fact]
+                                              (when (.apply when-fun nil (.-values fact))
+                                                fact))
+                             [plan node] (add-flow plan (->FilterMap nodes filter-map-fun))]
+                         [plan [node] vars]))))
+    Output (let [{:keys [memory pattern]} clause
+                 _ (assert (every? (set vars) (pattern->vars pattern)))
+                 [plan output-node] (add-flow plan (FilterMap. nodes (pattern->constructor pattern vars)))
+                 plan (add-output plan output-node memory)]
+             [plan nodes vars])))
 
 (comment
   (let [plan empty-flow-plan
-        [plan nodes-a vars-a] (recall->nodes plan (Recall. :known&pretended (->edge 'x 'y)))
-        [plan nodes-b vars-b] (recall->nodes plan (Recall. :known (->connected 'x 0)))]
+        [plan nodes-a vars-a] (add-clause plan nil nil (Recall. :known&pretended (->edge 'x 'y)))
+        [plan nodes-b vars-b] (add-clause plan nil nil (Recall. :known (->connected 'x 0)))]
     [plan nodes-a nodes-b vars-a vars-b])
-  )
-
-;; if clause can be calculated somehow then return a new [plan node vars] pair that calculates it
-;; TODO make this extensible
-(defn compute->nodes [plan nodes vars {:keys [pattern]}]
-  (condp = (.-shape pattern)
-    Let (when (every? (set vars) (:vars pattern))
-          (let [let-fun (expr->fun vars (:expr pattern))
-                filter-map-fun (fn [fact]
-                                 (let [new-value (.apply let-fun nil (.-values fact))
-                                       new-values (aclone (.-values fact))]
-                                   (apush new-values new-value)
-                                   (Fact. nil new-values)))
-                [plan node] (add-flow plan (->FilterMap nodes filter-map-fun))]
-            [plan [node] (conj vars (:name pattern))]))
-    When (when (every? (set vars) (:vars pattern))
-           (let [when-fun (expr->fun vars (:expr pattern))
-                 filter-map-fun (fn [fact]
-                                  (when (.apply when-fun nil (.-values fact))
-                                    fact))
-                 [plan node] (add-flow plan (->FilterMap nodes filter-map-fun))]
-             [plan [node] vars]))))
-
-(comment
+
   (let [plan empty-flow-plan
-        [plan nodes-a vars-a] (recall->nodes plan (Recall. :known&pretended (->edge 'x 'y)))
-        [plan nodes-b vars-b] (compute->nodes plan nodes-a vars-a (Compute. (->Let 'z '[x y] "x + y")))]
+        [plan nodes-a vars-a] (add-clause plan nil nil (Recall. :known&pretended (->edge 'x 'y)))
+        [plan nodes-b vars-b] (add-clause plan nodes-a vars-a (Compute. (->Let 'z '[x y] "x + y")))]
     [plan nodes-a nodes-b vars-a vars-b])
 
-
   (let [plan empty-flow-plan
-        [plan nodes-a vars-a] (recall->nodes plan (Recall. :known&pretended (->edge 'x 'y)))
-        res (compute->nodes plan nodes-a vars-a (Compute. (->Let 'z '[w x y] "w + x + y")))]
+        [plan nodes-a vars-a] (add-clause plan nil nil (Recall. :known&pretended (->edge 'x 'y)))
+        res (add-clause plan nodes-a vars-a (Compute. (->Let 'z '[w x y] "w + x + y")))]
     res)
 
   (let [plan empty-flow-plan
-        [plan nodes-a vars-a] (recall->nodes plan (Recall. :known&pretended (->edge 'x 'y)))
-        [plan nodes-b vars-b] (compute->nodes plan nodes-a vars-a (Compute. (->When '[x y] "x > y")))]
+        [plan nodes-a vars-a] (add-clause plan nil nil (Recall. :known&pretended (->edge 'x 'y)))
+        [plan nodes-b vars-b] (add-clause plan nodes-a vars-a (Compute. (->When '[x y] "x > y")))]
     [plan nodes-a nodes-b vars-a vars-b])
   )
 
-(defn computes->nodes [plan nodes vars computes]
-    (loop [orig-nodes nodes
-           plan plan
-           nodes nodes
-           vars vars
-           computes-remaining computes
-           computes-applied computes-applied
-           computes-unapplied []]
-      (if-let [[compute & computes-remaining] (seq computes-remaining)]
-        (if-let [[new-plan new-nodes new-vars] (compute->nodes plan nodes vars compute)]
-          (recur orig-nodes new-plan new-nodes new-vars computes-remaining (conj computes-applied compute) computes-unapplied)
-          (recur orig-nodes plan nodes vars computes-remaining computes-applied (conj computes-unapplied compute)))
-        (if (= nodes orig-nodes)
-          [plan nodes vars (set computes-applied)]
-          (recur nodes plan nodes vars computes-unapplied computes-applied [])))))
-
-(comment
-  (let [plan empty-flow-plan
-        [plan nodes-a vars-a] (recall->nodes plan (Recall. :known&pretended (->edge 'x 'y)))
-        [plan nodes-b vars-b computes-applied] (computes->nodes plan nodes-a vars-a [(Compute. (->When '[x z] "x > z"))
-                                                                                     (Compute. (->Let 'z '[x y] "x + y"))
-                                                                                     (Compute. (->Let 'z '[w x] "a + x"))])]
-    [plan nodes-b vars-b computes-applied])
-  )
-
-(defn output->nodes [plan nodes vars {:keys [memory pattern]}]
-  (assert (every? (set vars) (pattern->vars pattern)))
-  (let [[plan output-node] (add-flow plan (FilterMap. nodes (pattern->constructor pattern vars)))
-        plan (add-output plan output-node memory)]
-    plan))
-
-(comment
-  (let [plan empty-flow-plan
-        [plan nodes-a vars-a] (recall->nodes plan (Recall. :known&pretended (->edge 'x 'y)))
-        [plan nodes-b vars-b computes-applied] (computes->nodes plan nodes-a vars-a [(Compute. (->When '[x z] "x > z"))
-                                                                                     (Compute. (->Let 'z '[x y] "x + y"))
-                                                                                     (Compute. (->Let 'z '[w x] "a + x"))])
-        plan (output->nodes plan nodes-b vars-b (Output. :remembered (->connected 'x 'y)))]
-    plan)
-  )
-
-(defn join->nodes [plan nodes-a vars-a computes-applied-a nodes-b vars-b computes-applied-b]
+;; RULES
+
+(defrecord Rule [clauses])
+
+;; Correctness: Each clause must appear at least once in the plan
+;; Heuristic: Each Recall clause is used at most once in the plan
+;; Heuristic: Each Filter/Let clause is used at most once per path in the plan
+
+(defn add-computes [plan&nodes&vars computes]
+  (let [plan&nodes&vars (atom plan&nodes&vars)
+        computes-skipped #js []]
+    (doseq [compute computes]
+      (if-let [new-plan&nodes&vars (apply add-clause (conj @plan&nodes&vars compute))]
+        (reset! plan&nodes&vars new-plan&nodes&vars)
+        (apush computes-skipped compute)))
+    (if (= (count computes) (count computes-skipped))
+      @plan&nodes&vars
+      (recur @plan&nodes&vars computes-skipped))))
+
+(defn join-clauses [plan nodes-a vars-a nodes-b vars-b]
   (let [key-vars (intersection (set vars-a) (set vars-b))
         val-vars (union (set vars-a) (set vars-b))
         key-ixes-a (ixes-of vars-a key-vars)

        [plan index-b] (add-flow plan (->Index nodes-b key-ixes-b))
        [plan lookup-a] (add-flow plan (->Lookup [index-a] index-b key-ixes-a val-ixes-a))
        [plan lookup-b] (add-flow plan (->Lookup [index-b] index-a key-ixes-b val-ixes-b))]
    [plan [lookup-a lookup-b] (distinct (concat vars-a vars-b)) (union computes-applied-a computes-applied-b)]))

(comment
  (let [plan empty-flow-plan
        [plan nodes-a vars-a] (recall->nodes plan (Recall. :known&pretended (->edge 'x 'y)))
        [plan nodes-b vars-b] (recall->nodes plan (Recall. :known (->connected 'y 'z)))
        [plan nodes-c vars-c computes-applied] (join->nodes plan nodes-a vars-a #{} nodes-b vars-b #{})]
    [plan nodes-c vars-c])
  )

;; RULES

(defrecord Rule [clauses])

;; Correctness: Each clause must appear at least once in the plan
;; Heuristic: Each Recall clause is used at most once in the plan
;; Heuristic: Each Filter/Let clause is used at most once per path in the plan

(defn add-rule [plan rule]
  (let [recalls (filter #(= Recall (type %)) (:clauses rule))
        computes (set (filter #(= Compute (type %)) (:clauses rule)))
        outputs (filter #(= Output (type %)) (:clauses rule))
        main-plan (atom plan)
        nodes&vars&computes-applied (for [recall recalls]
                                      (let [plan @main-plan
                                            [plan node vars] (recall->nodes plan recall)
                                            [plan node vars computes-applied] (computes->nodes plan node vars computes)]
                                        (reset! main-plan plan)
                                        [node vars computes-applied]))
        [nodes vars computes-applied] (reduce (fn [[nodes-a vars-a computes-applied-a] [nodes-b vars-b computes-applied-b]]
                                               (let [plan @main-plan
                                                     [plan nodes vars computes-applied-old] (join->nodes plan nodes-a vars-a computes-applied-a nodes-b vars-b computes-applied-b)
                                                     [plan nodes vars computes-applied-new] (computes->nodes plan nodes vars (difference computes computes-applied-old))]
                                                 (reset! main-plan plan)
                                                 [nodes vars (union computes-applied-old computes-applied-new)]))
                                              nodes&vars&computes-applied)]
    (assert (= computes-applied (set computes)) (str "Could not apply " (pr-str (difference (set computes) computes-applied))))
    (doseq [output outputs]
      (reset! main-plan (output->nodes @main-plan nodes vars output)))
    @main-plan))

(defn add-rules [plan rules]

            sink (aclone values)]
        (dotimes [i (alength source-ixes)]
          (aset sink (aget sink-ixes i) (aget source (aget source-ixes i))))
        (Fact. shape sink nil)))))

(defn pattern->deconstructor [pattern]
  (let [shape (.-shape pattern)
