(ns debian.dh-clojure-lein.client
  "Support for dh-clojure-lein.clj.  See dh-clojure-lein(7)."
  (:require
   [debian.dh-clojure-lein.util
    :refer [abort dep-map-key dep-vecs->map normalize-dep]]
   [leiningen.core.main :as main]
   [leiningen.core.project :as proj]))

(defn- spec->map-key [spec]
  (-> spec normalize-dep proj/dependency-map dep-map-key))

(defn get-dep
  ([dep-map spec] (get-dep dep-map spec nil))
  ([dep-map spec default] (get dep-map (spec->map-key spec) default)))

(defn- validate-dep-vec [x dep-key? where]
  (letfn [(validate-dep-opts [opts]
            (when (odd? (count opts))
              (abort where " has odd number of dependency options: " (pr-str x) "\n"))
            (when-not (every? keyword? (take-nth 2 opts))
              (abort where " has non-keyword option names: " (pr-str x) "\n"))
            (let [m (apply hash-map opts)]
              (when-not (= (count m) (/ (count opts) 2))
                (abort where " has duplicate dependency options " (pr-str x) "\n"))
              (doseq [[k v] m]
                (case k
                  (:classifier :extension)
                  (do
                    (when (nil? v)
                      (abort where " has nil " (pr-str k) " in " (pr-str x) "\n"))
                    (when-not (string? v)
                      (abort where (pr-str k) " value not a string: " (pr-str x) "\n")))
                  :exclusions
                  (do
                    (when dep-key?
                      (abort where (pr-str x) " cannot have :exclusions\n"))
                    (when-not (sequential? v)
                      (abort where " :exclusions not an artifact list: " (pr-str x) "\n"))
                    (-> #(validate-dep-vec %2 true (str "exclusion " (inc %1) " in " where "\n"))
                        (map-indexed v)
                        dorun))
                  (abort where "has invalid option " (pr-str k) " in " (pr-str x) "\n")))))]
    (or (symbol? x)
        (do
          (when-not (vector? x) (abort where " is not a vector: " (pr-str x) "\n"))
          (when (empty? x) (abort where " is an empty vector") "\n")
          (when-not (-> x first symbol?)
            (abort where " artifact id isn't a symbol") "\n")
          (when-let [[maybe-ver & others :as opts] (-> x rest seq)]
            (if dep-key?
              (validate-dep-opts opts)
              (if (string? maybe-ver)
                (validate-dep-opts others)
                (validate-dep-opts opts))))))))

(defn- validate-dep-keys [x where]
  (when-not (vector? x) (abort where " value is not a vector: " (pr-str x) "\n"))
  (when (empty? x) (abort where " is an empty vector" "\n"))
  (dorun (map-indexed #(validate-dep-vec %2 true (str where "item" (inc %1))) x)))

(defn active-profiles [p] (-> p meta :active-profiles))

(defn add-dep [m dep-vec]
  (validate-dep-vec dep-vec false "add-dep dependency")
  (let [dm (-> dep-vec normalize-dep proj/dependency-map)
        dk (dep-map-key dm)]
    (when-not (:version dm)
      (abort "add-dep dependency has no version: " (pr-str dep-vec) "\n"))
    (when (contains? m dk)
      (abort "add-dep applied to existing dependency " (-> dep-vec first pr-str) "\n"))
    (assoc m dk dm)))

(defn del-dep [m dep-key]
  (validate-dep-vec dep-key true "del-dep dependency")
  (let [k (-> dep-key normalize-dep proj/dep-key)]
    (when-not (contains? m k)
      (abort "del-dep applied to missing dependency " (pr-str dep-key) "\n"))
    (dissoc m k)))

(defn set-dep [deps dep-key field value]
  (validate-dep-vec dep-key true "set-dep dependency")
  (let [dm (-> dep-key normalize-dep proj/dependency-map)
        dk (dep-map-key dm)]
    (when-not (contains? deps dk)
      (abort "set-dep applied to missing dependency " (pr-str dep-key) "\n"))
    (case field
      :version
      (do
        (when-not (string? value)
          (abort "set-dep :version must be a string\n"))
        (assoc-in deps [dk :version] value))

      :exclusions
      (if (nil? value)
        (update deps dk dissoc :exclusions)
        (do
          (validate-dep-keys value "set-dep :exclusions")
          (assoc-in deps [dk :exclusions]
                    (mapv #(-> % normalize-dep proj/dependency-map dep-map-key)
                          value))))

      (:classifier :extension)
      (abort "set-dep cannot change discriminator " field
             " (del-dep and add-dep instead)\n")

      (abort "unrecognized set-dep field " field "\n"))))
