<commit_before>(ns kixi.hecuba.charts
  (:require-macros [cljs.core.async.macros :refer [go-loop go]])
  (:require
   [om.core :as om :include-macros true]
   [om.dom :as dom :include-macros true]
   [cljs.core.async :refer [<! >! chan put! sliding-buffer close! pipe map< filter< mult tap map>]]
   [ajax.core :refer (GET POST)]
   [clojure.string :as str]
   [cljs.reader :as reader]
   [kixi.hecuba.common :refer (interval)]
   [kixi.hecuba.bootstrap :as bootstrap]
   [kixi.hecuba.multiple-properties-charts :as properties]
   [kixi.hecuba.history :as history]))

(enable-console-print!)

(def data-model
  (atom
   {:properties {:data []}
    :sensors {:data []}
    :selected {:properties #{} :sensors #{}}
    :chart {:unit ""
            :range {}
            :sensors #{}
            :measurements []
            :message ""}}))

(defn chart-feedback-box [cursor owner]
  (om/component
   (dom/div nil cursor)))

(defn should-be-checked? [data sensor]
  (let [selected (filter (fn [s] (= sensor s)) (:sensors (:selected data)))]
    (not (empty? selected))))

(defn- select-sensor [data history value]
  (let [[device-id type unit entity-id] (str/split value #"-")
        new-sensor                      (str/join "-" [device-id type entity-id])
        current-unit                    (-> @data :chart :unit)]

    (if (or (empty? current-unit) (= current-unit unit))
      (do 
        (om/update! data [:selected :sensors] (conj (:sensors (:selected @data)) new-sensor))
        (om/update! data [:chart :sensors] (:sensors (:selected @data)))
        (om/update! data [:chart :unit] unit)
        (om/update! data [:sensors :data] (into [] (map (fn [s]
                                                          (if (and (= (:device-id s) device-id)
                                                                   (= (:type s) type))
                                                            (assoc-in s [:checked] true)
                                                            s)) (:data (:sensors @data)))))
        (update-measurements data {:selection-key :sensors :checked true :new-selected new-sensor})
        (history/update-token-ids! history :properties (str/join ";" (:properties (:selected @data))))
        (history/update-token-ids! history :sensors (str/join ";" (:sensors (:selected @data))))
        (om/update! data [:chart :message] ""))
      (om/update! data [:chart :message] "Selected sensors must be of the same unit."))))

(defn- deselect-sensor [data history value]
  (let [[device-id type unit entity-id] (str/split value #"-")
        new-sensor                      (str/join "-" [device-id type entity-id])]
       (om/update! data [:selected :sensors] (disj (:sensors (:selected @data)) new-sensor))
       (om/update! data [:chart :sensors] (:sensors (:selected @data)))
       (om/update! data [:chart :message] "")
       (update-measurements data {:selection-key :sensors :checked false :new-selected new-sensor})
       (om/update! data [:sensors :data] (into [] (map (fn [s]
                                                         (if (and (= (:device-id s) device-id)
                                                                  (= (:type s) type))
                                                           (assoc-in s [:checked] false)
                                                           s)) (:data (:sensors @data)))))
       (history/update-token-ids! history :sensors (str/join ";" (:sensors (:selected @data))))
       (history/update-token-ids! history :properties (str/join ";" (:properties (:selected @data))))
       (when (empty? (-> @data :selected :sensors))
         (om/update! data [:chart :unit] ""))))

(defn- select-property [data history value]
  (let [all-properties (get-in @data [:properties :data])
        new-property   (into {} (filter #(= (:id %) value) all-properties))
        new-devices    (when-let [devices (:devices new-property)]
                         (map (fn [[k v]]
                                (merge {:id k} (reader/read-string v))) (reader/read-string devices)))
        new-sensors    (mapcat (fn [device]
                                 (map (fn [reading]
                                        (let [device-id (:id device)
                                              type      (get reading "type")
                                              entity-id (:entity-id device)]
                                          {:device-id device-id
                                           :type type
                                           :entity-id entity-id
                                           :unit (get reading "unit")
                                           :description (:description device)
                                           :checked (should-be-checked? data
                                                                        (str/join "-" [device-id type entity-id]))}
                                          )) (:readings device))) new-devices)]
    (om/update! data [:properties :data] (into [] (map (fn [p]
                                                         (if (= (:id p) value)
                                                           (assoc-in p [:checked] true)
                                                           p)) (:data (:properties @data)))))
    (om/update! data [:sensors :data] (concat (:data (:sensors @data)) new-sensors))
    (om/update! data [:selected :properties] (conj (:properties (:selected @data)) value))
    (history/update-token-ids! history :properties (str/join ";" (:properties (:selected @data))))))

(defn- deselect-property [data history value]
  (om/update! data [:selected :properties] (disj (:properties (:selected @data)) value))
  (om/update! data [:properties :data] (into [] (map (fn [p]
                                                       (if (= (:id p) value)
                                                         (assoc-in p [:checked] false)
                                                         p)) (:data (:properties @data)))))
  ;; TOFIX history clears everything below the updated element, which does not work with multiple selections. need to modify history component to support that. in the meantime history is (re)set on each selection
  (history/update-token-ids! history :properties (str/join ";" (:properties (:selected @data))))
  (history/update-token-ids! history :sensors (str/join ";" (:sensors (:selected @data))))
  (om/update! data [:sensors :data] (remove #(= (:entity-id %) value) (:data (:sensors @data))))
  (om/update! data [:chart :sensors] (:sensors (:selected @data)))
  (when (empty? (-> @data :selected :sensors))
    (om/update! data [:chart :unit] "")
    (om/update! data [:chart :message] "")))

(defn init-from-history [data history current-ids]
  (let [ids                   (-> current-ids :ids)
        [start-date end-date] (-> current-ids :search)
        properties            (-> ids :properties)
        sensors               (-> ids :sensors)]
    (when-not (empty? properties) (doseq [property (str/split properties #";")]
                                    (select-property data history property)))
    (when-not (empty? sensors) (doseq [sensor (str/split sensors #";")]
                                 (select-sensor data history sensor)))
    (when-not (nil? start-date) (om/update! data [:chart :range] {:start-date start-date :end-date end-date}))))

(defn get-properties [projects data history current-ids]
  (let [counter (atom 0)]
    (doseq [project projects]
      (GET (str "/4/projects/" (:id project) "/properties")
           {:handler #(om/update! data [:properties :data] (into [] (concat (-> @data :properties :data) %)))
            :headers {"Accept" "application/json"}
            :response-format :json
            :keywords? true}))))

(defn get-measurements [data sensors start-date end-date]
  (when (and start-date end-date (not (empty? sensors)))
    (doseq [sensor sensors]
      (let [[device-id type entity-id] (str/split sensor #"-")
            resource (case (interval start-date end-date)
                       :raw "measurements"
                       :hourly-rollups "hourly_rollups"
                       :daily-rollups "daily_rollups")
            url      (str "/4/entities/" entity-id "/devices/" device-id "/" resource "/" type "?startDate="
                          start-date "&endDate=" end-date)
            sensor (str/join "-" [device-id type])]
        
        (GET url {:handler #(om/update! data [:chart :measurements]
                                        (concat (:measurements (:chart @data))
                                                (into []
                                                      (map (fn [m] 
                                                             (assoc m "sensor" sensor "entity-id" entity-id)) 
                                                           (get % "measurements")))))})))))

(defmulti update-measurements (fn [data checked] (:selection-key checked)))

(defmethod update-measurements :properties [data {:keys [checked new-selected]}]
  (om/update! data [:chart :measurements] (remove #(= (get % "entity-id") new-selected) (:measurements (:chart @data))))
  ;; TODO Clears sensors belonging to deselected property - there must be a better way of doing this.
  (let [sensors-to-keep (into #{} (remove (fn [s] (let [[device-id type entity-id] (str/split s #"-")]
                                                    (= new-selected entity-id))) (:sensors (:selected @data))))]
    (om/update! data [:selected :sensors]  sensors-to-keep)
    (om/update! data [:chart :sensors] sensors-to-keep)))

(defmethod update-measurements :range [data checked]
  (let [chart (:chart @data)
        sensors (:sensors chart)
        {:keys [start-date end-date]} (:range chart)]
    (get-measurements data sensors start-date end-date)))

(defmethod update-measurements :sensors [data {:keys [checked new-selected]}]
  (if checked
    (let [chart      (:chart @data)
          sensors    (:sensors chart)
          start-date (:start-date (:range chart))
          end-date   (:end-date (:range chart))]
      (get-measurements data sensors start-date end-date))
    (let [[device-id type entity-id] (str/split new-selected #"-")
          sensor                     (str/join "-" [device-id type])]
      (om/update! data [:chart :measurements] (remove #(= (get % "sensor") sensor) (:measurements (:chart @data)))))))



(defmulti update-form (fn [click data history] (:selection-key click)))

(defmethod update-form :properties [click data history]
  (let [checked (:checked click)
        value   (:value click)]
    (if checked
      (select-property data history value)
      (deselect-property data history value))
    (update-measurements data {:selection-key :properties :checked nil :new-selected value})))

(defmethod update-form :sensors [click data history]
  (let [checked (:checked click)
        value   (:value click)]  
    (if checked
      (select-sensor data history value)
      (deselect-sensor data history value))))

(defmethod update-form :range [click data history]<commit_msg>Correct order of functions.<commit_after>(ns kixi.hecuba.charts
  (:require-macros [cljs.core.async.macros :refer [go-loop go]])
  (:require
   [om.core :as om :include-macros true]
   [om.dom :as dom :include-macros true]
   [cljs.core.async :refer [<! >! chan put! sliding-buffer close! pipe map< filter< mult tap map>]]
   [ajax.core :refer (GET POST)]
   [clojure.string :as str]
   [cljs.reader :as reader]
   [kixi.hecuba.common :refer (interval)]
   [kixi.hecuba.bootstrap :as bootstrap]
   [kixi.hecuba.multiple-properties-charts :as properties]
   [kixi.hecuba.history :as history]))

(enable-console-print!)

(def data-model
  (atom
   {:properties {:data []}
    :sensors {:data []}
    :selected {:properties #{} :sensors #{}}
    :chart {:unit ""
            :range {}
            :sensors #{}
            :measurements []
            :message ""}}))

(defn chart-feedback-box [cursor owner]
  (om/component
   (dom/div nil cursor)))

(defn should-be-checked? [data sensor]
  (let [selected (filter (fn [s] (= sensor s)) (:sensors (:selected data)))]
    (not (empty? selected))))

(defn get-measurements [data sensors start-date end-date]
  (when (and start-date end-date (not (empty? sensors)))
    (doseq [sensor sensors]
      (let [[device-id type entity-id] (str/split sensor #"-")
            resource (case (interval start-date end-date)
                       :raw "measurements"
                       :hourly-rollups "hourly_rollups"
                       :daily-rollups "daily_rollups")
            url      (str "/4/entities/" entity-id "/devices/" device-id "/" resource "/" type "?startDate="
                          start-date "&endDate=" end-date)
            sensor (str/join "-" [device-id type])]
        
        (GET url {:handler #(om/update! data [:chart :measurements]
                                        (concat (:measurements (:chart @data))
                                                (into []
                                                      (map (fn [m] 
                                                             (assoc m "sensor" sensor "entity-id" entity-id)) 
                                                           (get % "measurements")))))})))))

(defmulti update-measurements (fn [data checked] (:selection-key checked)))

(defmethod update-measurements :properties [data {:keys [checked new-selected]}]
  (om/update! data [:chart :measurements] (remove #(= (get % "entity-id") new-selected) (:measurements (:chart @data))))
  ;; TODO Clears sensors belonging to deselected property - there must be a better way of doing this.
  (let [sensors-to-keep (into #{} (remove (fn [s] (let [[device-id type entity-id] (str/split s #"-")]
                                                    (= new-selected entity-id))) (:sensors (:selected @data))))]
    (om/update! data [:selected :sensors]  sensors-to-keep)
    (om/update! data [:chart :sensors] sensors-to-keep)))

(defmethod update-measurements :range [data checked]
  (let [chart (:chart @data)
        sensors (:sensors chart)
        {:keys [start-date end-date]} (:range chart)]
    (get-measurements data sensors start-date end-date)))

(defmethod update-measurements :sensors [data {:keys [checked new-selected]}]
  (if checked
    (let [chart      (:chart @data)
          sensors    (:sensors chart)
          start-date (:start-date (:range chart))
          end-date   (:end-date (:range chart))]
      (get-measurements data sensors start-date end-date))
    (let [[device-id type entity-id] (str/split new-selected #"-")
          sensor                     (str/join "-" [device-id type])]
      (om/update! data [:chart :measurements] (remove #(= (get % "sensor") sensor) (:measurements (:chart @data)))))))

(defn- select-sensor [data history value]
  (let [[device-id type unit entity-id] (str/split value #"-")
        new-sensor                      (str/join "-" [device-id type entity-id])
        current-unit                    (-> @data :chart :unit)]

    (if (or (empty? current-unit) (= current-unit unit))
      (do 
        (om/update! data [:selected :sensors] (conj (:sensors (:selected @data)) new-sensor))
        (om/update! data [:chart :sensors] (:sensors (:selected @data)))
        (om/update! data [:chart :unit] unit)
        (om/update! data [:sensors :data] (into [] (map (fn [s]
                                                          (if (and (= (:device-id s) device-id)
                                                                   (= (:type s) type))
                                                            (assoc-in s [:checked] true)
                                                            s)) (:data (:sensors @data)))))
        (update-measurements data {:selection-key :sensors :checked true :new-selected new-sensor})
        (history/update-token-ids! history :properties (str/join ";" (:properties (:selected @data))))
        (history/update-token-ids! history :sensors (str/join ";" (:sensors (:selected @data))))
        (om/update! data [:chart :message] ""))
      (om/update! data [:chart :message] "Selected sensors must be of the same unit."))))

(defn- deselect-sensor [data history value]
  (let [[device-id type unit entity-id] (str/split value #"-")
        new-sensor                      (str/join "-" [device-id type entity-id])]
       (om/update! data [:selected :sensors] (disj (:sensors (:selected @data)) new-sensor))
       (om/update! data [:chart :sensors] (:sensors (:selected @data)))
       (om/update! data [:chart :message] "")
       (update-measurements data {:selection-key :sensors :checked false :new-selected new-sensor})
       (om/update! data [:sensors :data] (into [] (map (fn [s]
                                                         (if (and (= (:device-id s) device-id)
                                                                  (= (:type s) type))
                                                           (assoc-in s [:checked] false)
                                                           s)) (:data (:sensors @data)))))
       (history/update-token-ids! history :sensors (str/join ";" (:sensors (:selected @data))))
       (history/update-token-ids! history :properties (str/join ";" (:properties (:selected @data))))
       (when (empty? (-> @data :selected :sensors))
         (om/update! data [:chart :unit] ""))))

(defn- select-property [data history value]
  (let [all-properties (get-in @data [:properties :data])
        new-property   (into {} (filter #(= (:id %) value) all-properties))
        new-devices    (when-let [devices (:devices new-property)]
                         (map (fn [[k v]]
                                (merge {:id k} (reader/read-string v))) (reader/read-string devices)))
        new-sensors    (mapcat (fn [device]
                                 (map (fn [reading]
                                        (let [device-id (:id device)
                                              type      (get reading "type")
                                              entity-id (:entity-id device)]
                                          {:device-id device-id
                                           :type type
                                           :entity-id entity-id
                                           :unit (get reading "unit")
                                           :description (:description device)
                                           :checked (should-be-checked? data
                                                                        (str/join "-" [device-id type entity-id]))}
                                          )) (:readings device))) new-devices)]
    (om/update! data [:properties :data] (into [] (map (fn [p]
                                                         (if (= (:id p) value)
                                                           (assoc-in p [:checked] true)
                                                           p)) (:data (:properties @data)))))
    (om/update! data [:sensors :data] (concat (:data (:sensors @data)) new-sensors))
    (om/update! data [:selected :properties] (conj (:properties (:selected @data)) value))
    (history/update-token-ids! history :properties (str/join ";" (:properties (:selected @data))))))

(defn- deselect-property [data history value]
  (om/update! data [:selected :properties] (disj (:properties (:selected @data)) value))
  (om/update! data [:properties :data] (into [] (map (fn [p]
                                                       (if (= (:id p) value)
                                                         (assoc-in p [:checked] false)
                                                         p)) (:data (:properties @data)))))
  ;; TOFIX history clears everything below the updated element, which does not work with multiple selections. need to modify history component to support that. in the meantime history is (re)set on each selection
  (history/update-token-ids! history :properties (str/join ";" (:properties (:selected @data))))
  (history/update-token-ids! history :sensors (str/join ";" (:sensors (:selected @data))))
  (om/update! data [:sensors :data] (remove #(= (:entity-id %) value) (:data (:sensors @data))))
  (om/update! data [:chart :sensors] (:sensors (:selected @data)))
  (when (empty? (-> @data :selected :sensors))
    (om/update! data [:chart :unit] "")
    (om/update! data [:chart :message] "")))

(defn init-from-history [data history current-ids]
  (let [ids                   (-> current-ids :ids)
        [start-date end-date] (-> current-ids :search)
        properties            (-> ids :properties)
        sensors               (-> ids :sensors)]
    (when-not (empty? properties) (doseq [property (str/split properties #";")]
                                    (select-property data history property)))
    (when-not (empty? sensors) (doseq [sensor (str/split sensors #";")]
                                 (select-sensor data history sensor)))
    (when-not (nil? start-date) (om/update! data [:chart :range] {:start-date start-date :end-date end-date}))))

(defn get-properties [projects data history current-ids]
  (let [counter (atom 0)]
    (doseq [project projects]
      (GET (str "/4/projects/" (:id project) "/properties")
           {:handler #(om/update! data [:properties :data] (into [] (concat (-> @data :properties :data) %)))
            :headers {"Accept" "application/json"}
            :response-format :json
            :keywords? true}))))

(defmulti update-form (fn [click data history] (:selection-key click)))

(defmethod update-form :properties [click data history]
  (let [checked (:checked click)
        value   (:value click)]
    (if checked
      (select-property data history value)
      (deselect-property data history value))
    (update-measurements data {:selection-key :properties :checked nil :new-selected value})))

(defmethod update-form :sensors [click data history]
  (let [checked (:checked click)
        value   (:value click)]  
    (if checked
      (select-sensor data history value)
      (deselect-sensor data history value))))

(defmethod update-form :range [click data history]
{'fraction_non_alphanumeric': 0.1471220123534151, 'fraction_numerical': 0.00026855250201414377, 'mean_word_length': 1.786952725458401, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 134, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7637764', 'n_tokens_mistral': 6136, 'n_tokens_neox': 5764, 'n_words': 1790}