(ns particles.core)

(def display (.getElementById js/document "canvas-1"))
(def context (.getContext display "2d"))
(def damping 0.999)
(def max-particles 250)
(def line-width 2)

(def app-state (atom {:width (.-innerWidth js/window)
                      :height (.-innerHeight js/window)}))

(defn init-canvas 
  []
  (let [{:keys [width height]} @app-state]
    (do
      (set! (.-width display) width))
    (set! (.-height display) height)))

(defn rand-rgb
  []
  (let [r (rand-int 255)
        g (rand-int 255)
        b (rand-int 255)]
    (str "rgb(" r "," g "," b ")")))

(defn create-particle
  []
  (let [width (:width @app-state)
        height (:height @app-state)
        x (rand width)
        y (rand height)
        color (rand-rgb)]
    {:x x, :y y, :old-x x, :old-y y, :color color}))

(swap! app-state assoc :mouse {:x (* (:width @app-state) 0.5) 
                               :y (* (:height @app-state) 0.5)})
(swap! app-state assoc :particles (vec (map create-particle (range max-particles))))


(defn integrate
  [{:keys [x y old-x old-y] :as particle}]
  (let [velocity-x (* (- x old-x) damping)
        velocity-y (* (- y old-y) damping)]
    (assoc particle :x (+ x velocity-x)
      :y (+ y velocity-y)
      :old-x x
      :old-y y)))

(defn attract
  [pos-x pos-y {:keys [x y] :as particle}]
  (let [dx (- pos-x x)
        dy (- pos-y y)
        distance (.sqrt js/Math (+ (* dx dx) (* dy dy)))
        new-x (+ x (/ dx distance))
        new-y (+ y (/ dy distance))]
    (assoc particle :x new-x :y new-y)))

(defn draw
  [ctx {:keys [x y old-x old-y color]}]
  (do
    (set! (.-strokeStyle ctx) color)
    (set! (.-lineWidth ctx) line-width)
    (.beginPath ctx)
    (.moveTo ctx old-x old-y)
    (.lineTo ctx x y)
    (.stroke ctx)))


(defn render
  []
  (.requestAnimationFrame js/window render)
  (let [{:keys [width height particles mouse]} @app-state]
    (.clearRect context 0 0 width height)
    (dotimes [n max-particles]
      (let [particle (nth particles n)
            x (:x mouse)
            y (:y mouse)
            p (integrate (attract x y particle))]
        (draw context p)
        (swap! app-state assoc-in [:particles n] p)))))

(defn mousemove-handler
  [event]
  (swap! app-state assoc :mouse {:x (.-clientX event)
                                 :y (.-clientY event)}))

(defn windowresize-handler
  [event]
  (let [w (.-innerWidth js/window)
        h (.-innerHeight js/window)]
    (do
      (swap! app-state assoc :width w)
      (swap! app-state assoc :height h)
      (init-canvas))))

(.addEventListener display "mousemove" mousemove-handler)
(.addEventListener js/window "resize" windowresize-handler)

(init-canvas)
(render)