(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]))

(defn- impl! [thought & [parent]]
  (let [impl-idea (prop/just-property [:thought-impl (thought/operator thought)])]
    (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))))

(def root-thought (thought/make-thought :root))

;; at what point do returns get cleared?
(defn execute!
  "Execute `thought` with `parent`, applying aspects to `thought` according to its :extension-stages.
  Returns (potentially modified) `parent`."
  [thought & [parent]]
  (loop [th (prtc/copy thought)
         parent (or parent root-thought)]
    (let [cur (first (thought/stages thought))
          [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 return]
          (if (= cur [:thought (thought/operator th)])
            (impl! th parent)
            parent)

          ;; Fold return value into `parent`.
          parent (return/with-return parent (thought/operator th) return)]

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