(ns buddho.core.dhamma.data
  "Facilities for creating and manipulating dhamma objects."
  (:require [buddho.core.state :refer [value copy]]))

(defn make-dhamma
  "Helper function to make dhamma object. You may want `register-dhamma!` instead."
  [name & {:keys [data meta is-aspect-of? asp-props transient]
           :or {data {} meta {} is-aspect-of? (constantly true)
                asp-props [[:dhamma name]]
                transient true}}]
  (hash-map :name name
            :data data
            :meta meta
            :asp-props asp-props
            :is-aspect-of? is-aspect-of?
            ;; XXX idk if necessary but just to be sure
            :transient (not (false? transient))))

(defn get-field
  [field]
  (fn [dhamma & keys]
    (get-in (and dhamma (value dhamma))
            (vec (concat [field] keys)))))

(defn- vecify [ks] (vec (flatten [ks])))

(defn assoc-field [field]
  (fn [dhamma keys value]
    (let [k (concat [field] (vecify keys))]
      (assoc-in (copy dhamma) k value))))

(def ^{:arglists '([dhamma & keys])}
  get-data
  "Get value given by `keys` in :data field of `dhamma`."
  (get-field :data))

(def ^{:arglists '([dhamma & keys])}
  get-meta
  "Get value given by `keys` in :data field of `dhamma`."
  (get-field :meta))

(def ^{:arglists '([dhamma keys value])}
  assoc-data
  "Associate one or more fields given by `keys` in :data field to `value` in copy of `dhamma`."
  (assoc-field :data))

(def ^{:arglists '([dhamma keys value])}
  assoc-meta
  "Associate one or more fields given by `keys` in :meta field to `value` in copy of `dhamma`."
  (assoc-field :meta))

(defn extract-field
  "Extract metadata given in `defaults` from dhamma in `options` within `namespace`.
  Returns first non-nil value for each."
  [field]
  (fn [options namespace defaults]
    (let [fun (or namespace identity)]
    (->> (reverse options)
         (map #(-> % value field fun))
         (apply merge-with #(or %2 %1) (or defaults {}))))))

(defn extract-meta
  "Extract _metadata_ given in `defaults` from dhamma in `options` within `namespace`.
  Returns first non-nil value for each."
  [options & [defaults namespace]]
  ((extract-field :meta) options namespace defaults))

(defn extract-data
  "Extract _data_ given in `defaults` from dhamma in `options` within `namespace`.
  Returns first non-nil value for each."
  [options & [defaults namespace]]
  ((extract-field :data) options namespace defaults))

(defn mutate
  "Mutate a copy of `dhamma` using `fun`."
  [dhamma fun]
  (fun (copy dhamma)))
