;;; Run from command line:
;;; java -cp clojure.jar clojure.main -i chess-clock.clj -e '(chess/clock)'

;;; Usage:
;;; s     : setup
;;; p     : start / stop ("pause")
;;; r     : reset times to their original values
;;; ctrl  : left player's key
;;; enter : right player's key

;;; Configuration:
;;; See the variables under "Default values" below.

(ns chess
  (:import (java.awt BorderLayout Color Dimension Font Toolkit)
           (java.awt.event ActionListener KeyAdapter KeyEvent WindowAdapter
                           WindowListener)
           (javax.swing BoxLayout JButton JDialog JFrame JLabel JPanel JSpinner
                        SpinnerNumberModel Timer WindowConstants)))

;;; Default values
(def left-key KeyEvent/VK_CONTROL)
(def right-key KeyEvent/VK_ENTER)
(def left (ref 3600))
(def right (ref 3600))
(def original-left (ref 3600))
(def original-right (ref 3600))
(def current (ref left))


;;; Utilities

(when ((ns-refers *ns*) 'case)          ; Newer versions of Clojure already
  (ns-unmap *ns* 'case))                ; contain this utility

(defmacro case [value & pairs]
  (let [val (gensym "val")]
    `(let [~val ~value]
       (cond ~@(mapcat (fn [[key action]] `((= ~val ~key) ~action))
                       (partition 2 pairs))))))


;;; Graphics

(defn paint-bar [g [x y] over?]
  (.setColor g (if over? Color/red Color/green))
  (.fillRect g x y 300 60))

(defn paint-clock [g [x y] seconds]
  (let [x0 (+ x 150)
        y0 (+ y 150)
        minutes (/ seconds 60)
        cx (fn [rad len] (+ x0 (* (Math/cos rad) len)))
        cy (fn [rad len] (+ y0 (* (Math/sin rad) len)))]
    (.setColor g Color/white)
    (.fillArc g x y 300 300 0 360)
    (.setColor g Color/gray)
    (doseq [deg (range 0 360 6)]
      (let [rad (/ (* deg Math/PI) 180)
            len (if (zero? (mod deg 30)) 120 130)]
        (.drawLine g (cx rad 140) (cy rad 140) (cx rad len) (cy rad len))))
    (.setColor g Color/black)
    (.drawArc g x y 300 300 0 360)
    (let [rad (/ (* (mod (- 45 seconds) 60) 6 Math/PI) 180)]
      (.drawLine g x0 y0 (cx rad 140) (cy rad 140)))
    (let [rad (/ (* (mod (- 45 minutes) 60) 6 Math/PI) 180)]
      (.drawLine g x0 y0 (cx rad 100) (cy rad 100)))))

(defn paint-progress [g [x y] percentage]
  (.setColor g (cond (> percentage 0.5) Color/green
                     (> percentage 0.1) Color/yellow
                     true Color/red))
  (.fillRect g x y (* 300 percentage) 30)
  (.setColor g Color/black)
  (.drawRect g x y 300 30))

(defn paint-digital [g [x y] seconds]
  (.setColor g Color/black)
  (.setFont g (Font. "Courier" Font/BOLD 100))
  (.drawString g (format "%02d:%02d" (int (/ seconds 60)) (mod seconds 60))
               x (+ y 80)))

(defn paint-clocks [g]
  (if (= @current left)
    (paint-bar g [20 20] (<= @left 0))
    (paint-bar g [400 20] (<= @right 0)))
  (paint-clock g [20 100] @left)
  (paint-clock g [400 100] @right)
  (let [full (max @original-left @original-right)]
    (paint-progress g [20 420] (/ @left full))
    (paint-progress g [400 420] (/ @right full)))
  (paint-digital g [20 450] @left)
  (paint-digital g [400 450] @right))


;;; GUI

(defn set-times [parent]
  (let [frame (JDialog. parent "Setup")
        panel (JPanel.)
        labels (JPanel.)
        edits (JPanel.)
        left-time (JSpinner. (SpinnerNumberModel.
                              (int (/ @original-left 60)) 0 1000 1))
        right-time (JSpinner. (SpinnerNumberModel.
                               (int (/ @original-right 60)) 0 1000 1))
        button (JButton. "OK")
        listener (proxy [ActionListener] []
                   (actionPerformed [e]
                     (dosync
                      (ref-set original-left (* (.getValue left-time) 60))
                      (ref-set original-right (* (.getValue right-time) 60))
                      (ref-set left @original-left)
                      (ref-set right @original-right))
                     (.repaint parent)
                     (.dispose frame)))]
    (.addActionListener button listener)
    (doto labels
      (.setLayout (BoxLayout. labels BoxLayout/Y_AXIS))
      (.add (JLabel. "Left  [min]:"))
      (.add (JLabel. "Right [min]:")))
    (doto edits
      (.setLayout (BoxLayout. edits BoxLayout/Y_AXIS))
      (.add left-time)
      (.add right-time))
    (doto panel
      (.setLayout (BorderLayout.))
      (.add labels BorderLayout/WEST)
      (.add edits BorderLayout/EAST)
      (.add button BorderLayout/SOUTH))
    (doto frame
      (.add panel)
      (.pack)
      (.setLocationRelativeTo parent)
      (.setVisible true))))

(defn clock []
  (let [timer (Timer. 1000 nil)
        frame (JFrame. "Chess Clock")
        panel (proxy [JPanel ActionListener] []
                (paintComponent [g]
                  (proxy-super paintComponent g)
                  (paint-clocks g))
                (getPreferredSize []
                  (Dimension. 720 550))
                (actionPerformed [e]
                  (dosync (alter @current dec))
                  (.repaint this)
                  (when (or (<= @left 0) (<= @right 0))
                    (.stop timer)
                    (.beep (Toolkit/getDefaultToolkit)))))
        key-listener (proxy [KeyAdapter] []
                       (keyPressed [key]
                         (case (.getKeyCode key)
                           KeyEvent/VK_P (if (.isRunning timer)
                                           (.stop timer)
                                           (.start timer))
                           left-key (when (= @current left)
                                      (dosync (ref-set current right)))
                           right-key (when (= @current right)
                                       (dosync (ref-set current left)))
                           KeyEvent/VK_S (do (.stop timer)
                                             (set-times frame))
                           KeyEvent/VK_R (dosync
                                          (ref-set left @original-left)
                                          (ref-set right @original-right)))
                         (.repaint panel)))
        window-listener (proxy [WindowAdapter] []
                          (windowClosing [e]
                            (.stop timer)
                            (.dispose frame)))] 
    (.addActionListener timer panel)
    (doto panel
      (.setBackground Color/gray)
      (.setFocusable true)
      (.addKeyListener key-listener))
    (doto frame
      (.add panel)
      (.setDefaultCloseOperation WindowConstants/DO_NOTHING_ON_CLOSE)
      (.addWindowListener window-listener)
      (.pack)
      (.setVisible true))))