;; lightly adapted from 
;; https://github.com/cljfx/cljfx/blob/master/examples/e20_markdown_editor.clj
(ns mw-desktop.e20-markdown-editor
  (:require [cljfx.api :as fx]
            [clojure.core.cache :refer [lru-cache-factory]]
            [clojure.java.io :refer [resource]]
            [clojure.string :refer [join lower-case starts-with?]])
  (:import [java.awt Desktop]
           [java.io File]
           [java.net URI]
           [org.commonmark.node Node]
           [org.commonmark.parser Parser]))

;; does not work any more :(
#_(SvgImageLoaderFactory/install (PrimitiveDimensionProvider.))

(def *context
  (atom
   (fx/create-context {:typed-text (slurp (resource "doc/grammar.md"))}
                      #(lru-cache-factory % :threshold 4096))))

(defn commonmark->clj [^Node node]
  (let [tag (->> node
                 .getClass
                 .getSimpleName
                 (re-seq #"[A-Z][a-z]+")
                 (map lower-case)
                 (join "-")
                 keyword)
        all-attrs (->> node
                       bean
                       (map (fn [[k v]]
                              [(->> k
                                    name
                                    (re-seq #"[A-Z]?[a-z]+")
                                    (map lower-case)
                                    (join "-")
                                    keyword)
                               v]))
                       (into {}))]
    {:tag tag
     :attrs (dissoc all-attrs :next :previous :class :first-child :last-child :parent)
     :children (->> node
                    .getFirstChild
                    (iterate #(.getNext ^Node %))
                    (take-while some?)
                    (mapv commonmark->clj))}))

(defn node-sub [context]
  (-> (Parser/builder)
      .build
      (.parse (fx/sub-val context :typed-text))
      commonmark->clj))

(defmulti handle-event :event/type)

(defmethod handle-event :default [e]
  (prn e))

(defmethod handle-event ::type-text [{:keys [fx/event fx/context]}]
  {:context (fx/swap-context context assoc :typed-text event)})

(defmulti md->fx :tag)

(defn md-view [{:keys [node]}]
  (md->fx node))

(defmethod md->fx :heading [{children :children {:keys [level]} :attrs}]
  {:fx/type :text-flow
   :style-class ["heading" (str "level-" level)]
   :children (for [node children]
               {:fx/type md-view
                :node node})})

(defmethod md->fx :paragraph [{children :children}]
  {:fx/type :text-flow
   :style-class "paragraph"
   :children (for [node children]
               {:fx/type md-view
                :node node})})

(defmethod md->fx :text [{{:keys [literal]} :attrs}]
  {:fx/type :text
   :cache true
   :cache-hint :speed
   :text literal})

(defmethod md->fx :code [{{:keys [literal]} :attrs}]
  {:fx/type :label
   :cache true
   :cache-hint :speed
   :style-class "code"
   :text literal})

(defmethod md->fx :fenced-code-block [{{:keys [literal]} :attrs}]
  {:fx/type :v-box
   :padding {:top 9}
   :children [{:fx/type :scroll-pane
               :style-class ["scroll-pane" "code-block"]
               :fit-to-width true
               :content {:fx/type :label
                         :cache true
                         :cache-hint :speed
                         :max-width ##Inf
                         :min-width :use-pref-size
                         :text literal}}]})

(defmethod md->fx :indented-code-block [{{:keys [literal]} :attrs}]
  {:fx/type :v-box
   :padding {:top 9}
   :children [{:fx/type :scroll-pane
               :style-class ["scroll-pane" "code-block"]
               :fit-to-width true
               :content {:fx/type :label
                         :cache true
                         :cache-hint :speed
                         :max-width ##Inf
                         :min-width :use-pref-size
                         :text literal}}]})

(defmethod md->fx :link [{{:keys [^String destination]} :attrs children :children}]
  (let [link {:fx/type :hyperlink
              :on-action (fn [_]
                           (future
                             (try
                               (if (starts-with? destination "http")
                                 (.browse (Desktop/getDesktop) (URI. destination))
                                 (.open (Desktop/getDesktop) (File. destination)))
                               (catch Exception e
                                 (.printStackTrace e)))))}]
    (if (and (= 1 (count children))
             (= :text (:tag (first children))))
      (assoc link :text (-> children first :attrs :literal))
      (assoc link :graphic {:fx/type :h-box
                            :children (for [node children]
                                        {:fx/type md-view
                                         :node node})}))))

(defmethod md->fx :strong-emphasis [{:keys [children]}]
  (if (and (= 1 (count children))
           (= :text (:tag (first children))))
    {:fx/type :text
     :cache true
     :cache-hint :speed
     :style-class "strong-emphasis"
     :text (-> children first :attrs :literal)}
    {:fx/type :h-box
     :cache true
     :style-class "strong-emphasis"
     :children (for [node children]
                 {:fx/type md-view
                  :node node})}))

(defmethod md->fx :emphasis [{:keys [children]}]
  (if (and (= 1 (count children))
           (= :text (:tag (first children))))
    {:fx/type :text
     :cache true
     :cache-hint :speed
     :style-class "emphasis"
     :text (-> children first :attrs :literal)}
    {:fx/type :h-box
     :style-class "emphasis"
     :children (for [node children]
                 {:fx/type md-view
                  :node node})}))

(defmethod md->fx :soft-line-break [_]
  {:fx/type :text
   :text " "})

(defmethod md->fx :document [{:keys [children]}]
  {:fx/type :v-box
   :style-class "document"
   :children (for [node children]
               {:fx/type md-view
                :node node})})

(defmethod md->fx :image [{{:keys [destination]} :attrs}]
  {:fx/type :image-view
   :image {:url (if (starts-with? destination "http")
                  destination
                  (str "file:" destination))
           :background-loading true}})

(defmethod md->fx :bullet-list [{{:keys [bullet-marker]} :attrs children :children}]
  {:fx/type :v-box
   :style-class "md-list"
   :children (for [node children]
               {:fx/type :h-box
                :alignment :baseline-left
                :spacing 4
                :children [{:fx/type :label
                            :min-width :use-pref-size
                            :cache true
                            :cache-hint :speed
                            :text (str bullet-marker)}
                           {:fx/type md-view
                            :node node}]})})

(defmethod md->fx :ordered-list [{{:keys [delimiter start-number]} :attrs
                                  children :children}]
  {:fx/type :v-box
   :style-class "md-list"
   :children (map (fn [child number]
                    {:fx/type :h-box
                     :alignment :baseline-left
                     :spacing 4
                     :children [{:fx/type :label
                                 :cache true
                                 :cache-hint :speed
                                 :min-width :use-pref-size
                                 :text (str number delimiter)}
                                (assoc (md->fx child)
                                       :h-box/hgrow :always)]})
                  children
                  (range start-number ##Inf))})

(defmethod md->fx :list-item [{:keys [children]}]
  {:fx/type :v-box
   :children (for [node children]
               {:fx/type md-view
                :node node})})

(defmethod md->fx :default [{:keys [tag attrs children]}]
  {:fx/type :v-box
   :children [{:fx/type :label
               :cache true
               :cache-hint :speed
               :style {:-fx-background-color :red}
               :text (str tag " " attrs)}
              {:fx/type :v-box
               :padding {:left 10}
               :children (for [node children]
                           {:fx/type md-view
                            :node node})}]})

(defn note-input [{:keys [fx/context]}]
  {:fx/type :text-area
   :style-class "input"
   :text (fx/sub-val context :typed-text)
   :on-text-changed {:event/type ::type-text :fx/sync true}})

(defn note-preview [{:keys [fx/context]}]
  {:fx/type :scroll-pane
   :fit-to-width true
   :content {:fx/type md-view
             :node (fx/sub-ctx context node-sub)}})

(def app
  (fx/create-app *context
                 :event-handler handle-event
                 :desc-fn (fn [_]
                            {:fx/type :stage
                             :showing true
                             :width 960
                             :height 540
                             :scene {:fx/type :scene
                                     :stylesheets #{"doc/markdown.css"}
                                     :root {:fx/type :grid-pane
                                            :padding 10
                                            :hgap 10
                                            :column-constraints [{:fx/type :column-constraints
                                                                  :percent-width 100/2}
                                                                 {:fx/type :column-constraints
                                                                  :percent-width 100/2}]
                                            :row-constraints [{:fx/type :row-constraints
                                                               :percent-height 100}]
                                            :children [{:fx/type note-input
                                                        :grid-pane/column 0}
                                                       {:fx/type note-preview
                                                        :grid-pane/column 1}]}}})))
