 (ns mvxcvi.vault.tool
-  (:require [clojure.tools.cli :refer [cli]])
+  (:require [clojure.string :as string]
+            [clojure.tools.cli :refer [cli]])
   (:gen-class :main true))
 
 ;; Vault commands need to know:

               @new-opts
               subcommand-args)) ; NOTE: ignores unknown args before the subcommand
      (cond (:help opts) (do (println banner) (System/exit 0))
            (:action command) ((:action command) opts command-args)
            :else (do (println banner) (System/exit 1))))))


#_
(command "vault [global opts] <command> [command args]"
  "Command-line tool for the vault data store."

  ["--blob-store" "Path to blob-store configuration."
   :default "~/.config/vault/blob-store.edn"]
  ["-v" "--verbose" "Show extra debugging messages."
   :flag true :default false]
  ["-h" "--help" "Show usage information."
   :flag true :default false]

  (init [opts]
    (-> opts
        (assoc :blob-store (slurp (:blob-store opts)))))

  (command "blob [opts] <action> [args]"
    "Blob storage command."

    (command "list [opts]"
      "Enumerate the stored blobs."
      ; ... filtering/range options
      (action blob-tool/list-blobs))

    (command "info <blobref>"
      "Show information about a stored blob."
      (action blob-tool/blob-info))

    (command "get <blobref>"
      "Print the contents of a blob to stdout."
      (action blob-tool/get-blob))

    (command "put"
      "Store a blob of data and print the resulting blobref."
      (action blob-tool/put-blob))))



;; COMMAND LINE INTERFACE

(defn -main [& args]
  (let [[opts args banner]
        (cli
          args
          "Usage: vault [global opts] <system> <command> [command args]"
          ["--blob-store" "Path to blob-store configuration."
           :default "~/.config/vault/blob-store.edn"]
          ["-v" "--verbose" "Show extra debugging messages."
           :flag true :default false]
          ["-h" "--help"    "Show usage information."
           :flag true :default false])]
    (when (:help opts)
      (println banner)
      (System/exit 0))
    ;; TODO: command-line processing
    (println "Opts: " opts)
    (let [[system action & args] args]
      (println "System: " system)
      (println "Action: " action)
      (println "Args: " args))))

 :action (fn [opts args] ...)}


(def ^:private ^:dynamic *command-branch*
  "Vector containing the command names on the current branch."
  [])


(defmacro ^:private command
  "Simple macro for building readable command trees."
  [usage desc & more]
  (let [[cname usage] (string/split usage #" " 2)
        [specs more] (split-with vector? more)
        command {:name cname
                 :usage `(string/join " " (concat *command-branch* [~usage]))
                 :desc desc
                 :specs (vec specs)}
        elements (group-by first (filter list? more))]
    (when (> (count (elements 'init)) 1)
      (throw (IllegalArgumentException.
               (str "Multiple `init` elements in command definition: " (elements 'init)))))
    (when (> (count (elements 'action)) 1)
      (throw (IllegalArgumentException.
               (str "Multiple `action` elements in command definition: " (elements 'action)))))
    (when (and (elements 'command) (elements 'action))
      (throw (IllegalArgumentException.
               (str "Command definition contains both `command` and `action` elements: " more))))
    (let [assoc-fn (fn [cmd [k & body]]
                     (if k
                       (if (symbol? (first body))
                         (assoc cmd (keyword k) (first body))
                         (assoc cmd (keyword k) `(fn ~@body)))
                       cmd))
          command (reduce assoc-fn command
                          [(first (elements 'init))
                           (first (elements 'action))])]
      (if-let [subcommands (elements 'command)]
        `(binding [*command-branch* (conj *command-branch* ~(:name command))]
           (->>
             ~(vec subcommands)
             (map (juxt :name identity))
             (into {})
             (assoc ~command :commands)))
        `(binding [*command-branch* (conj *command-branch* ~(:name command))]
           ~command)))))


(defn- handle-command
  "Handles a sequence of arguments following a command tree structure."
  [command opts args]
  (let [subcommand-names (into #{} (cons "help" (keys (:commands command))))

