;;
;; Copyright 2010-2015 Mikhail Kryshen <mikhail@kryshen.net>
;;
;; This file is part of Indyvon.
;;
;; Indyvon is free software: you can redistribute it and/or modify it
;; under the terms of the GNU Lesser General Public License version 3
;; only, as published by the Free Software Foundation.
;;
;; Indyvon is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; Lesser General Public License for more details.
;;
;; You should have received a copy of the GNU Lesser General Public
;; License along with Indyvon.  If not, see
;; <http://www.gnu.org/licenses/>.
;;

(ns indyvon.demo
  "Indyvon demo and experiments."
  (:gen-class)
  (:use
   (indyvon core views viewport component))
  (:import
   (java.awt Color)
   (javax.swing JFrame)))

(defn draw-button!
  "Draws a button immediately (but uses callback for the action unlike
   IMGUI)."
  [id content callback & args]
  (with-handlers id
    (let [shadow-offset 2
          padding 4
          border-width 1
          offset (if (picked? id) (/ shadow-offset 2) 0)
          ^Color color (theme-get :alt-back-color)
          color (if (hovered? id) (.brighter color) color)
          width (- *width* shadow-offset)
          height (- *height* shadow-offset)]
      (with-color :shadow-color
        (.fillRect *graphics* shadow-offset shadow-offset width height))
      (with-color color
        (.fillRect *graphics* offset offset width height))
      (draw! (border border-width padding content)
             offset offset width height))
    ;; Event handlers
    [:mouse-entered _ (repaint!)]
    [:mouse-exited _ (repaint!)]
    [:mouse-pressed _ (repaint!)]
    [:mouse-released _ (repaint!)]
    [:mouse-clicked _ (apply callback args)]))

(defn combine-colors
  "Returns color between color1 and color2. When c (0 <= c <= 1.0) is
   closer to 0 the returned сolor is closer to color1."
  [^Color color1 ^Color color2 c]
  (case c
    0.0 color1
    1.0 color2
    (let [rgb1 (.getRGBComponents color1 nil)
          rgb2 (.getRGBComponents color2 nil)
          rgb (float-array (map #(+ (* (- 1 c) %1) (* c %2)) rgb1 rgb2))]
      (Color. (aget rgb 0) (aget rgb 1) (aget rgb 2) (aget rgb 3)))))

(defn animate
  "Changes the value of atom according to the specified range, speed,
   and current frame interval.  Invokes repaint! if change happens."
  [atom from to speed]
  (let [prev @atom
        state (cond
               (zero? speed) :stop
               (= prev from) (if (pos? speed) :start :stop)               
               (= prev to) (if (neg? speed) :start :stop)
               :default :continue)]
    (if (= state :stop)
       prev
       (let [interval (if (= state :start) 1 *interval*)
             step (* speed interval 1E-9)
             val (swap! atom #(-> % (+ step) (max from) (min to)))]
         (repaint!)
         val))))

(defn animated-button
  "Creates an animated button."
  [content callback & args]
  (let [padding 4
        border-width 1
        shadow-offset 2
        face (border padding border-width content)
        highlight (atom 0)
        animation-speed (atom 0)]
    (interval-view
     (reify
      View
      (render! [button]
        (with-handlers button
          (let [hovered (hovered? button)
                offset (if (picked? button) (/ shadow-offset 2) 0)
                color (combine-colors
                       (theme-get :alt-back-color) Color/WHITE
                       (animate highlight 0.0 1.0 @animation-speed))
                width (- *width* shadow-offset)
                height (- *height* shadow-offset)]
            (with-color :shadow-color
              (.fillRect *graphics*
                         shadow-offset shadow-offset
                         width height))
            (with-color color
              (.fillRect *graphics* offset offset width height))
            (draw! (border border-width padding content)
                   offset offset width height))
          ;; Event handlers
          [:mouse-entered _
           (reset! animation-speed 4)
           (repaint!)]
          [:mouse-exited _
           (reset! animation-speed -2)
           (repaint!)]
          [:mouse-pressed _ (repaint!)]
          [:mouse-released _ (repaint!)]
          [:mouse-clicked _ (apply callback args)]))
     (geometry [button]
       (let [face-geom (geometry face)]
         (->Size (+ (width face-geom) shadow-offset)
                 (+ (height face-geom) shadow-offset))))))))

(def button1 (animated-button (label "Animated button 1")
                              println "Animated button 1 clicked"))

(def button2 (animated-button (label "Animated button 2")
                              println "Animated button 2 clicked"))

(def test-view1
  (reify
   View
   (render! [view]
     (with-handlers view
       (with-color (if (hovered? view) Color/ORANGE Color/RED)
         (.fillRect *graphics* 0 0 *width* *height*))
       [:mouse-entered e
        (repaint!)
        (println e)]
       [:mouse-exited e
        (repaint!)
        (println e)]
       [:mouse-moved e
        (println e)]))
   (geometry [view]
     (->Size 30 20))))

(def test-view1b (border 2 3 test-view1))

(def test-view2
  (reify
   View
   (render! [view]
     (doto *graphics*
       (.setColor Color/YELLOW)
       (.fillRect 0 0 *width* *height*))
     (with-rotate 0.5 0 0
       (draw! test-view1b 30 25))
     (draw! test-view1 55 5))
   (geometry [view]
     (->Size 70 65))))

(def test-view2m (miniature 30 30 test-view2))

(def test-view3 (border (label :right :bottom "Sample\ntext")))

(def root
  (reify
   View
   (render! [view]
     (doto *graphics*
       (.drawLine 0 0 *width* *height*)
       (.drawLine *width* 0 0 *height*)
       ;; Random color to see when repaint happens.
       (.setColor (rand-nth [Color/BLACK Color/BLUE Color/RED]))
       (.fillOval 5 5 20 20))
     (draw! test-view2 30 20)
     (draw! test-view2m 120 50)
     (draw! test-view3 100 100 80 50)
     (draw! button1 50 160)
     (with-rotate (/ Math/PI 6) 250 200
       (draw! button1 210 140))
     (draw! button2 100 200)
     (with-bounds 180 240 140 30
       (draw-button! :button
        (label :center :center "Immediate button")
        #(println "Button clicked!"))))
   (geometry [view]
     (->Size 400 300))))

;; Main viewport
(def vp (viewport root))

;; Miniature (rendered asynchronously)
(def vp-miniature (->> vp (viewport-miniature 100 75) border shadow))

;; Main scene
(def scene
  (->> vp
       (decorator (fn [_ content]
                    (draw! content)
                    (draw-aligned!
                     (label (str "Drag mouse to pan," \newline
                                 "use mouse wheel to zoom."))
                     :left :bottom 5 (- *height* 5))
                    (draw! vp-miniature (- *width* 105) 5)))
       fps-view))

(defn show-frame [view]
  (doto (make-jframe "Test" view)
    (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE)
    (.setVisible true)))

(defn -main []
  (show-frame scene))

(comment
  (show-frame (viewport-miniature 200 150 vp)))