(ns emptyhead.thought.eval
  "Implements evaluation of thoughts."
  (:require [emptyhead.idea.protocol :as prtc]
            [emptyhead.thought.extend :as extend]
            [emptyhead.thought.crud :as thought]
            [emptyhead.util.logging :as logging]
            [emptyhead.thought.return :as return]
            [emptyhead.idea.property :as prop]
            [emptyhead.idea.crud :as idea]
            [emptyhead.util.magic :as magic]))

(defn- impl! [thought & [parent]]
  (let [impl-idea
        (-> thought prtc/value thought/operator magic/thought-impl-prop prop/just-property)]
    (if-not impl-idea
      (logging/error (str "No implementation for thought `" (thought/operator thought) "`.")
                     {:thought thought :parent parent :type :unimplemented-thought})
      ((prtc/copy-fn :implementation impl-idea) thought parent))))



  ;; FIXME I don't think omitting the parent here is actually valid?
  ;; might need to use thought.crud/root-thought, but better making parent mandatory tabun
(defn execute!
  "Execute `thought` with `parent`, applying aspects to `thought` according to its :extension-stages.
  Returns (potentially modified) `parent`."
  [thought & [parent]]

  ;; FIXME parent may change mid execution, breaking deep inspection
  (loop [th (assoc (prtc/value thought) :_parent (prtc/value parent))
         parent parent]
    (let [cur (first (thought/stages th))
          [extensions th] (extend/pop-stage th)

          ;; Execute extensions, potentially modifying th
          th (reduce #(execute! %2 %1) th extensions)

          ;; If it's time for `thought`'s implementation to run, do so,
          ;; potentially modifying `parent`.
          [parent & returns]
          (if (= '() (thought/stages th)) ;; NOTE the magic value is now [:EXECUTE] since otherwise nothing could be bound to the global [:emptyhead] propspace
            (impl! th parent)
            [parent nil])

          ;; Fold return value into `parent`.
          parent (return/with-return parent returns)]

      ;; Recur if there's remaining aspects, otherwise return `parent`.
      (if (not-empty (thought/stages th))
        (recur th parent)
        parent))))
