(ns image-tiles
  (:require [clojure.string :as str]))

(def default-tile-size 768)
(defonce current-image (atom nil))

(defn by-id [id]
  (.getElementById js/document id))

(defn text-value [id]
  (some-> (by-id id) .-value str/trim))

(defn set-text! [id value]
  (when-let [el (by-id id)]
    (set! (.-textContent el) value)))

(defn parse-positive-int [value label]
  (let [n (js/Number value)]
    (when-not (and (js/Number.isInteger n) (pos? n))
      (throw (js/Error. (str label " must be a positive integer."))))
    n))

(defn parse-dimensions [value]
  (let [[width height :as parts] (str/split (str/trim value) #"(?i)x")]
    (when (or (not= 2 (count parts)) (str/blank? width) (str/blank? height))
      (throw (js/Error. "Dimensions must look like 1080x2400.")))
    {:width (parse-positive-int (str/trim width) "Width")
     :height (parse-positive-int (str/trim height) "Height")}))

(defn tile-size []
  (let [value (text-value "tile-size")]
    (if (str/blank? value)
      default-tile-size
      (parse-positive-int value "Tile size"))))

(defn tiles-needed [{:keys [width height] :as image} tile-size]
  (let [cols (js/Math.ceil (/ width tile-size))
        rows (js/Math.ceil (/ height tile-size))]
    (assoc image
           :tile-size tile-size
           :cols cols
           :rows rows
           :tile-count (* cols rows))))

(defn render-result [{:keys [file-name width height tile-size cols rows tile-count]}]
  (str (when file-name
         (str "File: " file-name "\n"))
       "Dimensions: " width "x" height "\n"
       "Tile size: " tile-size "x" tile-size "\n"
       "Columns: " cols "\n"
       "Rows: " rows "\n"
       "Tile count: " tile-count))

(defn selected-file []
  (some-> (by-id "image-file") .-files (aget 0)))

(defn calculate! []
  (try
    (let [image (or @current-image
                    (when-let [dimensions (not-empty (text-value "image-dimensions"))]
                      (parse-dimensions dimensions)))]
      (if image
        (set-text! "image-tiles-result"
                   (render-result (tiles-needed image (tile-size))))
        (set-text! "image-tiles-result" "Upload an image or enter dimensions.")))
    (catch :default error
      (set-text! "image-tiles-result" (.-message error)))))

(defn load-image-file! [file]
  (let [url (js/URL.createObjectURL file)
        image (js/Image.)]
    (set! (.-onload image)
          (fn []
            (reset! current-image {:file-name (.-name file)
                                   :width (.-naturalWidth image)
                                   :height (.-naturalHeight image)})
            (set! (.-value (by-id "image-dimensions"))
                  (str (.-naturalWidth image) "x" (.-naturalHeight image)))
            (js/URL.revokeObjectURL url)
            (calculate!)))
    (set! (.-onerror image)
          (fn []
            (reset! current-image nil)
            (js/URL.revokeObjectURL url)
            (set-text! "image-tiles-result" "Could not read that image file.")))
    (set! (.-src image) url)))

(defn image-file-changed! []
  (if-let [file (selected-file)]
    (load-image-file! file)
    (do
      (reset! current-image nil)
      (set-text! "image-tiles-result" "Upload an image or enter dimensions."))))

(defn dimensions-changed! []
  (reset! current-image nil))

(defn init! []
  (when-let [file-input (by-id "image-file")]
    (.addEventListener file-input "change" image-file-changed!))
  (when-let [dimensions-input (by-id "image-dimensions")]
    (.addEventListener dimensions-input "input" dimensions-changed!))
  (when-let [button (by-id "count-image-tiles")]
    (.addEventListener button "click" calculate!)))

(init!)
