(ns buddho.core.dhamma
  (:require [buddho.core.state :as s :refer [value reference state]]
            [clojure.set :refer [union]]))

(defn make-dhamma
  "Helper function to make dhamma object. You usually want `register-dhamma!` instead."
  [name source data asp-props consequences is-aspect-of? modify-whole priority]
  (hash-map :name name
            :source source
            :data data
            :asp-props asp-props
            :consequences consequences
            :is-aspect-of? is-aspect-of?
            :modify-whole modify-whole
            :priority priority))

(defn register-dhamma!
  "Register a dhamma."
  [name source & {:keys [data consequences is-aspect-of? modify-whole asp-props priority]
                  :or {data {} consequences [] is-aspect-of? (constantly true)
                       modify-whole identity asp-props [] priority 0}}]
  (let [id (gensym "dhamma_")
        dhamma (make-dhamma
                name source data (conj asp-props [name])
                consequences is-aspect-of? modify-whole priority)]
    (s/register-idea! id dhamma)))

(defn get-data [dhamma key] (get-in dhamma [:data key]))
(defn assoc-data [dhamma key value] (assoc-in dhamma [:data key] value))

(defn register-aspect! [did asp-prop]
  (s/register-property! did [:_aspect_of asp-prop]))

(defn get-aspects [asp-prop]
  (loop [as asp-prop
         acc []]
    (if (empty? as)
      acc
      (recur (butlast as)
             (union acc (s/with-property [:_aspect_of as]))))))

(defmulti is-aspect-of? (fn [dhamma _whole] (s/apply-fn :name dhamma)))
(defmethod is-aspect-of? :default [dhamma whole]
  (s/apply-fn (s/apply-fn :is-aspect-of? dhamma) whole))

(defmulti modify-whole (fn [dhamma _whole] (s/apply-fn :name dhamma)))
(defmethod modify-whole :default [dhamma whole]
  (s/apply-fn (s/apply-fn :modify-whole dhamma) whole))

(defn aspects-of [dhamma]
  (->> dhamma
       value
       :asp-props
       (mapcat get-aspects)
       (filter #(is-aspect-of? % dhamma))
       set
       (sort-by :priority)))

;; TODO better error handling
(defn copy [dhamma] (assoc (value dhamma) :_reference nil))

(defmulti impl! (fn [dhamma & [_whole]] (:name dhamma)))

(defn occur! [dhamma & [whole]]
  (let [processed (reduce #(occur! %2 %1) (copy dhamma) (aspects-of dhamma))]
    (run! occur! (:consequences processed))

    (impl! (value processed) (and whole (value whole)))

    (when whole
      (modify-whole processed whole))))

(defmethod impl! :default [_dhamma & [_whole]]
  (println "default occur"))

(defmethod impl! :print [_dhamma & [whole]]
  (println "printing test!")
  (println (s/apply-fn :data whole)))
