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

(defn is-js-num? [n]
  (let [i (js/parseInt n)
        f (js/parseFloat n)]
    (or (not (js/isNaN i)) (not (js/isNaN f)))))

(defn is-js-int? [n]
  (and (is-js-num? n) (= (js/parseInt n) (js/parseFloat n))))

(defn is-js-float? [n]
  (and (is-js-num? n) (not (is-js-int? n))))

(defn js-float [n]
  (if (is-js-float? n)
    (js/parseFloat n)
    nil))

(defn js-int [n]
  (if (is-js-int? n)
    (js/parseInt n)
    nil))

(defn wz-marshal [v & [numify?]]
  (let [wz-del? (fn [s d1 & [d2]]
                 (and 
                  (string? s)
                  (clojure.string/starts-with? s d1)
                  (clojure.string/ends-with? s (or d2 d1))))
        wz-fun? (fn [k] (wz-del? k "(" ")"))
        wz-list? (fn [k] (wz-del? k "[" "]"))
        wz-map? (fn [k] (wz-del? k "{" "}"))
        wz-str? (fn [k] (wz-del? k "\""))
        wz-num? (fn [k] (or (number? k) (is-js-num? k)))
        v (if (and (is-js-num? v) numify?) (or (js-int v) (js-float v)) v)]
    (cond
     (wz-str? v) (clojure.string/join "\\n" (clojure.string/split-lines v))
     (or (wz-fun? v) (wz-list? v) (wz-map? v)) v
     (string? v) (str "\"" v "\"")
     (or (wz-num? v) (boolean? v)) v
     :else (str v))))

(defn wz-marshal-list [list & [deep?]]
  (let [list (if deep? (map wz-marshal list) list)]
    (str "[" (clojure.string/join " " list) "]")))

(defn wz-marshal-map
  ([m]
   "Gets a clojure map and marshals it into a whizzml map"
   (wz-marshal-map
    (map wz-marshal (keys m))
    (wz-marshal-list (vals m))))
  ([k v]
   "Marshals the given arguments into a whizzml map. v is always a
   marshalled object, for example a whizzml list, or it can be a
   whizzml symbol (e.g., the name of a whizzml list). k is a
   vector. marshalling takes place using whizzml primitive
   make-map. if k contains just one element, the marshalled value v
   may represent either a scalar value or a marshalled list of just
   one element. in the former case, the values is wrappred in brackets
   so it looks like a 1-element list as well."
   (let [n (count k)
         make-map #(str "(make-map " (wz-marshal-list %1 true) " " %2 ")")]
     (cond
      (> n 1) (make-map k v)
      (= n 1) (make-map k (if (clojure.string/starts-with? v "[")
                            v (str "[" v "]")))
      :else "{}"))))

(defn wz-marshal-string [s]
  (str "\"" s "\""))
