(import json)


(defn item-with-id [id items]
  (find |(= ($ "id") id) items))


(defn contains? [id node]
  (if (= node :null)
    false
    (or (= (node "id") id)
        (contains? id (node "firstChild"))
        (contains? id (node "secondChild")))))


(defn partition-on-focused [focused-id {"firstChild" node-a "secondChild" node-b}]
  (if (contains? focused-id node-a)
    [node-a node-b]
    [node-b node-a]))


(defn count-hidden [node]
  (match node
    :null 0
    
    {"hidden" true} 1
    
    {"firstChild" node-a "secondChild" node-b} (+ (count-hidden node-a)
                                                  (count-hidden node-b))))


(defn collect-hidden [node]
  (def hidden @[])
  (defn put-hidden [node]
    (match node
      :null nil
  
      {"hidden" true} (array/push hidden node)
  
      {"firstChild" node-a "secondChild" node-b} (do
                                                   (put-hidden node-a)
                                                   (put-hidden node-b))))
  (put-hidden node)
  hidden)


(defn fully-unhidden
  "Gets the first node with all children unhidden"
  [node]
  (match node
    :null nil

    {"firstChild" {"hidden" false} "secondChild" {"hidden" false}} node

    {"firstChild" {"hidden" true} "secondChild" second} (fully-unhidden second)

    {"firstChild" first "secondChild" {"hidden" true}} (fully-unhidden first)))


(defn innermost-hidden [focused-id node]
  (match node
    :null nil

    {"hidden" true} node
    
    _ (let [[focused not-focused] (partition-on-focused focused-id node)]
        (or (innermost-hidden focused-id focused)
            (innermost-hidden focused-id not-focused)))))


(defn rotate-clockwise []
  (os/execute ["bspc" "node" "@/" "--rotate" "90"] :p))


(defn rotate-counterclockwise []
  (os/execute ["bspc" "node" "@/" "--rotate" "-90"] :p))


(defn toggle-hidden [node]
  (os/execute ["bspc" "node" (string (node "id")) "--flag" "hidden"] :p))


(defn get-state []
  (with [pipe (os/spawn ["bspc" "wm" "-d"] :p {:out :pipe})]
    (:read (pipe :out) :all)))


(defn current-desktop []
  (let [data (get-state)
        state (json/decode data)
        monitor (item-with-id (state "focusedMonitorId") (state "monitors"))]
    
    (item-with-id (monitor "focusedDesktopId") (monitor "desktops"))))


(defn zoom-in []
  (let [desktop (current-desktop)
        unhidden (fully-unhidden (desktop "root"))
        focused-id (desktop "focusedNodeId")]

    (match unhidden
      nil (print "Não há mais nós para esconder")

      {"firstChild" :null "secondChild" :null} (toggle-hidden unhidden)

      _ (let [[_ not-focused] (partition-on-focused focused-id unhidden)]
          (toggle-hidden not-focused)
          (rotate-counterclockwise)))))


(defn zoom-out []
  (let [desktop (current-desktop)
        node (innermost-hidden (desktop "focusedNodeId") (desktop "root"))]

    (when node
      (rotate-clockwise)
      (toggle-hidden node))))


(defn zoom-level []
  (let [desktop (current-desktop)]
    (print (count-hidden (desktop "root")))))


(defn level-zero []
  (let [desktop (current-desktop)]
    (each node (collect-hidden (desktop "root"))
      (rotate-clockwise)
      (toggle-hidden node))))


(defn main [& args]
  (if (= 1 (length args))
    (print "Que comando você quer executar? `in` ou `out`?")

    (match (args 1)
      "in" (zoom-in)
      "out" (zoom-out)
      "zoom-level" (zoom-level)
      "level-zero" (level-zero)
      command (print "Comando não reconhecido: " command))))