(ns emptyhead.idea.crud
  "Functions for Creating, Updating and Deleting ideas.
  The 'R' in 'CRUD' is implemented by [[emptyhead.idea.protocol/value]]."
  (:require [emptyhead.idea.state :refer [state]]
            [emptyhead.idea.property :as prop]
            [emptyhead.idea.protocol :as prtc]
            [emptyhead.idea.memtag :as memtag]
            [clojure.set :as set]))

(defn- register-idea!
  "Helper function to scaffold an 'empty' idea."
  [ref]
  (swap! state assoc-in [ref :_meta :_properties] #{})
  (swap! state assoc-in [ref :_meta :_reference] ref)
  ref)

(defn swap-idea!
  "Swap data inside `idea` with given `data`.
  Returns a reference to `idea`."
  [idea data]
  (swap! state assoc
         (prtc/reference idea)
         (merge data {:_meta (prtc/val-fn :_meta idea)}))
  (prtc/reference idea))

(defn extend-idea!
  "Merge `data` into state of `idea`.
  Returns a reference to `idea`."
  [idea data]
  (swap! state assoc (prtc/reference idea) (prtc/val-fn merge idea data))
  (prtc/reference idea))

(defn mutate-idea!
  "Evaluate `fun` on `idea` with optional extra `args`, then replace `idea` by the result.
  Returns a reference to `idea`."
  [fun idea & args]
  (swap-idea! idea (prtc/val-fn fun idea args))
  (prtc/reference idea))

(defn forget-idea!
  "Delete `idea` from the state.
  Returns a copy of the `idea`."
  [idea]
  (let [val (prtc/value idea)]
    (apply prop/remove-property! idea (prop/properties idea))
    (prtc/ref-fn #(swap! state dissoc %) idea)
    (prtc/copy val)))

(defn have-idea!
  "Instantiate up to `count` new ideas, optionally prefixing reference symbol with `prefix`.
  Additionally allows you to immediately attach `properties` and `data`.
  Returns a single idea or a list of ideas depending on whether `count` was given."
  [& {:keys [prefix count properties data shadowing]
      :or {count 1 prefix "idea" properties [] shadowing []}}]
  (let [fun #(register-idea! (gensym (str prefix "$")))
        ideas (take count (repeatedly fun))
        shadowing (apply set/union (map prop/with-property shadowing))]
    (run! forget-idea! shadowing)
    (run! #(apply prop/register-property! % properties) ideas)
    (run! #(prop/register-property! % (memtag/uid-of %)) ideas)
    (when data (run! #(extend-idea! % data) ideas))
    (if (= count 1)
      (first ideas)
      ideas)))
