(ns buddho.core.state)

(def empty-state
  {:_properties {}})

(defonce state
  (atom empty-state))

(defn reset-state! []
  (reset! state empty-state))

(defprotocol Idea
  (fn-liberal [idea fn]
    "Apply function `fn` to `idea`,
accepting both identifiers and values for the latter;
mind that keys are functions.")
  (reference [idea] "Reference, i.e. symbol, for `idea`.")
  (value [idea] "Value, i.e. map, of `idea`."))

(extend-protocol Idea
  cljs.core/PersistentHashMap
  (fn-liberal [dhamma fn]
    (fn dhamma))
  (reference [idea] (:_reference idea))
  (value [idea] idea)

  cljs.core/PersistentArrayMap
  (fn-liberal [dhamma fn]
    (fn dhamma))
  (reference [idea] (:_reference idea))
  (value [idea] idea)

  Symbol
  (fn-liberal [idea fn]
    (fn-liberal (value idea) fn))
  (reference [idea] idea)
  (value [idea] (get @state idea)))

(defn apply-fn [fn idea] (fn-liberal idea fn))

(defn register-idea! [iid idea]
  (swap! state assoc iid idea)
  (swap! state assoc-in [iid :_properties] #{})
  (swap! state assoc-in [iid :_reference] iid)
  iid)

(defn have-idea! [& [prefix]]
  (register-idea! (gensym (or prefix "idea_")) {}))

(defn register-property! [idea property]
  (when property
    (let [iid (reference idea)]
      (reduce (fn [acc property]
                (swap! state update-in (conj acc property :_node) (fnil conj #{}) iid)
                (swap! state update-in [iid :_properties] (fnil conj #{}) (conj (vec (rest acc)) property))
                (conj acc property))
              [:_properties] property))))

(defn properties [idea]
  (:_properties (value idea)))

(defn with-property [property]
  (get-in @state (concat [:_properties] property [:_node])))

(defn has-property? [idea property]
  (contains? (properties idea) property))

(defn get-element [idea element]
  (get (value idea) element))
