 (defn set-pad-color
   "Set the color of one of the 64 touch pads to the closest
   approximation available for a desired color."
-  [x y color]
+  [controller x y color]
   {:pre [(<= 0 x 7) (<= 0 y 7)]}
   (let [note (+ 36 x (* y 8))]  ;; Calculate note from grid coordinates
-    (midi/midi-note-on @user-port-out note (velocity-for-color color))))
+    (midi/midi-note-on (:user-port-out controller) note (velocity-for-color color))))
 
 (def monochrome-button-states
   "The control values and modes for a labeled button which does not

   
   })

(defn set-button-state
  "Set one of the labeled buttons to a particular state, and, if
  supported, color."
  ([button state]
   (set-button-state button state :amber))
  ([button state color-key]
   (let [base-value ((keyword state) monochrome-button-states)
         color-shift (or (when (and (= (:kind button) :color)
                                    (not= state :off))
                           ((keyword color-key) color-button-colors))
                         0)]
     (midi/midi-control @user-port-out (:control button)
                        (+ base-value color-shift)))))

(defn set-track-select-state
  "Set one of the top-row pads to a particular state and color."
  ([x state]
   (set-track-select-state x state :amber))
  ([x state color-key]
   {:pre [(<= 0 x 7)]}
   (let [base-value ((keyword state) monochrome-button-states)
         color-shift (or (when-not (= state :off)
                           ((keyword color-key) color-button-colors))
                         0)]
     (midi/midi-control @user-port-out (+ x 20) (+ base-value color-shift)))))

(defn set-scene-launch-color
  "Set the color of one of the 8 scene-launch touch pads (right above
  the 8x8 pad of larger, velocity sensitive, pads) to the closest
  approximation available for a desired color."
  [x color]
  {:pre [(<= 0 x 7)]}
  (let [control (+ 102 x)]  ;; Calculate controller number
    (midi/midi-control @user-port-out control (velocity-for-color color))))

(defn set-display-line
  "Sets a line of the text display."
  [line bytes]
  {:pre [(<= 1 line 4)]}
  (let [message (concat [240 71 127 21 (+ line 23) 0 69 0]
                        (take 68 (concat (map int bytes) (repeat 32)))
                        [247])]
    (midi/midi-sysex @live-port-out message)))
 
(defn clear-display-line
  "Clears a line of the text display."
  [line]
  {:pre [(<= 1 line 4)]}
  (midi/midi-sysex @live-port-out [240 71 127 21 (+ line 27) 0 0 247]))

(defn show-labels
  "Illuminates all buttons with text labels"
  ([]
   (show-labels :bright :amber))
  ([state]
   (show-labels state :amber))
  ([state color]
   (doseq [[_ button] control-buttons]
     (set-button-state button state color))))

(declare clear-interface)


  "Render a frame of the welcome animation, or if it is done, start
  the main interface update thread, and terminate the task running the
  animation."
  [controller counter task]
  (try
    (cond
      (< @counter 8)
      (doseq [y (range 0 (inc @counter))]
        (let [color (com.evocomputing.colors/create-color
                     :h 0 :s 0 :l (max 10 (- 50 (/ (* 50 (- @counter y)) 4))))]
          (set-pad-color controller 3 y color)
          (set-pad-color controller 4 y color)))

      (< @counter 12)
      (doseq [x (range 0 (- @counter 7))
              y (range 0 8)]
        (let [color (com.evocomputing.colors/create-color
                     :h 340 :s 100 :l (if (= x (- @counter 8)) 75 50))]
          (set-pad-color controller (- 3 x) y color)
          (set-pad-color controller (+ 4 x) y color)))

      (< @counter 15)
      (doseq [y (range 0 8)]
        (let [color (com.evocomputing.colors/create-color
                     :h (* 13 (- @counter 11)) :s 100 :l 50)]
          (set-pad-color controller (- @counter 7) y color)
          (set-pad-color controller (- 14 @counter) y color)))

      (= @counter 15)
      (show-labels controller :bright :amber)
      
      (= @counter 16)
      (doseq [x (range 0 8)]
        (set-track-select-state controller x :bright :amber))
      
      (= @counter 17)
      (doseq [x (range 0 8)]
        (set-scene-launch-color controller x
                                (com.evocomputing.colors/create-color :h 45 :s 100 :l 50))
        (set-track-select-state controller x :bright :red))

      (< @counter 26)
      (doseq [x (range 0 8)]
        (let [color (com.evocomputing.colors/create-color
                     :h (+ 60 (* 40 (- @counter 18))) :s 100 :l 50)]
          (set-pad-color controller x (- 25 @counter) color)))
      
      (= @counter 26)
      (do
        (show-labels controller :dim :amber)
        (doseq [x (range 0 8)]
          (set-track-select-state controller x :off)))

      (= @counter 27)
      (doseq [x (range 0 8)]
          (set-scene-launch-color controller x off-color))

      (< @counter 36)
      (doseq [x (range 0 8)]
        (set-pad-color controller x (- 35 @counter) off-color))
      
      :else
      (do
        (clear-interface controller)
        (reset! (:task controller) (at-at/every (:refresh-interval controller)
                                                #(update-interface controller)
                                                controllers/pool))
        (at-at/kill @task)))
    (catch Throwable t
      (warn t "Animation frame failed")))

  (swap! counter inc))

