(ns emptyhead.idea.property
  "Implements 'properties' - hierarchical tags for ideas."
  (:require [clojure.set :as stdset]
            [emptyhead.idea.protocol :as prtc]
            [emptyhead.idea.state :as state]))

(defn properties
  "Returns a set of all properties associated with `idea`."
  [idea]
  (prtc/val-fn #(get-in % [:_meta :_properties]) idea))

(defn with-property
  "Returns a set of all ideas with `property`."
  [property]
  (get-in @state/state (concat [:_properties] property [:_node])))

;; XXX should error if multiple are found
(defn just-property
  "Returns the single idea with `property`."
  [property]
  (first (with-property property)))

(defn has-property?
  "Returns true if and only if `property` is associated with `idea`."
  [idea property]
  (contains? (properties idea) property))

(defn register-property!
  "Associate one or more `properties` to an `idea`."
  [idea & properties]
  (let [property (first properties)
        tail (rest properties)
        iref (prtc/reference idea)]
    (when property
      (reduce
       (fn [acc property]
         (swap! state/state update-in (conj acc property :_node) (fnil conj #{}) iref)
         (swap! state/state update-in [iref :_meta :_properties] (fnil conj #{}) (conj (vec (rest acc)) property))
         (conj acc property))
       [:_properties] property))
    (when tail
      (run! #(register-property! idea %) tail))
    iref))

(defn- remove-property-node! [idea property]
  (swap! state/state update-in
         (concat [:_properties] property [:_node])
         disj (prtc/reference idea))
  (prtc/reference idea))

(defn- child-properties [property]
  (map #(conj property %)
       (-> @state/state :_properties (get-in property)
           keys set (disj :_node))))

(defn- rm-prop! [idea property]
  (swap! state/state update-in [idea :_meta :_properties] disj property)
  (remove-property-node! idea property)
  (let [children (stdset/intersection
                  (set (child-properties property))
                  (properties idea))]
    (run! #(rm-prop! idea %) children)))

(defn remove-property!
  "Dissociate one or more `properties` from `idea`."
  [idea & properties]
  (run! #(prtc/ref-fn rm-prop! idea %) properties)
  (prtc/reference idea))
