 
 (defn fetch
   "Fetch protocol-buffer source and unzip it."
-  []
-  (when-not (.exists (io/file srcdir))
-    (with-open [stream (.openStream url)]
-      (io/copy stream (io/file zipfile)))
-    (unzip (io/file zipfile) "lib")))
+  [project]
+  (let [target (:target-dir project)]
+    (when-not (.exists (io/file target srcdir)) 
+      (let [zipped (io/file target zipfile)]
+        (with-open [stream (.openStream url)]
+          (io/copy stream (io/file zipped)))
+        (unzip (io/file zipped) (:target-dir project))))))
 
 (defn uninstall
   "Remove protoc if it is installed."
-  []
+  [project]
   (when (installed?)
     (let [password (read-pass)]
       (sh "sudo" "-S" "make" "uninstall"
-          :dir srcdir :in (str password "\n")))))
+          :dir (io/file (:target-dir project) srcdir)
+          :in (str password "\n")))))
 
 (defn install
   "Compile and install protoc to /usr/local."
-  []
+  [project]
   (when-not (installed?)
-    (fetch)
-    (when-not (.exists (io/file srcdir "src" "protoc"))
-      (.setExecutable (io/file srcdir "configure") true)
-      (.setExecutable (io/file srcdir "install-sh") true)
-      (println "Configuring protoc...")
-      (sh "./configure" :dir srcdir)
-      (println "Running 'make'...")
-      (sh "make" :dir srcdir))
-    (let [password (str (read-pass) "\n")
-          opts     {:dir srcdir :input-string (str password "\n")}]
-      (println "Installing...")
-      (if (= :linux (get-os))
-        (sh "script" "-q" "-c" "sudo -S make install" "/dev/null"
-            :dir srcdir :in password)
-        (sh "sudo" "-S" "make" "install"
-            :dir srcdir :in password)))))
+    (fetch project)
+    (let [source (io/file (:target-dir project) srcdir)]
+      (when-not (.exists (io/file source "src" "protoc"))
+        (.setExecutable (io/file source "configure") true)
+        (.setExecutable (io/file source "install-sh") true)
+        (println "Configuring protoc...")
+        (sh "./configure" :dir source)
+        (println "Running 'make'...")
+        (sh "make" :dir source))
+      (let [password (str (read-pass) "\n")
+            opts     {:dir source :input-string (str password "\n")}]
+        (println "Installing...")
+        (if (= :linux (get-os))
+          (sh "script" "-q" "-c" "sudo -S make install" "/dev/null"
+              :dir source :in password)
+          (sh "sudo" "-S" "make" "install"
+              :dir source :in password))))))
 
 (defn protoc
   "Create .java and .class files from the provided .proto files."
-  ([project protos] (protoc project protos "build/protosrc"))
+  ([project protos]
+     (protoc project protos (io/file (:target-dir project) "protosrc")))
   ([project protos dest]
-     (when (or (> (modtime "proto") (modtime dest))
-               (> (modtime "proto") (modtime "classes")))
-       (.mkdirs (io/file dest))
-       (.mkdir (io/file "build" "proto"))
-       (doseq [proto protos]
-         (println "Compiling" proto "to" dest)
-         (extract-dependencies (io/file "proto" proto))
-         (sh "protoc" proto (str "--java_out=../" dest) "-I." "-I../build/proto"
-             :dir "proto"))
-       (javac (assoc project :java-source-path dest)))))
+     (let [target (:target-dir project)
+           dest-path (.getPath dest)]
+       (when (or (> (modtime "proto") (modtime dest))
+                 (> (modtime "proto") (modtime "classes")))
+         (.mkdirs dest)
+         (.mkdir (io/file target "proto"))
+         (doseq [proto protos]
+           (println "Compiling" proto "to" dest-path)
+           (extract-dependencies (io/file "proto" proto) target)
+           (sh "protoc"
+               proto
+               (str "--java_out=../" dest-path)
+               "-I."
+               (str "-I../" target "/proto")
+               :dir "proto"))
+         (javac (assoc project :java-source-path dest-path))))))
 
 (defn compile-google-protobuf
   "Compile com.google.protobuf.*"
   [project]
-  (let [proto-files (io/file "proto/google/protobuf")]
+  (let [proto-files (io/file "proto/google/protobuf")
+        target (:target-dir project)]
     (.mkdirs proto-files)
-    (io/copy (io/file (str srcdir "/src/google/protobuf/descriptor.proto"))
-             (io/file proto-files "descriptor.proto")))
-  (protoc project
-          ["google/protobuf/descriptor.proto"]
-          (str srcdir "/java/src/main/java")))
+    (io/copy (io/file target srcdir "src/google/protobuf/descriptor.proto")
+             (io/file proto-files "descriptor.proto"))
+    (protoc project
+            ["google/protobuf/descriptor.proto"]
+            (io/file target srcdir "java/src/main/java"))))
 
 (defn compile
   "Compile protocol buffer files located in proto dir."
   ([project] (compile project (proto-files (io/file "proto"))))
   ([project files]
-     (install)
-     (fetch)
+     (install project)
+     (fetch project)
      (when (= "protobuf" (:name project))
        (compile-google-protobuf project))
      (protoc project files)))

  (:import java.util.zip.ZipFile))

(def version "2.3.0")
(def srcdir  (format "lib/protobuf-%s" version))
(def zipfile (format "lib/protobuf-%s.zip" version))
(def url     (java.net.URL.
              (format
               "http://protobuf.googlecode.com/files/protobuf-%s.zip"
               version)))

(defn- unzip [source target-dir]
  (let [zip (ZipFile. source)

      (.mkdirs (.getParentFile f))
      (io/copy (.getInputStream zip entry) f))))

(defn- proto-dependencies
  "look for lines starting with import in proto-file"
  [proto-file]
  (for [line (line-seq (io/reader proto-file)) :when (.startsWith line "import")]
    (second (re-matches #".*\"(.*)\".*" line))))

(defn extract-dependencies
  "extract all files proto is dependent on"
  [proto-file target]
  (loop [files (vec (proto-dependencies proto-file))]
    (when-not (empty? files)
      (let [proto (peek files)
            files (pop files)]
        (if (or (.exists (io/file "proto" proto))
                (.exists (io/file target "proto" proto)))
          (recur files)
          (let [location (str "proto/" proto)
                proto-file (io/file target location)]
            (io/copy (io/reader (io/resource location)) proto-file)
            (recur (into files (proto-dependencies proto-file)))))))))

(defn modtime [dir]
  (let [files (-> dir io/file file-seq rest)]
    (if (empty? files)
      0
      (apply max (map #(.lastModified %) files)))))