(defn welcome-animation
  "Provide a fun animation to make it clear the Push is online."
  [controller]
  (set-display-line controller 0 (concat (repeat 24 \space) (seq "Welcome toAfterglow")))
  (set-display-line controller 2 (concat (repeat 27 \space)
                              (seq (str "version" (env  :afterglow-version)))))
  (let [counter (atom 0)
        task (atom nil)]
    (reset! task (at-at/every 30 #(welcome-frame controller counter task)
                              controllers/pool))))

(defn clear-interface
  "Clears the text display and all illuminated buttons and pads."
  [controller]
  (doseq [line (range 4)]
    (clear-display-line controller line))
  (doseq [x (range 8)]
    (set-track-select-state controller x :off)
    (set-scene-launch-color controller x off-color)
    (doseq [y (range 8)]
      (set-pad-color controller x y off-color)))
  (doseq [[_ button] control-buttons]
    (set-button-state controller button :off)))

(defonce ^{:doc "Counts the controller bindings which have been made,
  so each can be assigned a unique ID."}
  controller-counter (atom 0))

(defonce ^{:doc "Controllers which are currently bound to shows,
  indexed by the controller binding ID."}
  active-bindings (atom {}))

(defn bind-to-show
  "Establish a connection to the Ableton Push, for managing the
  given show. Initialize the display, and start the UI
  updater thread. Since SysEx messages are required for updating the
  display, assumes that if you are on a Mac, you have installed
  [mmj](http://www.humatic.de/htools/mmj.htm) to provide a
  working implementation."
  {:doc/format :markdown}
  [show & {:keys [prefix refresh-interval]
           :or {prefix (when (re-find #"Mac" (System/getProperty "os.name"))
                         "Ableton Push - ")
                refresh-interval show/default-refresh-interval}}]
  (let [controller
        {:id (swap! controller-counter inc)
         :show show
         :refresh-interval refresh-interval
         :user-port-out (midi/midi-out (str prefix "User Port"))
         :live-port-out (midi/midi-out (str prefix "Live Port"))
         :task (atom nil)
         :last-display (vec (for [_ (range 4)] (byte-array (take 68 (repeat 32)))))
         :next-display (vec (for [_ (range 4)] (byte-array (take 68 (repeat 32)))))
         :last-text-buttons (atom {})
         :next-text-buttons (atom {})
         :metronome-mode (atom true)
         }]
    (clear-interface controller)
    (welcome-animation controller)
    (swap! active-bindings assoc (:id controller) controller)
    controller))

(defn deactivate
  "Deactivates a controller interface, killing its update thread
  and removing its MIDI listeners."
  [controller]
  (swap! (:task controller) (fn [task]
                              (when task (at-at/kill task))
                              nil))
  (clear-interface controller)
  ;; TODO: Remove any MIDI listeners which have been set up.

  (swap! active-bindings dissoc (:id controller)))

(defn deactivate-all
  "Deactivates all controller bindings which are currently active."
  []
  (doseq [[_ controller] @active-bindings]
    (deactivate controller)))
