(ns figwheel.client.socket
  (:require
   [goog.object :as gobj]
   [figwheel.client.utils :as utils]
   [cljs.reader :refer [read-string]]))

(defn get-websocket-imp []
  (or
   (gobj/get goog.global "FIGWHEEL_WEBSOCKET_CLASS")
   (gobj/get goog.global "WebSocket")
   (cond
     ;; TODO remove
     (utils/html-or-react-native-env?) (gobj/get js/window "WebSocket")
     (utils/node-env?) (try (js/require "ws")
                            (catch js/Error e
                              nil))
     ;; TODO remove
     (utils/worker-env?) (gobj/get js/self "WebSocket")
     :else nil)))


;; messages have the following formats

;; files-changed message
;; { :msg-name :files-changed
;;   :files    [{:file "/js/compiled/out/example/core.js",
;;               :type :javascript,
;;               :msg-name :file-changed,
;;               :namespace "example.core" }] }

;; css-files-changed message
;; there should really only be one file in here at a time
;; { :msg-name :css-files-changed
;;   :files    [{:file "/css/example.css",
;;               :type :css }] }

;; compile-failed message
;; { :msg-name :compile-failed
;;   :exception-data {:cause { ... lots of exception info ... } }}
;; the exception data is nested raw info obtained for the compile time
;; exception

(defonce message-history-atom (atom (list)))

(defonce socket-atom (atom false))

(defn send!
  "Send a end message to the server."
  [msg]
  (when @socket-atom
    (.send @socket-atom (pr-str msg))))

(defn close! []
  (set! (.-onclose @socket-atom) identity)
  (.close @socket-atom))

(defn handle-incoming-message [msg]
  (utils/debug-prn msg)
  (and (map? msg)
       (:msg-name msg)
       ;; don't forward pings
       (not= (:msg-name msg) :ping)
       (swap! message-history-atom
              conj msg)))

(defn open [{:keys [retry-count retried-count websocket-url build-id] :as opts}]
  (if-let [WebSocket (get-websocket-imp)]
    (do
      (utils/log :debug "Figwheel: trying to open cljs reload socket")
      (let [url (str websocket-url (if build-id (str "/" build-id) ""))
            socket (WebSocket. url)]
        (set! (.-onmessage socket) (fn [msg-str]
                                     (when-let [msg
                                                (read-string (.-data msg-str))]
                                       (#'handle-incoming-message msg))))
        (set! (.-onopen socket)  (fn [x]
                                   (reset! socket-atom socket)
                                   (when (utils/html-env?)
                                     (.addEventListener js/window "beforeunload" close!))
                                   (utils/log :debug "Figwheel: socket connection established")))
        (set! (.-onclose socket) (fn [x]
                                   (let [retried-count (or retried-count 0)]
                                     (utils/debug-prn "Figwheel: socket closed or failed to open")
                                     (when (> retry-count retried-count)
                                       (js/setTimeout
                                        (fn []
                                          (open
                                           (assoc opts :retried-count (inc retried-count))))
                                        ;; linear back off
                                        (min 10000 (+ 2000 (* 500 retried-count))))))))
        (set! (.-onerror socket) (fn [x] (utils/debug-prn "Figwheel: socket error ")))
        socket))
    (utils/log :debug
               (if (utils/node-env?)
                 "Figwheel: Can't start Figwheel!! Please make sure ws is installed\n do -> 'npm install ws'"
                 "Figwheel: Can't start Figwheel!! This browser doesn't support WebSockets"))))