(ns horizon.controls.widgets.tree.hierarchy-helpers-test
  (:require
   [cljs.test :refer-macros [deftest testing is use-fixtures async]]
   [horizon.controls.widgets.tree.hierarchy-helpers :as sut]
   [horizon.test-helpers.async-test-tools :as async-test-tools]
   [greenpowermonitor.test-doubles :as td]
   [cljs.core.async :as core.async]
   [horizon.common.logging :as log]
   [om.core :as om]))

(deftest selecting-node-classes
  (is (= "tree-node level-0" (sut/select-node-classes #{} 1 0 3)))
  (is (= "tree-node level-1" (sut/select-node-classes #{} 1 1 3)))
  (is (= "tree-node selected level-0" (sut/select-node-classes #{{:id 1}} 1 0 3)))
  (is (= "tree-node level-0" (sut/select-node-classes #{{:id 1}} 5 0 3)))
  (is (= "tree-node level-1 clickable" (sut/select-node-classes #{{:id 1}} 5 1 0))))

;: Handling branch-channel messages
;;----------------------------------
(deftest branch-channel-expanding-nodes-message
  (let [msg {:type :expand-node :value :some-value}
        channel (core.async/chan)]
    (async done
           (async-test-tools/expect-async-message
            channel
            :expected-message msg
            :done-fn done)
           (sut/handle-branch-channel-messages channel msg))))

(deftest branch-channel-selecting-alerts-message
  (let [msg {:type :select-alert}
        channel (core.async/chan)]
    (async done
           (async-test-tools/expect-async-message
            channel
            :expected-message msg
            :done-fn done)
           (sut/handle-branch-channel-messages channel msg))))

(deftest branch-channel-selecting-nodes-message
  (let [msg {:type :select-nodes :value :some-value}
        channel (core.async/chan)]
    (async done
           (async-test-tools/expect-async-message
            channel
            :expected-message msg
            :done-fn done)
           (sut/handle-branch-channel-messages channel msg))))

(deftest branch-channel-any-other-type-of-message-logs-an-error
  (td/with-doubles
    :spying [log/log-unknown-message-type]
    (let [msg {:type :any-other-type}
          channel :not-used-channel]

      (sut/handle-branch-channel-messages channel msg)

      (is (= [[::sut/handle-branch-channel-messages msg]]
             (td/calls-to log/log-unknown-message-type))))))

(deftest branch-channel-any-other-type-of-message-produces-no-messages-in-the-channel
  (let [msg {:type :any-other-type}
        channel (core.async/chan)]
    (async done
           (async-test-tools/expect-no-messages
            channel
            :done-fn done)
           (sut/handle-branch-channel-messages channel msg))))

;: Handling tree-channel-handler messages
;;----------------------------------
(deftest tree-channel-expanding-nodes-message
  (let [owner :some-owner
        msg {:type :expand-node :value :some-value}
        channel (core.async/chan)]
    (async done
           (async-test-tools/expect-async-message
            channel
            :expected-message msg
            :done-fn done)
           (sut/handle-tree-channel-messages owner channel msg))))

(deftest tree-channel-selecting-alerts-message
  (let [owner :some-owner
        msg {:type :select-alert}
        channel (core.async/chan)]
    (async done
           (async-test-tools/expect-async-message
            channel
            :expected-message msg
            :done-fn done)
           (sut/handle-tree-channel-messages owner channel msg))))

(deftest tree-channel-selecting-nodes-message
  (let [owner :some-owner
        msg {:type :select-nodes :value :some-value}
        channel :not-used-channel]
    (td/with-doubles
      :spying [sut/select-node-handler]

      (sut/handle-tree-channel-messages owner channel msg)

      (is (= [[owner :some-value channel]] (td/calls-to sut/select-node-handler))))))

(deftest tree-channel-any-other-type-of-message-logs-an-error
  (td/with-doubles
    :spying [log/log-unknown-message-type]
    (let [msg {:type :any-other-type}
          owner :some-owner
          channel :not-used-channel]

      (sut/handle-tree-channel-messages owner channel msg)

      (is (= [[::sut/handle-tree-channel-messages msg]]
             (td/calls-to log/log-unknown-message-type))))))

(deftest tree-channel-any-other-type-of-message-produces-no-messages-in-the-channel
  (let [msg {:type :any-other-type}
        owner :some-owner
        channel (core.async/chan)]
    (async done
           (async-test-tools/expect-no-messages
            channel
            :done-fn done)
           (sut/handle-tree-channel-messages owner channel msg))))

(deftest selecting-a-node-handler
  (let [owner :some-owner
        channel :a-channel
        node :a-node]
    (testing "when shift is pressed"
      (let [pressed-keys #{:control :shift}]
        (td/with-doubles
          :stubbing [om/get-state :maps {[owner :pressed-keys] pressed-keys}]
          :spying [sut/handle-control-selection]

          (sut/select-node-handler owner node channel)

          (is (= [[channel owner node true]] (td/calls-to sut/handle-control-selection))))))

    (testing "when control but not shift is pressed"
      (let [pressed-keys #{:shift}]
        (td/with-doubles
          :stubbing [om/get-state :maps {[owner :pressed-keys] pressed-keys}]
          :spying [sut/handle-shift-selection]

          (sut/select-node-handler owner node channel)

          (is (= [[node channel owner false]] (td/calls-to sut/handle-shift-selection))))))

    (testing "if any other keys are pressed"
      (let [pressed-keys #{}]
        (td/with-doubles
          :stubbing [om/get-state :maps {[owner :pressed-keys] pressed-keys}]
          :spying [sut/handle-control-selection]

          (sut/select-node-handler owner node channel)

          (is (= [[channel owner node false]] (td/calls-to sut/handle-control-selection))))))))

(deftest handling-control-selection
  (let [owner :some-owner
        channel :some-channel
        clicked-node {:id 1 :name "koko"}
        a-selected-node {:id 2 :name "moko"}]
    (testing "when selection is additive and clicked node is already in selected nodes"
      (let [additive true
            selected-nodes #{clicked-node a-selected-node}
            expected-nodes-to-select #{a-selected-node}]
        (td/with-doubles
          :stubbing [om/get-state :maps {[owner :selected] selected-nodes}]
          :spying [sut/update-state-global-and-local!]

          (sut/handle-control-selection channel owner clicked-node additive)

          (is (= [[expected-nodes-to-select channel owner nil]]
                 (td/calls-to sut/update-state-global-and-local!))))))

    (testing "when selection is additive and clicked node is not in selected nodes"
      (let [additive true
            selected-nodes #{a-selected-node}
            expected-nodes-to-select #{clicked-node a-selected-node}]
        (td/with-doubles
          :stubbing [om/get-state :maps {[owner :selected] selected-nodes}]
          :spying [sut/update-state-global-and-local!]

          (sut/handle-control-selection channel owner clicked-node additive)

          (is (= [[expected-nodes-to-select channel owner nil]]
                 (td/calls-to sut/update-state-global-and-local!))))))

    (testing "when selection is not additive and clicked node is not in selected nodes"
      (let [additive false
            selected-nodes #{a-selected-node}
            expected-nodes-to-select #{clicked-node}]
        (td/with-doubles
          :stubbing [om/get-state :maps {[owner :selected] selected-nodes}]
          :spying [sut/update-state-global-and-local!]

          (sut/handle-control-selection channel owner clicked-node additive)

          (is (= [[expected-nodes-to-select channel owner clicked-node]]
                 (td/calls-to sut/update-state-global-and-local!))))))

    (testing "when selection is not additive and clicked node is already in selected nodes"
      (let [additive false
            selected-nodes #{clicked-node a-selected-node}
            expected-nodes-to-select #{}]
        (td/with-doubles
          :stubbing [om/get-state :maps {[owner :selected] selected-nodes}]
          :spying [sut/update-state-global-and-local!]

          (sut/handle-control-selection channel owner clicked-node additive)

          (is (= [[expected-nodes-to-select channel owner nil]]
                 (td/calls-to sut/update-state-global-and-local!))))))))

(deftest handling-shift-selection
  (let [owner :some-owner
        channel :some-channel]
    (testing "when there're no shift selected node"
      (let [shift-selected-node nil
            additive :any-boolean
            clicked-node {:id 1 :name "k"}]
        (td/with-doubles
          :stubbing [om/get-state :maps {[owner :shift-selection] shift-selected-node}]
          :spying [om/set-state!]

          (sut/handle-shift-selection clicked-node channel owner additive)

          (is (= [[:some-owner :shift-selection clicked-node]]
                 (td/calls-to om/set-state!))))))

    (testing "when there are a shift selected node"
      (let [shift-selected-node {:id 3 :name "k"}
            currently-selected-node :any-node
            expanded-nodes-from-dom [{:id 1 :name "k"} {:id 2 :name "o"} {:id 3 :name "k"} {:id 4 :name "o"}]
            nodes-from-shift-selected-one-to-clicked-one #{{:id 1 :name "k"} {:id 2 :name "o"} {:id 3 :name "k"}}
            clicked-node {:id 1 :name "k"}]
        (testing "when selection is additive"
          (let [additive true
                expected-nodes-to-select (conj nodes-from-shift-selected-one-to-clicked-one currently-selected-node)]
            (td/with-doubles
              :stubbing [om/get-state :maps {[owner :shift-selection] shift-selected-node
                                             [owner :selected] currently-selected-node}
                         sut/get-expanded-nodes-from-dom :returns [expanded-nodes-from-dom]]
              :spying [sut/update-state-global-and-local!]

              (sut/handle-shift-selection clicked-node channel owner additive)

              (is (= [[expected-nodes-to-select :some-channel :some-owner shift-selected-node]]
                     (td/calls-to sut/update-state-global-and-local!))))))
        (testing "when selection is not additive"
          (let [additive false
                expected-nodes-to-select nodes-from-shift-selected-one-to-clicked-one]
            (td/with-doubles
              :stubbing [om/get-state :maps {[owner :shift-selection] shift-selected-node
                                             [owner :selected] currently-selected-node}
                         sut/get-expanded-nodes-from-dom :returns [expanded-nodes-from-dom]]
              :spying [sut/update-state-global-and-local!]

              (sut/handle-shift-selection clicked-node channel owner additive)

              (is (= [[expected-nodes-to-select :some-channel :some-owner shift-selected-node]]
                     (td/calls-to sut/update-state-global-and-local!))))))))))

(deftest updating-global-and-local-state
  (let [selected :some-selected-nodes
        channel :some-channel
        owner :some-owner
        node :some-node]
    (td/with-doubles
      :spying [om/set-state!
               sut/notify-selected-nodes!]

      (sut/update-state-global-and-local! selected channel owner node)

      (is (= [[owner :shift-selection node] [owner :selected selected]] (td/calls-to om/set-state!)))
      (is (= [[channel selected]] (td/calls-to sut/notify-selected-nodes!))))))

(deftest notifying-selected-nodes
  (let [selected :some-nodes
        channel (core.async/chan)]
    (async done
           (async-test-tools/expect-async-message
            channel
            :expected-message {:type :select-nodes :value selected}
            :done-fn done)
           (sut/notify-selected-nodes! channel selected))))