;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC

(ns app.main.ui.shapes.attrs
  (:require
   [app.common.colors :as clr]
   [app.common.data :as d]
   [app.common.data.macros :as dm]
   [app.common.files.helpers :as cfh]
   [app.common.geom.shapes :as gsh]
   [app.common.svg :as csvg]
   [app.common.types.shape :refer [stroke-caps-line stroke-caps-marker]]
   [app.common.types.shape.radius :as ctsr]
   [app.util.object :as obj]
   [cuerdas.core :as str]))

(defn- calculate-dasharray
  [style width]
  (let [w+5  (+ 5 width)
        w+1  (+ 1 width)
        w+10 (+ 10 width)]
    (case style
      :mixed  (str/concat "" w+5 "," w+5 "," w+1 "," w+5)
      :dotted (str/concat "0," w+5)
      :dashed (str/concat "" w+10 "," w+10)
      "")))

(defn get-border-props
  [shape]
  (case (ctsr/radius-mode shape)
    :radius-1
    (let [radius (gsh/shape-corners-1 shape)]
      #js {:rx radius :ry radius})

    :radius-4
    (let [[r1 r2 r3 r4] (gsh/shape-corners-4 shape)
          x      (dm/get-prop shape :x)
          y      (dm/get-prop shape :y)
          width  (dm/get-prop shape :width)
          height (dm/get-prop shape :height)
          top    (- width r1 r2)
          right  (- height r2 r3)
          bottom (- width r3 r4)
          left   (- height r4 r1)]
      #js {:d (dm/str
               "M" (+ x r1) "," y " "
               "h" top " "
               "a" r2 "," r2 " 0 0 1 " r2 "," r2 " "
               "v" right " "
               "a" r3 "," r3 " 0 0 1 " (- r3) "," r3 " "
               "h" (- bottom) " "
               "a" r4 "," r4 " 0 0 1 " (- r4) "," (- r4) " "
               "v" (- left) " "
               "a" r1 "," r1 " 0 0 1 " r1 "," (- r1) " "
               "z")})))

(defn add-border-props!
  [props shape]
  (obj/merge! props (get-border-props shape)))

(defn add-fill!
  ([attrs fill-data render-id index type]
   (add-fill! attrs fill-data render-id index type "none"))
  ([attrs fill-data render-id index type fill-default]
   (let [index (if (some? index) (dm/str "-" index) "")]
     (cond
       (contains? fill-data :fill-image)
       (let [id (dm/str "fill-image-" render-id)]
         (obj/set! attrs "fill" (dm/str "url(#" id ")")))

       (some? (:fill-color-gradient fill-data))
       (let [id (dm/str "fill-color-gradient-" render-id index)]
         (obj/set! attrs "fill" (dm/str "url(#" id ")")))

       (contains? fill-data :fill-color)
       (obj/set! attrs "fill" (:fill-color fill-data))

       :else
       (obj/set! attrs "fill" fill-default))

     (when (contains? fill-data :fill-opacity)
       (obj/set! attrs "fillOpacity" (:fill-opacity fill-data)))

     (when (and (= :text type)
                (nil? (:fill-color-gradient fill-data))
                (nil? (:fill-color fill-data)))
       (obj/set! attrs "fill" "black"))

     attrs)))

(defn add-stroke!
  [attrs data render-id index open-path?]
  (let [style (:stroke-style data :solid)]
    (when-not (= style :none)
      (let [width       (:stroke-width data 1)
            gradient    (:stroke-color-gradient data)
            color       (:stroke-color data)
            opacity     (:stroke-opacity data)]

        (obj/set! attrs "strokeWidth" width)

        (when (some? gradient)
          (let [gradient-id (dm/str "stroke-color-gradient-" render-id "-" index)]
            (obj/set! attrs "stroke" (str/ffmt "url(#%)" gradient-id))))

        (when-not (some? gradient)
          (when (some? color)
            (obj/set! attrs "stroke" color))
          (when (some? opacity)
            (obj/set! attrs "strokeOpacity" opacity)))

        (when (not= style :svg)
          (obj/set! attrs "strokeDasharray" (calculate-dasharray style width)))

        ;; For simple line caps we use svg stroke-line-cap attribute. This
        ;; only works if all caps are the same and we are not using the tricks
        ;; for inner or outer strokes.
        (let [caps-start (:stroke-cap-start data)
              caps-end   (:stroke-cap-end data)
              alignment  (:stroke-alignment data)]
          (cond
            (and (contains? stroke-caps-line caps-start)
                 (= caps-start caps-end)
                 (or open-path?
                     (and (not= :inner alignment)
                          (not= :outer alignment)))
                 (not= :dotted style))
            (obj/set! attrs "strokeLinecap" (name caps-start))

            (= :dotted style)
            (obj/set! attrs "strokeLinecap" "round"))

          (when (or open-path?
                    (and (not= :inner alignment)
                         (not= :outer alignment)))

            ;; For other cap types we use markers.
            (when (or (contains? stroke-caps-marker caps-start)
                      (and (contains? stroke-caps-line caps-start)
                           (not= caps-start caps-end)))
              (obj/set! attrs "markerStart" (str/ffmt "url(#marker-%-%)" render-id (name caps-start))))

            (when (or (contains? stroke-caps-marker caps-end)
                      (and (contains? stroke-caps-line caps-end)
                           (not= caps-start caps-end)))
              (obj/set! attrs "markerEnd" (str/ffmt "url(#marker-%-%)" render-id (name caps-end))))))))

    attrs))

