(ns bigml-nodered-core.wz.whizzml
  (:require clojure.string
            [bigml-nodered-core.wz.marshal :as marshal]
            [bigml-nodered-core.wz.units :as units]))

(defn- wz-let [inputs]
  (reduce (fn [m [k v]] (str m (str k " " v " "))) "" inputs))

(defn- wz-output [step]
  (:name step))

(defn- wz-expand [step]
  "Expands step into a tuple [wz-var wz-code]"
  (let [lets (wz-let (conj (:lets step) ["result" (:fun step)]))
        ins (:in-pars step)
        outs (:out-pars step)
        outs (if (nil? outs) "result " (marshal/wz-marshal-map outs "result"))
        outs (units/wz-merge "r" outs)]
    [lets outs]))

(defn- wz-collect-enclose
  [list & {:keys [force delim1 delim2 sep]
           :or {force false, delim1 "[", delim2 "]", sep " "}}]
  (if (= (count list) 0)
    (if (= force true) (str delim1 delim2) "")
    (if (= (count list) 1)
      (if (= force true) (str delim1 (first list) delim2) (first list))
      (str delim1 (clojure.string/join sep list) delim2))))

(defn- wz-collect-let [lets body]
  "Builds a (let [lets] body) expression given lets and body, where lets is a vector
of [name expr] tuples and body is an expression."
  (str (wz-collect-enclose
        [lets]
        :delim1 "(let ("
        :delim2 ") "
        :force true)
       body ")"))

(defn- wz-collect [name exprs]
  "Collects a list of expressions into a name function, with the last
expression used as the function's body."
  (let [lets (first exprs)
        body (last exprs)]
    (units/wz-defn name
             (wz-collect-enclose ["r"] :delim1 "(" :delim2 ")")
             (wz-collect-let lets body))))

(defn- wz-mangle [name]
  (clojure.string/lower-case (clojure.string/replace name " " "-")))

(defn- wz-step [step]
  "Makes a step into a function"
  (wz-collect (wz-mangle (:name step))
              (wz-expand step)))

(defn- wz-run-step [m step]
  (let [input (first (last m))
        wz-var-name #(str (wz-mangle %1) "-out")]
    (conj m [(wz-var-name (:name step))
             (str "(" (wz-mangle (:name step)) " " input ")")])))

(defn whizzml [stack msg]
  (let [stack (reverse stack)
        init (js->clj (or (.-payload msg) msg))]
    (clojure.string/join
     "\n"
     (concat
      (map (fn [step] (wz-step step)) stack)
      (map (fn [def] (units/wz-def (first def) (last def)))
           (reduce (fn [acc step] (wz-run-step acc step))
                   [["init" init]]
                   stack))))))
