    [goog.string]
    [goog.net.jsloader :as loader]
    [clojure.string :as string]
+   [clojure.set :refer [difference]]
    [cljs.core.async :refer [put! chan <! map< close! timeout alts!] :as async])
   (:require-macros
    [cljs.core.async.macros :refer [go go-loop]]))
 
-(declare reload-file*)
+(declare reload-file* resolve-ns)
 
 (defonce ns-meta-data (atom {}))
 
 ;; dependency resolution
 
-#_(defn dependencies []
-  (let [relevant-nms (filter (fn [n]
-                               (and
-                                (not (.startsWith goog.string n "goog"))
-                                (not (.startsWith goog.string n "cljs"))
-                                (not (.startsWith goog.string n "clojure"))))
-                             (js-keys (.. js/goog -dependencies_ -nameToPath)))]
-    (reduce (fn [am nm]
-              (let [pth (aget (.. js/goog -dependencies_ -nameToPath) nm)
-                    reqs (js-keys (aget (.. js/goog -dependencies_ -requires) pth))]
-                (reduce (fn [amm r] (assoc-in amm [r nm] true)) am reqs)))
+(defonce dependency-cache (atom {}))
+
+(defn all? [pred coll]
+  (reduce #(and %1 %2) true (map pred coll)))
+
+(defn namespace-file-map? [m]
+  (or
+   (and (map? m)
+        (string? (:namespace m))
+        (string? (:file m))
+        (= (:type m)
+           :namespace))
+    (do
+      (println "Error not namespace-file-map" (pr-str m))
+      false)))
+
+(defn not-start [n pre]
+  (dev-assert (string? n) (string? pre))
+  (not (.startsWith goog.string n pre)))
+
+(defn invalidate-dependency-cache! []
+  (reset! dependency-cache {}))
+
+(defn relevant-nms []
+  (filter (fn [n]
+            (dev-assert (string? n))
+            (reduce #(and %1 (not-start n %2)) true ["goog" "cljs" "clojure"]))
+          (js-keys (.. js/goog -dependencies_ -nameToPath))))
+
+(defn dependencies* []
+  (dev-assert (map? @dependency-cache))
+  (reduce (fn [acc-map nm]
+            (let [pth (aget (.. js/goog -dependencies_ -nameToPath) nm)
+                  reqs (js-keys (aget (.. js/goog -dependencies_ -requires) pth))]
+              (reduce (fn [amm r] (assoc-in amm [r nm] true)) acc-map reqs)))
             {}
-            relevant-nms)))
-
-#_(defn ns-that-depend-on [nm]
-  (keys (get (dependencies) nm)))
+            (relevant-nms)))
+
+(defn dependencies []
+  (dev-assert (map? @dependency-cache))
+  (if (not-empty @dependency-cache)
+    @dependency-cache
+    (let [deps (dependencies*)]
+      (dev-assert (map? deps))
+      (reset! dependency-cache deps)
+      deps)))
+
+(defn ns-that-depend-on [nm]
+  (dev-assert (string? nm))
+  (set (keys (get (dependencies) nm))))
+
+(defn ancestor* [nm nm2]
+  (dev-assert (string? nm) (string? nm2))
+  (let [deps (ns-that-depend-on nm)]
+    (dev-assert (set? deps))
+    (if (empty? deps)
+      false
+      (if (get deps nm2)
+        true
+        (reduce #(or %1 %2) false
+                (map #(ancestor* % nm2) deps))))))
+
+(defn ancestor [nm nm2]
+  (dev-assert (string? nm) (string? nm2) (map? @dependency-cache))
+  ;; sharing a cache so that it gets blown away when deps are updated
+  (let [cached (get-in @dependency-cache [:ancestor nm nm2])]
+    (if (not (nil? cached))
+      cached
+      (let [res (ancestor* nm nm2)]
+        (swap! dependency-cache assoc-in [:ancestor nm nm2] res)
+        res))))
+
+(defn topo-compare [nm nm2]
+  (dev-assert (string? nm) (string? nm2))
+  (cond
+    (= nm nm2) 0
+    (ancestor nm nm2) -1
+    :else 1))
+
+(defn topo-compare-file-msg [f1 f2]
+  (dev-assert (namespace-file-map? f1) (namespace-file-map? f2))
+  (topo-compare (:namespace f1) (:namespace f2)))
+
+(defn topo-sort-files
+  "Takes a list of file maps and returns a list of files in dep order."
+  [files]
+  (dev-assert (all? namespace-file-map? files))
+  (sort topo-compare-file-msg files))
+
+(defn expand-files-to-include-deps [file-msgs]
+  (dev-assert (all? namespace-file-map? file-msgs))
+  (let [current-ns    (set (map :namespace file-msgs))
+        dependent-ns  (set (mapcat ns-that-depend-on current-ns))
+        additional-ns (difference dependent-ns current-ns)
+        additional-files (map (fn [x] { :namespace x
+                                       :meta-data nil
+                                       :file (resolve-ns x)
+                                       :type :namespace }) additional-ns)]
+    (topo-sort-files (concat (set file-msgs)
+                             additional-files))))
 
 ;; this assumes no query string on url
 (defn add-cache-buster [url]


(defn reload-file [{:keys [request-url] :as file-msg} callback]
  (dev-assert (string? request-url)
              (map? file-msg)
              (not (nil? callback)))
  (utils/debug-prn (str "FigWheel: Attempting to load " request-url))
  (reload-file* request-url
                (fn [success?]

(defn load-all-js-files
  "Returns a chanel with one collection of loaded filenames on it."
  [files]
  (let [out (chan)]
    (go-loop [[f & fs] files]
      (if-not (nil? f)
        (do (put! out (<! (reload-js-file f)))
            (recur fs))
        (close! out)))
    (async/into [] out))
  ;; the following is a parallel load which is facter but non-deterministic
  #_(async/into [] (async/filter< identity (async/merge (mapv reload-js-file files)))))


(defn add-request-url [{:keys [url-rewriter] :as opts} {:keys [file] :as file-msg}]
  (let [resolved-path (resolve-url file-msg)]
    (assoc file-msg :request-url
           (if url-rewriter
             (url-rewriter resolved-path)
             resolved-path))))

(defn add-request-urls [opts files]
  (map (partial add-request-url opts) files))
