(ns weathergen.ui.layers.flights
  "Responsible for drawing the flight path layer and the associated controls."
  (:require [clojure.set :as set]
            [clojure.string :as str]
            [garden.stylesheet :as css]
            [goog.string :as gstring]
            [goog.string.format]
            [hoplon.core :refer [a cond-tpl datalist defelem div do-watch fieldset for-tpl h4 hr
                                 i if-tpl img input label legend li
                                 option pre select span style
                                 table text tbody td thead tr ul when-tpl with-timeout]]
            [hoplon.svg :as svg]
            [javelin.core :refer [cell cell? cell= cell-let dosync formula-of lens with-let]]
            [taoensso.timbre :as log
             :include-macros true
             :refer (log trace debug info warn error fatal report
                         logf tracef debugf infof warnf errorf fatalf reportf
                         spy get-env log-env)]
            [weathergen.coordinates :as coords]
            [weathergen.falcon.constants :as c]
            [weathergen.falcon.files.images :as im]
            [weathergen.falcon.files.mission :as mission]
            [weathergen.help :refer [help-icon with-help]]
            [weathergen.time :as time]
            [weathergen.ui.buttons :as buttons]
            [weathergen.ui.common :as comm :refer [colors control-section
                                                   format-heading format-distance format-speed format-mach format-time
                                                   inl pre-cell pct px styled team-color]]
            [weathergen.ui.grids :as grids]
            [weathergen.util :as util :refer [has-flag?]])
  (:require-macros
   [weathergen.cljs.macros :refer [keyed-for-tpl with-attr-bindings with-bbox with-time #_formula-of]]))

;; (set! *warn-on-infer* true)

(def mission-category-names
  {c/ATO_OCA "OCA"
   c/ATO_STRIKE "Strike"
   c/ATO_INTERDICTION "Interdiction"
   c/ATO_SEAD "SEAD"
   c/ATO_CAS "CAS"
   c/ATO_DCA "DCA"
   c/ATO_CCCI "C3I"
   c/ATO_MARITIME "Maritime"
   c/ATO_SUPPORT "Support"
   c/ATO_OTHER "Other"})

(def default-mission-category-colors
  {c/ATO_OCA (comm/to-hex-str "green")
   c/ATO_STRIKE "#e00000"
   c/ATO_INTERDICTION "#cc00cc"
   c/ATO_SEAD "#640082"
   c/ATO_CAS (comm/to-hex-str "darkblue")
   c/ATO_DCA "#ffa800"
   c/ATO_CCCI (comm/to-hex-str "white")
   c/ATO_MARITIME (comm/to-hex-str "lightblue")
   c/ATO_SUPPORT "#a8a8a8"
   c/ATO_OTHER (comm/to-hex-str "black")})

(def mission-category-colors
  (comm/versioned-local-storage ::mission-category-colors default-mission-category-colors))

(def hidden-flights-columns
  (comm/versioned-local-storage ::hidden-flights-columns #{}))

(def movable-flights-columns
  (comm/versioned-local-storage
   ::movable-flights-columns
   [:combatant
    :callsign
    :airframe
    :mission
    :package
    :squadron
    :airbase
    :takeoff
    :tot
    :egress]))

(def hidden-steerpoints-columns
  (comm/versioned-local-storage
   ::hidden-steerpoints-columns
   #{:speed-ias :speed-tas :action :enroute :depart}))

(def movable-steerpoints-columns
  (comm/versioned-local-storage
   ::movable-steerpoints-columns
   [:description
    :arrive
    :depart
    :remain
    #_:formation
    #_:speed-locked?
    :action
    :enroute
    :distance
    :heading
    :speed-cas
    :speed-tas
    :speed-gnd
    :speed-mach
    :speed-ias
    :altitude
    :comments]))

(def row-primary-color "white")
(def row-alternate-color "#f0f0f0")
(def row-tertiary-color "darkgrey")

(defn row-color
  "Returns a color for a table row based on its ordinal."
  [rownum]
  (when rownum
    (if (even? rownum)
      row-alternate-color
      row-primary-color)))

(defn flight-mission-color
  "Returns the color for a flight mission"
  [mission flight colors]
  (->> flight
       ::mission/mission
       :category
       colors))

(defn flight-aircraft
  "Returns a string describing the compsition of the flight. E.g. `2 F-16`."
  [flight]
  (let [{:keys [quantity airframe]} (::mission/aircraft flight)]
    (str quantity " " airframe)))

(comm/register-styles!
 ::briefing-summary
 [[(comm/class-selector-for ::briefing-summary)
   {:background "initial"}
   [:tr {:background "initial"}
    [:td {:border "none"}]]]])

(defn flight-details
  "Returns UI for the info/details display of the flight given a cell for the flight."
  [{:keys [mission flight display tabbed-flights checked-flights]}]
  (let [col      (fn col
                   ([title help-key f] (col title help-key {} f))
                   ([title help-key opts f]
                    (let [{:keys [align]} opts]
                      {:title        (with-help [:flights :waypoint-columns help-key]
                                       title)
                       :disable-sort true
                       :sort-key     f
                       :formatter    (fn [waypoint]
                                       (div
                                        :css {:white-space "nowrap"
                                              :text-align  (when (= :right align) "right")
                                              :padding     (px 0 2)}
                                        (formula-of [waypoint]
                                          (f waypoint))))})))
        bold     (fn [& contents]
                   (apply div :css {:font-weight "bold"} contents))
        has-tab? (formula-of [tabbed-flights flight]
                   (tabbed-flights (:id flight)))]
    (div
     (div
      (div
       :css {:display     "inline-block"
             :font-weight "bold"
             :font-size   "120%"}
       (cell= (:name flight)))
      (div
       :css (formula-of [has-tab?]
              {:display         (when has-tab? "none")
               :float           "right"
               :text-decoration "underline"
               :color           "blue"
               :cursor          "pointer"
               :user-select     "none"})
       :click (fn [_]
                (swap! tabbed-flights
                       assoc
                       (:id @flight)
                       @flight))
       "Open flight in tab"))
     (table
      :class (comm/class-for ::briefing-summary)
      (tr (td (bold "Mission"))  (td (formula-of [flight]
                                       (-> flight ::mission/mission :name))))
      (tr (td (bold "Package"))  (td (formula-of [flight]
                                       (-> flight ::mission/package :name))))
      (tr (td (bold "Aircraft")) (td (formula-of [flight]
                                       (flight-aircraft flight))))
      ;;(tr (td "Time on Station") (td "TODO"))
      )
     (hr)
     (div
      :css {:font-weight "bold"
            :font-size   "110%"}
      "Steerpoints")
     (div
      :css {:margin-bottom (px 3)}
      (comm/checkbox
       :value (formula-of [checked-flights flight]
                (checked-flights flight))
       :change (fn [e]
                 (.log js/console "checkbox change" (if (@checked-flights @flight)
                                                      "true"
                                                      "false"))
                 (swap! checked-flights util/toggle-set-membership @flight)))
      (with-help [:flight-paths :show?]
        (span
         :css {:margin-left (px 3)}
         "Show flight path on map?")))
     (div
      :css {:margin-bottom (px 3)}
      (with-help [:flight-paths :color]
        (span
         :css {:margin-right (px 3)}
         "Color:"))
      (comm/color-picker2
       :value (formula-of [mission flight display mission-category-colors]
                (let [{:keys [camp-id]} flight]
                  (if-let [c (get-in display [camp-id :color])]
                    c
                    (flight-mission-color mission
                                          flight
                                          mission-category-colors))))
       :change #(swap! display assoc-in [(:camp-id @flight) :color] %)))
     (grids/table-grid
      :data (-> flight ::mission/waypoints cell=)
      :key-fn ::mission/number
      :row-attrs (constantly {})
      :when-empty "Flight has no waypoints"
      :fixed-columns [:number]
      :movable-columns movable-steerpoints-columns
      :hidden-columns hidden-steerpoints-columns
      :columns {:number      (col "#"
                                  :number
                                  {:align :right}
                                  ::mission/number)
                :description (col "Desc"
                                  :description
                                  ::mission/description)
                :action      (col "Action"
                                  :action
                                  ::mission/action-name)
                :enroute     (col "Enroute"
                                  :enroute
                                  ::mission/enroute-action-name)
                :arrive      (col "Time"
                                  :arrive
                                  #(-> % :arrive (format-time {:seconds? true}) (or "--")))
                :depart      (col "Depart"
                                  :depart
                                  #(-> % :depart (format-time {:seconds? true}) (or "--")))
                :remain      (col "Remain"
                                  :remain
                                  #(-> %
                                       ::mission/remain
                                       (format-time {:omit-day? true :seconds? true})
                                       (or "--")))
                ;; Couldn't figure out where to get the formation text from
                ;; :formation (col "Formation" :formation #_::mission/formation-name)
                :distance    (col "Dist"
                                  :distance
                                  {:align :right}
                                  #(-> % ::mission/distance format-distance (or "--")))
                :heading     (col "Hdg"
                                  :heading
                                  {:align :right}
                                  #(-> % ::mission/heading  format-heading  (or "--")))
                :speed-cas   (col "CAS"
                                  :speed-cas
                                  {:align :right}
                                  #(-> % ::mission/speed-cas format-speed (or "--")))
                :speed-ias   (col "IAS"
                                  :speed-ias
                                  {:align :right}
                                  #(-> % ::mission/speed-ias format-speed (or "--")))
                :speed-tas   (col "TAS"
                                  :speed-tas
                                  {:align :right}
                                  #(-> % ::mission/speed-tas format-speed (or "--")))
                :speed-gnd   (col "Gnd Spd"
                                  :speed-gnd
                                  {:align :right}
                                  #(-> % ::mission/speed-gnd format-speed (or "--")))
                :speed-mach  (col "Mach"
                                  :speed-mach
                                  {:align :right}
                                  #(-> % ::mission/speed-mach format-mach (or "--")))
                ;; :speed-locked? (col "Spd lck?" #(if (has-flag? % c/WPF_SPEED_LOCKED) "Yes" "No"))
                :altitude    (col "Alt"
                                  :altitude
                                  {:aligh :right}
                                  ::mission/altitude)
                :comments    (col "Comments"
                                  :comments
                                  ::mission/comments)})
     (hr)
     (div
      :css {:font-weight "bold"
            :font-size   "120%"}
      "Ordnance")
     (if-tpl (->> flight ::mission/loadouts (every? empty?) cell=)
       (div
        :css {:margin-left   (px 10)
              :margin-top    (px 7)
              :margin-bottom (px 5)}
        "No weapons carried")
       (div
        :css {:display        "flex"
              :flex-direction "row"
              :flex-wrap      "wrap"}
        (for-tpl [indexed (formula-of [flight]
                            (map-indexed vector (::mission/loadouts flight)))]
          (cell-let [[index loadout] indexed
                     {:keys [quantity name]} loadout]
            (div
             :css {:width     (pct 35)
                   :min-width (pct 35)
                   :max-width (pct 45)
                   :padding   (px 10)}
             (div
              (span
               :css {:font-weight "bold"}
               (cell= (str (:name flight) "-" (inc index))))
              (span
               :css {:font-size  "90%"
                     :font-style "italic"}
               (formula-of [flight index]
                 (let [stats (-> flight :plane-stats (nth index))]
                   (condp = stats
                     c/AIRCRAFT_MISSING " (missing)"
                     c/AIRCRAFT_DEAD    " (destroyed)"
                     "")))))
             (ul
              :css {:margin-top (px 5)}
              (for-tpl [weapon loadout]
                (cell-let [{:keys [name quantity]} weapon]
                  (li
                   (cell= (str quantity " x " name))))))))))))))

(defn flight-info-formatter
  "Returns a formatter for the info cell of the flight table."
  [{:keys [mission display detailed-flight scroll-container tabbed-flights checked-flights]}]
  (fn [flight]
    (cell-let [{:keys [camp-id]} flight]
      (let [visible?  (cell= (= camp-id detailed-flight))
            doc-click (fn click-fn [e]
                        (.removeEventListener js/document "click" click-fn)
                        (reset! detailed-flight nil))]
        (div
         :click (constantly false)
         :css {:position    "relative"
               :white-space "nowrap"}
         (buttons/a-button
          :latched? visible?
          :click #(do
                    (swap! detailed-flight
                           (fn [current]
                             (when (not= current @camp-id)
                               (.addEventListener js/document
                                                  "click"
                                                  doc-click)
                               @camp-id)))
                    false)
          :css {:border-radius "50%"
                :width         (px 12)
                :height        (px 12)
                :text-align    "center"
                :font-weight   "bold"
                :font-family   "serif"
                :font-size     "110%"
                :line-height   (px 12)}
          :title "More options..."
          "i")
         (buttons/image-button
          :latched? (cell= (contains? tabbed-flights (:id flight)))
          :click (fn [_]
                   (if (contains? @tabbed-flights (:id @flight))
                     (swap! tabbed-flights dissoc (:id @flight))
                     (swap! tabbed-flights assoc  (:id @flight) @flight)))
          :width (px 16)
          :title "Show flight in separate tab"
          :src "images/open-external.svg")
         (when-tpl visible?
           (with-let [d (div
                         :css {:position      "absolute"
                               :top           (px 25)
                               :left          (px 25)
                               :padding       (px 5)
                               :border-style  "double"
                               :border-color  "black"
                               :background    "#FDFFD9"
                               :z-index       1
                               :margin-bottom (px 5)}
                         (flight-details {:mission         mission
                                          :flight          flight
                                          :display         display
                                          :tabbed-flights  tabbed-flights
                                          :checked-flights checked-flights}))]
             (do-watch visible?
                       (fn [old new]
                         (when (and (not old) new)
                           (with-timeout 0
                             ;; The math here is totally wrong. It only works in the
                             ;; context of the scroll setup I have in the main part of the
                             ;; sim - it doesn't work in other setups. TODO: figure out
                             ;; how to do this right.
                             (let [container   (js/jQuery scroll-container)
                                   offset      (-> container .offset .-top)
                                   vis-top     (.scrollTop container)
                                   vis-height  (- (-> js/window js/jQuery .height) offset)
                                   vis-bottom  (+ vis-top vis-height)
                                   elem        (js/jQuery d)
                                   elem-height (.height elem)
                                   elem-top    (-> elem .offset .-top (- offset) (+ vis-top))
                                   elem-bottom (+ elem-top elem-height)
                                   ;; Positive in these two means that there is space
                                   ;; between its edge and the edge of the visible area.
                                   top-diff    (- elem-top vis-top 25)
                                   bottom-diff (- vis-bottom elem-bottom)]
                               (prn :vis-top vis-top :vis-height vis-height :vis-bottom vis-bottom
                                    :elem-height elem-height :elem-top elem-top :elem-bototm elem-bottom)
                               (cond
                                 ;; The top of the element is scrolled off the top - scroll it down
                                 (neg? top-diff)
                                 (-> "body" js/jQuery (.animate #js {:scrollTop (+ elem-top 25)} 300))

                                 ;; The bottom of the element is
                                 ;; scrolled off the bottom - scroll
                                 ;; it up, but not farther than would
                                 ;; scroll the top out of view.
                                 (neg? bottom-diff)
                                 (-> scroll-container js/jQuery (.animate #js {:scrollTop (if (< elem-height vis-height)
                                                                                            (+ 25 (- vis-top bottom-diff))
                                                                                            (- elem-top 25))}
                                                                          300)))))))))))))))

(defn flight-path-display-controls
  "Returns a control section for affecting the way flight-paths
  render on the map."
  [title state]
  (control-section
   :title (with-help [:flights :display-options :overview]
            title)
   (let [{:keys [map-flight-path-size]} state
         {:keys [label key init min max]}
         {:label "Flight path size:"
          :key   :map-flight-path-size
          :init  0
          :min   -1.0
          :max   1.0}]
     (div
      (with-help [:flights :display-options key]
        label)
      (let [id (str (gensym))]
        (inl
         :css {:vertical-align "middle"
               :margin (px 0 5)}
         (datalist
          :id id
          (for [val (range (* 100 min) (* 100 (inc max)) (* (- max min) 25))]
            (option :value val)))
         (input {:type  "range"
                 :list  id
                 :min   (long (* min 100))
                 :max   (long (* max 100))
                 :value (-> map-flight-path-size (* 100) cell=)
                 :input #(reset! map-flight-path-size
                                 (/ @% 100.0))})))
      (inl
       (buttons/a-button
        :click #(reset! map-flight-path-size init)
        "Reset")
       (help-icon [:flights :display-options (keyword (str "reset-" (name key)))]))))))

(defn controls
  [{:keys [all-flights checked-flights display highlighted-flight
           mission selected-teams tabbed-flights visible-teams scroll-container]
    :as   state}]
  (cond-tpl
    (-> mission not cell=)
    "No mission loaded"

    (-> all-flights count zero? cell=)
    "No flights present in the mission"

    :else
    (div
     (control-section
      :title (with-help [:flights :mission-key]
               "Mission Key")
      :css {:margin-bottom (px 3)}
      ;; (pre-cell "checked flights"
      ;;           (cell= (map :id checked-flights)))
      ;; (pre-cell "display"
      ;;           display)
      (div
       :css {:display   "flex"
             :flex-wrap "wrap"
             :max-width (px 400)}
       (for [[id name] mission-category-names]
         (div
          :css {:white-space "nowrap"}
          (inl
           :css {:margin-right (px 3)}
           :click (constantly false)
           (comm/color-picker2
            :value (formula-of [mission-category-colors] (mission-category-colors id))
            :change #(swap! mission-category-colors assoc id %)))
          (inl
           :css {:width          (px 70)
                 :vertical-align "middle"}
           name))))
      (buttons/a-button
       :click #(reset! mission-category-colors default-mission-category-colors)
       "Reset to defaults")
      (help-icon [:flights :mission-key-reset]))
     (flight-path-display-controls "Display Options" state)
     (control-section
      :title (with-help [:flights :team-visibility]
               "Team Visibility")
      (comm/team-selector
       :mission mission
       :selected-teams selected-teams
       :selectable-teams visible-teams))
     #_(pre-cell "visible-teams"
               visible-teams
                 #_(formula-of [tabbed-flights]
                     (map #(select-keys % [:title]) tabbed-flights)))
     (control-section
      :title (with-help [:flights :flight-info]
               "Flight Info")
      (let [detailed-flight (cell nil)]
        (grids/table-grid
         :data (formula-of [mission all-flights selected-teams]
                 (->> all-flights
                      (filter #(contains? selected-teams (:owner %)))))
         :key-fn :id
         :when-empty (text "No flights to show")
         :row-attrs (fn [flight rownum]
                      (when (and flight rownum)
                        {:css        (formula-of [rownum flight highlighted-flight]
                                       {:background (if (= highlighted-flight flight)
                                                      "lightgoldenrodyellow"
                                                      (row-color rownum))})
                         :mouseenter #(reset! highlighted-flight @flight)
                         :mouseleave #(reset! highlighted-flight nil)
                         :click      #(do
                                        ;; It's confusing if we click something to
                                        ;; hide it, but it stays visible due to
                                        ;; highlighting.
                                        (dosync
                                         (swap! checked-flights util/toggle-set-membership @flight)
                                         (reset! highlighted-flight (@checked-flights @flight))))
                         :class      (cell= {:highlighted (= highlighted-flight flight)})}))
         :hidden-columns hidden-flights-columns
         :fixed-columns (cell [:show? :info])
         :movable-columns movable-flights-columns
         :columns (let [col (fn [title help-key f]
                              {:title     (with-help [:flights :columns help-key]
                                            title)
                               :sort-key  #(f @mission %)
                               :formatter (fn [flight]
                                            (formula-of [mission flight]
                                              (f mission flight)))})]
                    {:show?     {:title     (with-help [:flights :columns :show?]
                                              "Show?")
                                 :sort-key  (fn [flight]
                                              (not (@checked-flights flight)))
                                 :formatter (fn [flight]
                                              (cell-let [{:keys [camp-id]} flight]
                                                (let [visible? (formula-of [checked-flights flight]
                                                                 (checked-flights flight))]
                                                  (div
                                                   :css {:white-space "nowrap"}
                                                   (input :camp-id camp-id
                                                          :type "checkbox"
                                                          :value visible?)
                                                   (inl
                                                    :css {:margin-right   "2px"
                                                          :vertical-align "middle"}
                                                    :click (constantly false)
                                                    (comm/color-picker2
                                                     :value (formula-of [mission flight camp-id display mission-category-colors]
                                                              (if-let [c (get-in display [camp-id :color])]
                                                                c
                                                                (flight-mission-color mission
                                                                                      flight
                                                                                      mission-category-colors)))
                                                     :change #(swap! display assoc-in [@camp-id :color] %)))))))}
                     :info      {:title        (with-help [:flights :columns :info]
                                                 "Info")
                                 :disable-sort true
                                 :formatter    (flight-info-formatter {:mission          mission
                                                                       :display          display
                                                                       :detailed-flight  detailed-flight
                                                                       :scroll-container scroll-container
                                                                       :tabbed-flights   tabbed-flights
                                                                       :checked-flights  checked-flights})}
                     :combatant {:title     (with-help [:flights :columns :combatant]
                                              "Who")
                                 :sort-key  #(->> %
                                                  :owner
                                                  (mission/team-name @mission))
                                 :formatter #(let [team (cell= (:owner %))]
                                               (inl
                                                :css (cell= {:color (team-color team :dark-text)})
                                                (cell= (mission/team-name mission team))))}
                     :package   (col "Package"
                                     :package
                                     (fn [mission flight]
                                       (->> flight
                                            ::mission/package
                                            :name)))
                     :squadron  (col "Squadron"
                                     :squadron
                                     (fn [mission flight]
                                       (->> flight
                                            ::mission/squadron
                                            :name)))
                     :airbase   (col "Airbase"
                                     :airbase
                                     (fn [mission flight]
                                       (some->> flight
                                                ::mission/airbase
                                                :name)))
                     :airframe  {:title     "Aircraft"
                                 :sort-key  #(flight-aircraft %)
                                 :formatter (fn [flight]
                                              (div
                                               (div
                                                :css {:display    "inline-block"
                                                      :float      "left"
                                                      :width      (px 19)
                                                      :text-align "center"}
                                                (comm/centered-image mission
                                                                     (cell= (::mission/squadron-image flight))
                                                                     19))
                                               (span
                                                :css {:margin-left (px 3)}
                                                (cell= (flight-aircraft flight)))))}
                     :callsign  (col "Callsign"
                                     :callsign
                                     (fn [_ flight] (:name flight)))
                     :mission   {:title     (with-help [:flights :columns :mission]
                                              "Mission")
                                 :sort-key  (fn [flight]
                                              (->> flight ::mission/mission :name))
                                 :formatter (fn [flight]
                                              (formula-of [flight]
                                                (-> flight ::mission/mission :name)))
                                 :css       (fn [flight rownum]
                                              (formula-of [mission flight mission-category-colors rownum]
                                                (let [color    (flight-mission-color mission
                                                                                     flight
                                                                                     mission-category-colors)
                                                      contrast (comm/contrasting color
                                                                                 row-tertiary-color
                                                                                 (row-color rownum))]
                                                  {:background contrast
                                                   :color      color})))}
                     :takeoff   (col "T/O"
                                     :takeoff
                                     (fn [_ flight]
                                       (->> flight :waypoints first :depart format-time)))
                     :tot       (col "TOT"
                                     :tot
                                     (fn [_ flight]
                                       (->> flight :time-on-target format-time)))
                     :egress    (col "Egress"
                                     :egress
                                     (fn [_ flight]
                                       (->> flight :mission-over-time format-time)))})))))))

(defn waypoint-grid-coords
  "Given a BMS waypoint, return an x/y map of its weatherspace coordinates."
  [mission waypoint]
  (coords/fgrid->weather mission (:grid-x waypoint) (:grid-y waypoint)))

(defn waypoints->path
  "Given a sequence of BMS waypoints, return an SVG path that follows them."
  [mission waypoints]
  (some->> waypoints
           (util/take-until #(-> % :flags (has-flag? c/WPF_LAND)))
           (mapv #(waypoint-grid-coords mission %))
           (mapv #(gstring/format "%f,%f" (:x %) (:y %)))
           (interpose " L")
           (apply str "M")))

(defn flight-path-overlay
  [flight {:keys [checked-flights
                  display
                  highlighted-flight
                  mission
                  map-zoom
                  map-text-scale
                  map-flight-path-scale
                  map-show-text-background?]}]
  (let [camp-id                    (cell= (:camp-id flight))
        highlighted?               (formula-of [flight highlighted-flight]
                                     (= flight highlighted-flight))
        color                      (formula-of [mission flight camp-id display highlighted? mission-category-colors]
                                     (get-in display [camp-id :color] (flight-mission-color mission
                                                                                            flight
                                                                                            mission-category-colors))
                                     #_(if highlighted?
                                         "yellow"
                                         (get-in display [camp-id :color] (flight-mission-color mission
                                                                                                flight
                                                                                                mission-category-colors))))
        ;; Styling
        opacity                    1
        label-stroke-width         0.01
        narrow-stroke-width        0.1
        lines-stroke-dasharray-on  0.1
        lines-stroke-dasharray-off 0.1
        wide-stroke-width          0.15
        font-size                  4 ; Percent
        font-weight                400
        marker-size                0.4
        background-opacity         0.4
        compute-strokes            (fn [color]
                                     [{:width wide-stroke-width
                                       :color (comm/contrasting color)}
                                      {:width narrow-stroke-width
                                       :color color}])
        magnify                    (fn [scale]
                                     (->> scale (* 1.5) (+ 1)))]
    (svg/g
     :flight (cell= (:name flight))
     (cell-let [{:keys [waypoints]} flight]
       (let [magnification (cell= (* map-flight-path-scale (if highlighted? 1.4 1.2)))
             strokes       (cell= (compute-strokes color))
             anticolor     (cell= (comm/contrasting color))]
         (svg/g
          :debug "waypoint markers and lines"
          :svg/class (formula-of [highlighted?]
                       (when highlighted? comm/pulse-class))
          ;; TODO: Steerpoint lines
          ;; The flight path route itself
          (svg/g
           :debug "waypoint lines"
           (for-tpl [stroke strokes]
             (cell-let [{:keys [width color]} stroke]
               (svg/path
                :stroke color
                :stroke-width (cell= (-> width (* magnification) (/ map-zoom)))
                :fill "none"
                :pointer-events "none"
                :opacity opacity
                :d (formula-of [mission waypoints]
                     (waypoints->path mission waypoints))))))
          (svg/g
           :debug "waypoint markers"
           :transform (formula-of [map-zoom]
                        (comm/svg-scale (/ 1.0 map-zoom) (/ 1.0 map-zoom)))
           (for-tpl [indexed (cell= (map-indexed vector waypoints))]
             (cell-let [[ordinal waypoint] indexed
                        {:keys [action grid-x grid-y]} waypoint
                        {:keys [x y]} (cell= (coords/fgrid->weather mission grid-x grid-y))]
               (let [style {:pointer-events "none"
                            :fill           "none"
                            :opacity        opacity}]
                 (svg/g
                  :debug "waypoint markers"
                  :transform (formula-of [x y map-zoom]
                               (comm/svg-translate (* x map-zoom) (* y map-zoom)))
                  (for-tpl [stroke strokes]
                    (svg/g
                     :debug "waypoint marker"
                     (cell-let [{:keys [width color]} stroke]
                       (let [r       (formula-of [magnification] (/ (* marker-size magnification) 2))
                             style*  (assoc style
                                            :opacity opacity
                                            :stroke color
                                            :stroke-width (cell= (* 0.6 width magnification)))
                             circle? (formula-of [waypoint]
                                       (-> waypoint :flags (has-flag? c/WPF_TARGET) not))]
                         (if-tpl circle?
                           (svg/circle
                            style*
                            :cx 0
                            :cy 0
                            :r r)
                           (comm/triangle
                            style*
                            :r r))))))
                  (let [show-labels?  (cell true)
                        show-numbers? (cell true)]
                    (let [txt          (formula-of [waypoint action show-labels? show-numbers? ordinal]
                                         (cond
                                           (and show-labels? (-> waypoint :flags (has-flag? c/WPF_ALTERNATE)))
                                           "Alternate Field"

                                           (and show-labels? (= action c/WP_REFUEL))
                                           (when show-labels? "Tanker")

                                           :else
                                           (when show-numbers? (inc ordinal))))
                          bbox-trigger (formula-of [txt magnification]
                                         [txt magnification])]
                      (with-bbox :w tw :h th :x tx :y ty :watch bbox-trigger
                        [^js/SVGTextElement t (svg/text
                                               :stroke color
                                               :stroke-width (cell= (* label-stroke-width magnification))
                                               :fill color
                                               :font-size (cell= (str (* font-size magnification) "%"))
                                               :text-anchor "middle"
                                               :opacity opacity
                                               :x 0
                                               :y (cell= (- (* marker-size magnification)))
                                               txt)]
                        (when-tpl (cell= (some? txt))
                          (svg/g
                           :debug "waypoint label"
                           :transform (cell= (comm/svg-scale (/ map-text-scale map-flight-path-scale)))
                           (when-tpl map-show-text-background?
                             (svg/rect
                              :x tx
                              :y ty
                              :width tw
                              :height th
                              :fill anticolor
                              :opacity background-opacity))
                           t))))))))))))))))

(defn overlay
  [{:keys [checked-flights highlighted-flight mission selected-teams] :as state}]
  (let [displayed-flights (formula-of [checked-flights highlighted-flight mission selected-teams]
                            (->> checked-flights
                                 (filter #(selected-teams (:owner %)))
                                 set))]
    (svg/g
     :section "flights-overlay"
     (svg/g
      :section "checked-flight-paths"
      (for-tpl [flight (cell= (disj displayed-flights highlighted-flight))]
        (flight-path-overlay flight state))
      (when-tpl highlighted-flight
        (svg/g
         :section "highlighted-flight-path"
         (flight-path-overlay highlighted-flight state)))))))

(defn create
  [mission {:keys [map-zoom map-text-scale visible-teams] :as opts}]
  (let [map-flight-path-size  (cell 0)
        map-flight-path-scale (cell= (Math/pow 4.0 map-flight-path-size))
        tabbed-flights        (cell {})
        checked-flights       (cell #{})
        highlighted-flight    (cell nil)
        display               (cell {})
        state                 (merge opts
                                     {:mission               mission
                                      :checked-flights       checked-flights
                                      :highlighted-flight    highlighted-flight
                                      :display               display
                                      :all-flights           (cell= (::mission/flights mission))
                                      :selected-teams        (cell #{})
                                      :tabbed-flights        tabbed-flights
                                      :map-flight-path-size  map-flight-path-size
                                      :map-flight-path-scale map-flight-path-scale})
        watchable             (formula-of [mission visible-teams]
                                [mission visible-teams])]
    (do-watch watchable
      ::reset-on-mission-change
      (fn [_ [mission visible-teams]]
        (with-time
          "updating flights section"
          (dosync
           (reset! (:display state) {})
           (reset! (:highlighted-flight state) nil)
           (reset! (:checked-flights state) #{})
           (reset! (:selected-teams state)
                   (let [last-allied (->> (mission/last-player-team mission)
                                          (mission/side mission)
                                          (mission/teams-for-side mission)
                                          set)]
                     (if (->> visible-teams
                              (set/intersection last-allied)
                              empty?)
                       visible-teams
                       last-allied)))))))
    (assoc state
           :controls-fn (fn [scroll-container]
                          (controls (assoc state :scroll-container scroll-container)))
           :map-display-controls-fn #(flight-path-display-controls % state)
           :overlay-fn (fn [] (overlay state))
           :tab-body-fn (fn [data]
                          (flight-details {:mission         mission
                                           :flight           (cell= (::flight data))
                                           :display          display
                                           :tabbed-flights   tabbed-flights
                                           :checked-flights checked-flights}))
           :tab-title-fn (fn [data selected?]
                           (div
                            :mouseenter #(reset! highlighted-flight (-> @data ::flight))
                            :mouseleave #(reset! highlighted-flight nil)
                            (comm/centered-image mission
                                                 (-> data ::flight ::mission/squadron-image cell=)
                                                 19)
                            (span
                             :css (formula-of [mission data mission-category-colors display selected?]
                                    (let [flight (::flight data)
                                          color  (if-let [c (get-in display [(:camp-id flight) :color])]
                                                   c
                                                   (flight-mission-color mission
                                                                         flight
                                                                         mission-category-colors))]
                                      {:background     color
                                       :border-width   (px 1)
                                       :border-color   (if selected? "white" "black")
                                       :border-style   "solid"
                                       :display        "inline-block"
                                       :vertical-align "middle"
                                       :margin-left    (px 5)
                                       :width          (px 13)
                                       :height         (px 14)}))
                             " ")
                            (span
                             :css {:vertical-align "middle"
                                   :margin-left    (px 5)}
                             (cell= (:title data)))
                            (span
                             :css {:color       "red"
                                   :float       "right"
                                   :margin-left "5px"
                                   :font-weight "bold"
                                   :font-size   (pct 90)}
                             :click (fn [_]
                                      (reset! highlighted-flight nil)
                                      (swap! tabbed-flights
                                             dissoc
                                             (:id @data)))
                             "X")))
           :tabs (formula-of [tabbed-flights]
                   (sort-by :title
                            (for [[id flight] tabbed-flights]
                              {:id      id
                               :title   (:name flight)
                               ::flight flight}))))))


(defn briefing-data
  "Returns data for saving in a briefing file."
  [layer]
  (let [{:keys [display tabbed-flights checked-flights]} layer]
    {:display         @display
     :tabbed-flights  (set (keys @tabbed-flights))
     ;; Wish I would have used IDs instead of the flights themselves
     :checked-flights (set (map :id @checked-flights))}))

(defn load-briefing-data
  "Given briefing data, update the layer with the state from it."
  [layer data]
  (let [{:keys [display tabbed-flights checked-flights mission]} layer]
    (dosync
     (.log js/console "Using data" (pr-str (select-keys  data [:display :tabbed-flights :checked-flights])))
     (reset! display (:display data))
     (reset! tabbed-flights (let [flights (->> @mission
                                               ::mission/flights
                                               (filter #(contains? (:tabbed-flights data) (:id %))))]
                              (zipmap (map :id flights) flights)))
     (reset! checked-flights
             (->> @mission
                  ::mission/flights
                  (filter #(contains? (:checked-flights data) (:id %)))
                  set)))))
