(ns topdown2d.demoscene
  (:require [topdown2d.collision :as collision]
            [topdown2d.sprites :as sprites]
            [topdown2d.dom :refer [by-id]]
            [topdown2d.tileset :as tileset]
            [clojure.walk :as walk]))

(def map-def (atom nil))

(defn init [gamestate scenestate]
  (-> (.fetch js/window "map/map.json")
      (.then #(.json %))
      (.then #(reset! map-def (js->clj %))))
  (merge
    scenestate
    {:map-def {:images {:pokelike (by-id "demoscene-tileset")}
               :def nil
               :zoom 3}
     :player {:x (/ (get-in gamestate [:dimensions :w]) 2)
              :y (/ (get-in gamestate [:dimensions :h]) 2)
              :w 0 :h 0
              :d :s
              :animation {:ids {:s 778 :n 778
                                :e 819 :w 819}
                          :last-cycle 0
                          :curr-id 778
                          :tileset :pokelike
                          :tileset-def nil
                          :image (by-id "demoscene-tileset")}}
     :viewport {:keep-in {:x 0 :y 0
                          :w 0 :h 0}
                :tileset :pokelike
                :image (by-id "demoscene-tileset")
                :tileset-def nil
                :x 6 :y 9
                :w (get-in gamestate [:dimensions :w])
                :h (get-in gamestate [:dimensions :h])
                :d :?
                ;; pixels per second
                :pps 150}}))

(defn update-player [gamestate player dir]
  (let [old-dir (:d player)
        new-dir (if (= :? dir) old-dir dir)]
    (as-> player p
          (assoc p :d new-dir)
          (if (= :? dir)
            (sprites/reset player)
            (sprites/proc gamestate p)))))

(defn update-viewport [gamestate viewport dir]
  (collision/move-inside
    (assoc viewport :d dir)
    (:keep-in viewport)
    (collision/pps->px gamestate viewport)))

(defn init-viewport [scenestate]
  (let [{loaded-def :def zoom :zoom} (:map-def scenestate)
        viewport (:viewport scenestate)
        {mw :width
         mh :height
         tw :tilewidth
         th :tileheight} loaded-def]
    (-> scenestate
        (assoc-in [:viewport :keep-in :w] (* (dec mw) zoom tw))
        (assoc-in [:viewport :keep-in :h] (* (dec mh) zoom th))
        (update-in [:viewport :x] * tw zoom)
        (update-in [:viewport :y] * th zoom)
        (assoc-in [:viewport :tileset-def]
                  (tileset/map-def->tileset loaded-def
                                            (:tileset viewport))))))

(defn init-player [scenestate]
  (let [{tw :tilewidth th :tileheight :as map-def}
        (get-in scenestate [:map-def :def])
        zoom (-> scenestate :map-def :zoom)]
    (-> scenestate
        (assoc-in [:player :w] (- tw 2))
        (assoc-in [:player :h] (- th 2))
        (update-in [:player :x] - (* zoom (/ tw 2)))
        (update-in [:player :y] - (* zoom (/ th 2)))
        (assoc-in [:player :animation :tileset-def]
                  (tileset/map-def->tileset map-def
                                            (-> scenestate
                                                :player
                                                :animation
                                                :tileset))))))

(defn init-map-def [scenestate]
  (if (and (not (-> scenestate :map-def :def))
           (some? @map-def))
    (let [loaded-def (walk/keywordize-keys @map-def)
          zoom (get-in scenestate [:map-def :zoom])]
      (-> scenestate
          (assoc-in [:map-def :def] loaded-def)
          (update-in [:map-def :def]
                     (partial tileset/prepare-map-def zoom))
          init-player
          init-viewport))
    scenestate))

(defn update-scene [gamestate scenestate]
  (let [new-scenestate (init-map-def scenestate)
        player (:player new-scenestate)
        viewport (:viewport new-scenestate)
        dir (-> gamestate :input :dir)]
    (assoc new-scenestate
      :player (update-player gamestate player dir)
      :viewport (update-viewport gamestate viewport dir))))

(defn draw-scene [gamestate scenestate]
  (let [viewport (:viewport scenestate)
        ctx (:ctx gamestate)]
    (when (-> scenestate :map-def :def)
      (tileset/draw-viewport (:map-def scenestate)
                             ctx
                             viewport)
      (let [{:keys [tileset-def curr-id image]}
            (-> scenestate :player :animation)]
        (tileset/draw-tile ctx
                           tileset-def
                           image
                           curr-id
                           (:player scenestate))))))