(defn get-svg-props
  [shape render-id]
  (let [attrs (get shape :svg-attrs {})
        defs  (get shape :svg-defs {})]
    (if (and (empty? defs)
             (empty? attrs))
      #js {}
      (-> attrs
          (csvg/update-attr-ids
           (fn [id]
             (if (contains? defs id)
               (dm/str render-id "-" id)
               id)))
          (dissoc :id)
          (obj/map->obj)))))

(defn get-fill-style
  ([fill-data index render-id type]
   (add-fill! #js {} fill-data render-id index type))
  ([fill-data index render-id type fill-default]
   (add-fill! #js {} fill-data render-id index type fill-default)))

(defn add-fill-props!
  ([props shape render-id]
   (add-fill-props! props shape 0 render-id))

  ([props shape position render-id]
   (let [shape-fills  (get shape :fills)
         shape-shadow (get shape :shadow)
         shape-blur   (get shape :blur)

         svg-attrs    (get-svg-props shape render-id)
         svg-styles   (obj/get svg-attrs "style")

         shape-type   (dm/get-prop shape :type)

         style        (-> (obj/get props "style")
                          (obj/clone)
                          (obj/merge! svg-styles))

         url-fill?    (or ^boolean (some? (:fill-image shape))
                          ^boolean (cfh/image-shape? shape)
                          ^boolean (> (count shape-fills) 1)
                          ^boolean (some? (some :fill-color-gradient shape-fills))
                          ^boolean (some? (some :fill-image shape-fills)))

         props        (if (cfh/frame-shape? shape)
                        props
                        (if (or (some? (->> shape-shadow (remove :hidden) seq))
                                (and (some? shape-blur) (not ^boolean (:hidden shape-blur))))
                          (obj/set! props "filter" (dm/fmt "url(#filter-%)" render-id))
                          props))]

     (cond
       ;; If the shape comes from an imported SVG (we know because
       ;; it has the :svg-attrs atribute), and it does not have an
       ;; own fill, we set a default black fill. This will be
       ;; inherited by child nodes, and is for emulating the
       ;; behavior of standard SVG, in that a node that has no
       ;; explicit fill has a default fill of black. This may be
       ;; reset to normal if a Penpot frame shape appears below
       ;; (see main.ui.shapes.frame/frame-container).
       (and ^boolean (contains? shape :svg-attrs)
            ^boolean (or ^boolean (= :svg-raw shape-type)
                         ^boolean (= :group shape-type))
            ^boolean (empty? shape-fills))
       (let [wstyle (get shape :wrapper-styles)
             fill   (obj/get wstyle "fill")
             fill   (d/nilv fill clr/black)]
         (obj/set! style "fill" fill))

       ^boolean url-fill?
       (do
         (obj/unset! style "fill")
         (obj/unset! style "fillOpacity")
         (obj/set! props "fill" (dm/fmt "url(#fill-%-%)" position render-id)))

       (and ^boolean (some? svg-styles)
            ^boolean (obj/contains? svg-styles "fill"))
       (let [fill    (obj/get svg-styles "fill")
             opacity (obj/get svg-styles "fillOpacity")]
         (when (some? fill)
           (obj/set! style "fill" fill))
         (when (some? opacity)
           (obj/set! style "fillOpacity" opacity)))

       (and ^boolean (some? svg-attrs)
            ^boolean (empty? shape-fills))
       (let [fill    (obj/get svg-attrs "fill")
             opacity (obj/get svg-attrs "fillOpacity")]
         (when (some? fill)
           (obj/set! style "fill" fill))
         (when (some? opacity)
           (obj/set! style "fillOpacity" opacity)))

       ^boolean (d/not-empty? shape-fills)
       (let [fill (nth shape-fills 0)
             svg-fill (obj/get svg-attrs "fill")
             fill-default (d/nilv svg-fill "none")]
         (obj/merge! style (get-fill-style fill render-id 0 shape-type fill-default)))

       (and ^boolean (cfh/path-shape? shape)
            ^boolean (empty? shape-fills))
       (obj/set! style "fill" "none"))

     (-> props
         (obj/merge! svg-attrs)
         (obj/set! "style" style)))))
