(ns roll.sente
  (:require [taoensso.timbre :refer [info]]
            [taoensso.sente  :as sente]
            [integrant.core  :as ig]
            [taoensso.sente.packers.transit :as st]
            [roll.util :as u]))



(defonce sente-fns nil)


(defn send-msg [& args]
  (some-> (:chsk-send! sente-fns)
          (apply args)))

(defn connected-uids []
  (:connected-uids sente-fns))




;; add new methods for custom events
(defmulti event-msg-handler :id)


(defmethod event-msg-handler
  :default         ; Default/fallback case (no other matching handler)
  [{:as ev-msg :keys [event id ?data ring-req ?reply-fn send-fn]}]
  (let [session (:session ring-req)
        uid     (:uid     session)]
    (when ?reply-fn
      (?reply-fn {:umatched-event event}))))



(defn broadcast [event]
  (when-let [uids (some-> (connected-uids) deref :any)]
    ;;(info "broadcasting data to" (count uids) "clients...")
    (doseq [uid uids]
      (send-msg uid event))))



(defn get-sch-adapter []
  (when-let [server-name
             (cond
               (resolve 'org.httpkit.server/run-server)
               'http-kit

               (resolve 'nginx.clojure.embed/run-server)
               'nginx-clojure

               (resolve 'aleph.http/start-server)
               'aleph)]
    
    (let [sym (->> server-name (str "taoensso.sente.server-adapters.") symbol)]
      (require sym)
      ((resolve (symbol (str sym "/get-sch-adapter")))))))




(defn get-packer [& [{:keys [write-handlers read-handlers]}]]
  (st/->TransitPacker
   :json
   {:handlers (into {} (apply merge write-handlers))}
   {:handlers (into {} (apply merge read-handlers))}))




(defn init-sente [& [{:as opts :keys [packer]}]]
  (when-let [sch-adapter (get-sch-adapter)]
    (let [{:keys [ch-recv send-fn connected-uids
                  ajax-post-fn ajax-get-or-ws-handshake-fn]}
        
          (sente/make-channel-socket!
           sch-adapter
           (merge
            {:packer (get-packer packer)
             :user-id-fn #(:client-id %)}
            (select-keys opts [:handshake-data-fn])))]

      {:ring-ajax-post                ajax-post-fn
       :ring-ajax-get-or-ws-handshake ajax-get-or-ws-handshake-fn
       :ch-chsk                       ch-recv
       :chsk-send!                    send-fn
       :connected-uids                connected-uids})))




(defn start-sente [& [opts]]
  (info "starting roll/sente:")
  (info (u/spp opts))
  
  (let [{:as opts :keys [handler]
         :or {handler event-msg-handler}} (u/resolve-syms opts)]
    
    (when-let [fns (init-sente
                    (select-keys opts [:handshake-data-fn :packer]))]
      (assoc fns
             :stop-fn (sente/start-server-chsk-router! (:ch-chsk fns) handler)
             ;; needed by ring handler
             :routes [(or (:path opts) "/chsk")
                      {:get  (:ring-ajax-get-or-ws-handshake fns)
                       :post (:ring-ajax-post fns)}]))))




(defmethod ig/init-key :roll/sente [_ opts]
  (alter-var-root #'sente-fns (constantly (start-sente opts))))



(defmethod ig/halt-key! :roll/sente [_ {:keys [stop-fn]}]
  (info "stopping roll/sente...")
  (alter-var-root #'sente-fns (constantly nil))
  (when stop-fn
    (stop-fn)))


 add new methods for custom events
 Default/fallback case (no other matching handler)
(info "broadcasting data to" (count uids) "clients...")
 needed by ring handler
(ns roll.sente
  (:require [taoensso.timbre :refer [info]]
            [taoensso.sente  :as sente]
            [integrant.core  :as ig]
            [taoensso.sente.packers.transit :as st]
            [roll.util :as u]))



(defonce sente-fns nil)


(defn send-msg [& args]
  (some-> (:chsk-send! sente-fns)
          (apply args)))

(defn connected-uids []
  (:connected-uids sente-fns))




(defmulti event-msg-handler :id)


(defmethod event-msg-handler
  [{:as ev-msg :keys [event id ?data ring-req ?reply-fn send-fn]}]
  (let [session (:session ring-req)
        uid     (:uid     session)]
    (when ?reply-fn
      (?reply-fn {:umatched-event event}))))



(defn broadcast [event]
  (when-let [uids (some-> (connected-uids) deref :any)]
    (doseq [uid uids]
      (send-msg uid event))))



(defn get-sch-adapter []
  (when-let [server-name
             (cond
               (resolve 'org.httpkit.server/run-server)
               'http-kit

               (resolve 'nginx.clojure.embed/run-server)
               'nginx-clojure

               (resolve 'aleph.http/start-server)
               'aleph)]
    
    (let [sym (->> server-name (str "taoensso.sente.server-adapters.") symbol)]
      (require sym)
      ((resolve (symbol (str sym "/get-sch-adapter")))))))




(defn get-packer [& [{:keys [write-handlers read-handlers]}]]
  (st/->TransitPacker
   :json
   {:handlers (into {} (apply merge write-handlers))}
   {:handlers (into {} (apply merge read-handlers))}))




(defn init-sente [& [{:as opts :keys [packer]}]]
  (when-let [sch-adapter (get-sch-adapter)]
    (let [{:keys [ch-recv send-fn connected-uids
                  ajax-post-fn ajax-get-or-ws-handshake-fn]}
        
          (sente/make-channel-socket!
           sch-adapter
           (merge
            {:packer (get-packer packer)
             :user-id-fn #(:client-id %)}
            (select-keys opts [:handshake-data-fn])))]

      {:ring-ajax-post                ajax-post-fn
       :ring-ajax-get-or-ws-handshake ajax-get-or-ws-handshake-fn
       :ch-chsk                       ch-recv
       :chsk-send!                    send-fn
       :connected-uids                connected-uids})))




(defn start-sente [& [opts]]
  (info "starting roll/sente:")
  (info (u/spp opts))
  
  (let [{:as opts :keys [handler]
         :or {handler event-msg-handler}} (u/resolve-syms opts)]
    
    (when-let [fns (init-sente
                    (select-keys opts [:handshake-data-fn :packer]))]
      (assoc fns
             :stop-fn (sente/start-server-chsk-router! (:ch-chsk fns) handler)
             :routes [(or (:path opts) "/chsk")
                      {:get  (:ring-ajax-get-or-ws-handshake fns)
                       :post (:ring-ajax-post fns)}]))))




(defmethod ig/init-key :roll/sente [_ opts]
  (alter-var-root #'sente-fns (constantly (start-sente opts))))



(defmethod ig/halt-key! :roll/sente [_ {:keys [stop-fn]}]
  (info "stopping roll/sente...")
  (alter-var-root #'sente-fns (constantly nil))
  (when stop-fn
    (stop-fn)))

