(ns emptyhead.idea.protocol
  "Implements transparent conversions between the _value_ of an idea, i.e. a map containing its data,
  and the _reference_ of an idea, i.e. a symbol that identifies it in the state;
  i.e., state looks like {reference_1 value_1 ...}"
  (:require [emptyhead.util.logging :as log]
            [emptyhead.idea.state :refer [state]]))

(defn- to-reference [val]
  (let [ref (get-in val [:_meta :_reference])]
    (cond
      (get-in val [:_meta :_stale-reference])
      (do
        (println "fucky wucky " val)
        (log/error (str "Attempt to find stale reference `" ref "` -- this is a copy.")
                   {:value val :type :stale-reference}))

      (not (symbol? ref))
      (log/error (str "Attempt to find invalid reference `" ref "` --  invalid idea?")
                 {:value val :type :invalid-reference})

      :else ref)))

(defprotocol Idea
  (reference [idea] "Reference, i.e. symbol, for `idea`.")
  (value [idea] "Value, i.e. map, of `idea`."))

(extend-protocol Idea
  cljs.core/PersistentHashMap
  (reference [idea] (to-reference idea))
  (value [idea] idea)

  cljs.core/PersistentArrayMap
  (reference [idea] (to-reference idea))
  (value [idea] idea)

  Symbol
  (reference [idea] idea)
  (value [idea] (get @state idea)))

(defn copy
  "Make a copy of `idea`.
  Returns an object that is identical to the value of `idea`,
  but marked as not containing a reference to anything in the state."
  [idea]
  (assoc-in (value idea) [:_meta :_stale-reference] true))

(defn non-copy [idea] (assoc-in (value idea) [:_meta :_stale-reference] false))

(defn uncopy!
  "Takes a copied idea and 'uncopies' it, making its reference active again
  and updating what is in the game state."
  [copy-obj]
  (let [idea (non-copy copy-obj)]
    (swap! state assoc (reference idea) idea)
    (reference idea)))

(defn force-reference
  "Get the (now stale!) reference of a copied idea."
  [copy-obj]
  (to-reference (non-copy copy-obj)))

(defn copy-fn
  "Execute `fun` on a copy of `idea` with optional additional `args`."
  [fun idea & args]
  (apply fun (copy idea) args))

(defn val-fn
  "Execute `fun` on the value of `idea` with optional additional `args`."
  [fun idea & args]
  (apply fun (value idea) args))

(defn ref-fn
  "Execute `fun` on a reference to `idea` with optional additional `args`."
  [fun idea & args]
  (apply fun (reference idea) args))

(defn force-reference-fn
  "Execute `fun` on a reference to `idea` with optional additional `args`.
  Unlike [[reference-fn]], this will work on a copy."
  [fun idea & args]
  (apply fun (force-reference idea) args))
