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

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

(defn remove-aspect! [dhamma asp-prop]
  (s/remove-property! (reference dhamma) [:_aspect_of asp-prop]))

(defn make-dhamma
  "Helper function to make dhamma object. You may want `register-dhamma!` instead."
  [name & {:keys [data is-aspect-of? cascade asp-props transient]
           :or {data {} is-aspect-of? (constantly true)
                cascade [] asp-props [[name] [:dhamma]]
                transient true}}]
  (hash-map :name name
            :data data
            :asp-props asp-props
            :is-aspect-of? is-aspect-of?
            :cascade cascade
            :transient transient))

(defn register-dhamma!
  "Register a dhamma."
  [name & {:keys [data is-aspect-of? cascade asp-props transient]
           :as args}]
  (let [idea (s/have-idea! "dhamma_")
        dhamma (make-dhamma
                name
                args)]
    (s/update-idea! idea dhamma)
    (s/register-property! idea [:dhamma])))

(defn remove-dhamma! [dhamma]
  (s/forget-idea! dhamma))

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

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

(defn add-cascade [dhamma & cascades]
  (update-in (copy (value dhamma)) [:cascade]
             #(into [] (concat % cascades))))

(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? (value dhamma)) (value whole)))

(defn aspects-of [dhamma]
  (let [asp-props (->> dhamma value :asp-props)
        filter-fn #(is-aspect-of? % dhamma)
        aspects (map
                 #(->> % get-aspects (filter filter-fn))
                 asp-props)]
    (zipmap asp-props aspects)))

(defmulti impl! (fn [dhamma & [_whole]] (:name (value dhamma))))
;; TODO should be an error
(defmethod impl! :default [_dhamma & [_whole]]
  (println "default occur"))

(defn occur! [dhamma & [whole]]
  (let [processed (reduce #(occur! %2 %1)
                          (copy dhamma)
                          (flatten (vals (aspects-of dhamma))))
        processed (impl! (value processed) (when whole (value whole)))]
    (run! #(occur! %1 processed) (:cascade processed))
    (when-not (:transient processed)
      (remove-dhamma! dhamma))
    processed))
