(ns sekai.io.display
  (:require [sekai.core.gamestate :refer [game-state]]
            [sekai.entity.selector :as s]
            [sekai.entity.entity :as entity]
            [sekai.core.space :as space]
            [sekai.math.vectors :as vect]
            [sekai.core.logging :refer [log]]
            [sekai.entity.tags :as tags]))

(defn sprite [entity]
  (get-in @game-state [:entities entity :sprite :sprite]))

(defn center->orig
  "Center of sprite to sprite origin in screenspace."
  [entity s-center]
  (let [sprite (sprite entity)
        width (.-width sprite)
        height (.-height sprite)]
    (-> s-center space/coords->vec
        (vect/sub [width height 0])
        space/vec->coords)))

(defn binary-search-insertion-index [sorted-children dist-fn new-sprite-dist]
  (loop [low 0
         high (count sorted-children)]
    (if (< low high)
      (let [mid (bit-shift-right (+ low high) 1)
            mid-sprite (nth sorted-children mid)
            mid-dist (dist-fn mid-sprite)]
        (if (>= mid-dist new-sprite-dist)
            (recur low mid)
            (recur (inc mid) high)))
      low)))

(defn linear-search-insertion-index [sprites-array dist-fn distance]
    (loop [idx 0]
      (if (or (= idx (count sprites-array))
              (>= distance (dist-fn (nth sprites-array idx))))
       idx
        (recur (inc idx)))))

(defn sprite2screen [sprite]
  (space/dist2screen (get-in @game-state [:sprites sprite])))

(defn enable! [eid]
  (let [sprite (sprite eid)
        world ^js/PIXI.Container (-> @game-state :canvas :world)
        new-sprite-dist (space/dist2screen eid)
        sorted-children (-> world .-children js->clj)]

    (tags/register-tag! eid :visible)
    (swap! game-state update-in [:sprites] assoc sprite eid)

    ;;; FIXME unfuck
    (-> sprite .-anchor (.set 0.0))

    (let [insertion-index
          (linear-search-insertion-index
           sorted-children sprite2screen new-sprite-dist)]
      (.addChildAt ^js/PIXI.Container world sprite insertion-index))))

(defn disable! [eid]
  (let [sprite (sprite eid)
        world (-> @game-state :canvas :world)]
    (tags/deregister-tag! eid :visible)
    (.removeChild world sprite)))

(defn render-screen! [eid s-coords]
  (let [sprite (sprite eid)
        orig (center->orig eid s-coords)]
    (set! (.-x sprite) (:x orig))
    (set! (.-y sprite) (:y orig)) ))

(defn render-world! [eid]
  (render-screen!
   eid (-> eid (entity/get-component :position)
           space/world->screen)))

(defn sort-sprites! []
  (let [stage (-> @game-state :canvas :canvas .-stage)]
    (set!
     (.-children stage)
     (.sort
      (.-children stage)
      (fn [a b]
        (let [eid1 (get-in @game-state [:sprites a])
              eid2 (get-in @game-state [:sprites b])]
          (- (space/dist2screen eid2) (space/dist2screen eid1))))))))


(defn render! [& [delta]]
  (let [uis (s/gather (s/with-tags :ui :visible))
        spatials (s/gather (s/with-tags :spatial :visible))]
    (run! render-world! spatials)
    (run! render-screen! uis)))

(defn move-camera!
  "Set position of camera; nil leaves coord unchanged."
  [x & [y z]]
  (let [camera (space/camera->world)
        coords {:x (or x (:x camera)) :y (or y (:y camera)) :z (or z (:z camera))}
        world (-> @game-state :canvas :world)
        tile-size (-> @game-state :settings :tile-size)]
    (swap! game-state assoc :camera coords)
    (set! (.-x world.position) (* (:x coords) tile-size))
    (set! (.-y world.position) (* (:y coords) tile-size))))
