(ns weathergen.ui.layers.flights (:require [clojure.set :as set] [clojure.string :as str] [garden.stylesheet :as css] [goog.string :as gstring] [goog.string.format] [hoplon.svg :as svg] [taoensso.timbre :as log :include-macros true :refer ^{:line 11, :column 21} (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?]] [javelin.core :refer [->Cell input? cell cell? destroy-cell! lift lens? set-formula! constant? cell-doseq* deref* set-cell! lens formula? alts! dosync* cell-map formula]] [hoplon.jquery :refer [set-attributes! check-val! set-styles! text-val!]] [hoplon.core :refer [form audio input menuitem hgroup do! timeout $text base h1 set-attributes! embed shadow h3 body keygen progress main cite on-page-load i p nav ruby a menu blockquote img $comment span track data u dl select html thead del fieldset aside figure figcaption q on! bdi append-child! video address caption dd rp hr tbody table html-var add-initfn! pre ul replace-child! html-time html-map sup dfn sub mark script button wbr insert-before! strong normalize-class li dt rtc td tr section th optgroup bust-cache iframe remove-child! legend em kbd spliced html-object article abbr template multicol prerendering? <!-- source output set-styles! route-cell header datalist tfoot s ins footer title h5 canvas param div option summary samp small style textarea loop-tpl* h4 head ol details col label picture rt when-dom h6 link page-load colgroup meter html-meta static-elements bdo --> b attribute? code dialog noframes do-watch noscript safe-nth h2 area br]]) (:require-macros [weathergen.cljs.macros :refer [with-bbox with-time formula-of]] [javelin.core :refer [with-let mx2 dosync cell= set-cell!= prop-cell cell-doseq formulet defc defc= macroexpand-all mx formula-of cell-let]] [hoplon.core :refer [definterval text elem+ cache-key with-timeout defelem+ when-tpl static sexp defelem elem def-values if-tpl cond-tpl with-page-load for-tpl with-dom case-tpl loop-tpl with-interval with-init!]]))



























;; (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."
  [flight]
  (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))]
    (div
     (div
      :css {:font-weight "bold"
            :font-size   "120%"}
      (cell= (:name flight)))
     (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")
     (grids/table-grid
      :data (-> flight ::mission/waypoints cell=)
      :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."
  [detailed-flight scroll-container]
  (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"}
         (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")
         (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 flight))]
             (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 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 "3px"}
      (div
       :css {:display   "flex"
             :flex-wrap "wrap"
             :max-width "400px"}
       (for [[id name] mission-category-names]
         (div
          :css {:white-space "nowrap"}
          (inl
           :css {:margin-right "3px"}
           :click (constantly false)
           (comm/color-picker
            :value (formula-of [mission-category-colors] (mission-category-colors id))
            :change #(swap! mission-category-colors assoc id @%)))
          (inl :css {:width "70px"} 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))
     (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 %)))))
         :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))]
                                                  [(input :camp-id camp-id
                                                          :type "checkbox"
                                                          :value visible?)
                                                   (inl
                                                    :css {:margin-right   "2px"
                                                          :vertical-align "middle"}
                                                    :click (constantly false)
                                                    (comm/color-picker
                                                     :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 detailed-flight scroll-container)}
                     :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  (col "Aircraft"
                                     :airframe
                                     (fn [mission flight]
                                       (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)))]
                         (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))
        state                (merge opts
                                    {:mission              mission
                                     :checked-flights      (cell #{})
                                     :highlighted-flight   (cell nil)
                                     :display              (cell {})
                                     :all-flights          (cell= (some->> mission mission/flights))
                                     :selected-teams       (cell #{})
                                     :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 (overlay state))))

