(ns topdown2d.tileset)

(defn next-animation-cycle [animation curr-cycle]
  (let [curr-ix (.indexOf animation curr-cycle)
        next-ix (inc curr-ix)
        next-ix (if (> next-ix (count animation))
                  0 next-ix)]
    (nth animation next-ix)))

(defn map-def->tileset [map-def tileset-id]
  (->> (:tilesets map-def)
       (filter #(= (name tileset-id) (:name %)))
       first))

(defn tick-animation [tileset-def anim-def now]
  (let [map-def (:def tileset-def)
        {:keys [last-cycle curr-id tileset]} anim-def
        animation (->> (map-def->tileset map-def tileset)
                       :tiles
                       (filter #(= curr-id (:id %)))
                       first
                       :animation)
        curr-cycle (filter #(= curr-id (:tileid %)) animation)
        trigger-cycle? (> (- now last-cycle)
                          (:duration curr-cycle))
        next-cycle (and trigger-cycle?
                        (next-animation-cycle animation curr-cycle))]
    (if trigger-cycle?
      (assoc anim-def
        :last-cycle now
        :curr-id (:tileid next-cycle))
      anim-def)))

(defn ix->coords [ix width]
  (let [x (rem ix width)]
    [(dec x)
     (/ (- ix x) width)]))

(defn map-tileset
  [zoom {:keys [tilewidth tileheight columns tilecount]
         :as tileset}]
  (assoc tileset
    :target-width (* zoom tilewidth)
    :target-height (* zoom tileheight)
    :tiles-x columns
    :tiles-y (/ tilecount columns)))

(defn map-tilesets [zoom map-def]
  (update map-def
          :tilesets
          (fn [tilesets]
            (map (partial map-tileset zoom)
                 tilesets))))

(defn layer->dest [{:keys [target-width target-height]} layer]
  (map-indexed
    (fn [ix tile]
      (let [[dxc dyc] (ix->coords ix (:width layer))]
        {:tile tile
         :x (* dxc target-width)
         :y (* dyc target-height)
         :w target-width
         :h target-height}))
    (:data layer)))

(defn map-layers [map-def]
  (let [first-tileset (-> map-def :tilesets first)]
    (update map-def
            :layers
            (fn [layers]
              (map (partial layer->dest first-tileset)
                   layers)))))

(defn prepare-map-def [zoom map-def]
  (map-layers (map-tilesets zoom map-def)))

(defn draw-tile [ctx tileset image tileid dest]
  (let [{:keys [tileheight tilewidth
                tiles-x target-width target-height]} tileset
        [x y] (ix->coords tileid tiles-x)
        sx (* x tilewidth)
        sy (* y tileheight)]
    (.drawImage ctx image
                sx sy tilewidth tileheight
                (int (:x dest)) (int (:y dest))
                target-width target-height)))

(defn draw-viewport [map-def ctx viewport]
  (let [def (:def map-def)
        image (:image viewport)
        tileset (:tileset-def viewport)]
    (doseq [layers (:layers def)]
      (doseq [dest layers
              :let [dx (- (:x dest) (:x viewport))
                    dy (- (:y dest) (:y viewport))]]
        (draw-tile ctx tileset image
                   (:tile dest)
                   {:x dx :y dy})))))
