(ns auto-ap.cursor
  (:import (clojure.lang IDeref Atom ILookup Counted IFn AFn Indexed ISeq Seqable)))

(defprotocol ICursor
  (path [cursor])
  (state [cursor]))


(defprotocol ITransact
  (-transact! [cursor f]))


(declare to-cursor cursor?)


(deftype ValCursor [value state path]
  IDeref
  (deref [_]
    (get-in @state path value))
  ICursor
  (path [_] path)
  (state [_] state)
  ITransact
  (-transact! [_ f]
    (get-in
      (swap! state (if (empty? path) f #(update-in % path f)))
      path)))


(deftype MapCursor [value state path]
  Counted
  (count [_]
    (count (get-in @state path value)))
  ICursor
  (path [_] path)
  (state [_] state)
  IDeref
  (deref [_]
    (get-in @state path value))
  IFn
  (invoke [this key]
    (get this key))
  (invoke [this key defval]
    (get this key defval))
  (applyTo [this args]
    (AFn/applyToHelper this args))
  ILookup
  (valAt [obj key]
    (.valAt obj key nil))
  (valAt [_ key defv]
    (let [value (get-in @state path value)]
      (to-cursor (get value key defv) state (conj path key) defv)))
  ITransact
  (-transact! [cursor f]
    (get-in
      (swap! state (if (empty? path) f #(update-in % path f)))
      path))
  Seqable
  (seq [this]
    (for [[k v] @this]
      [k (to-cursor v state (conj path k) nil)])))


(deftype VecCursor [value state path]
  Counted
  (count [_]
    (count (get-in @state path)))
  ICursor
  (path [_] path)
  (state [_] state)
  IDeref
  (deref [_]
    (get-in @state path))
  IFn
  (invoke [this i]
    (nth this i))
  (applyTo [this args]
    (AFn/applyToHelper this args))
  ILookup
  (valAt [this i]
    (nth this i))
  (valAt [this i not-found]
    (nth this i not-found))
  Indexed
  (nth [_ i]
    (let [value (get-in @state path value)]
      (to-cursor (nth value i) state (conj path i) nil)))
  (nth [_ i not-found]
    (let [value (get-in @state path value)]
      (to-cursor (nth value i not-found) state (conj path i) not-found)))
  ITransact
  (-transact! [cursor f]
    (get-in
      (swap! state (if (empty? path) f #(update-in % path f)))
      path))
  Seqable
  (seq [this]
    (for [[v i] (map vector @this (range))]
      (to-cursor v state (conj path i) nil))))


(defn- to-cursor
  ([v state path value]
   (cond
     (cursor? v) v
     (map? v) (MapCursor. value state path)
     (vector? v) (VecCursor. value state path)
     :else (ValCursor. value state path)
     )))


(defn cursor? [c]
  "Returns true if c is a cursor."
  (satisfies? ICursor c))


(defn cursor [v]
  "Creates cursor from supplied value v. If v is an ordinary
   data structure, it is wrapped into atom. If v is an atom,
   it is used directly, so all changes by cursor modification
   functions are reflected in supplied atom reference."
  (to-cursor (if (instance? Atom v) @v v)
             (if (instance? Atom v) v (atom v))
             [] nil))


(defn synthetic-cursor [v prefix]
  (let [internal-cursor (cursor v)]
    (reify ICursor
      (path [this]
        (into prefix (path internal-cursor)))
      (state [this]
        (state internal-cursor)))))


(defn transact! [cursor f]
  "Changes value beneath cursor by passing it to a single-argument
   function f. Old value will be passed as function argument. Function
   result will be the new value."
  (-transact! cursor f))


(defn update! [cursor v]
  "Replaces value supplied by cursor with value v."
  (-transact! cursor (constantly v)))

(defn ensure-path! [cursor p default]
  (let [next-to-last (get-in cursor (butlast p))
        next-to-last-v @next-to-last]
    (when (not (get next-to-last-v (last p)))
      (transact! next-to-last #(assoc % (last p) default))))
  cursor)
