(require '[clojure.string:only [join]:refer [join]])

(def day-row "Su Mo Tu We Th Fr Sa")

(def col-width (count day-row))

(defn month-to-word 
  "Translate a month from 0 to 11 into its word representation."
  [month]
  ((vec (.getMonths (new java.text.DateFormatSymbols))) month))

(defn month [date]
  (.get date (java.util.Calendar/MONTH))) 

(defn total-days-in-month [date]
  (.getActualMaximum date (java.util.Calendar/DAY_OF_MONTH)))

(defn first-weekday [date]
  (.get date (java.util.Calendar/DAY_OF_WEEK)))

(defn normal-date-string
  "Returns a formatted list of strings of the days of the month."
  [date]		 
  (map #(join " " %)
    (partition 7
      (concat
        (repeat (dec (first-weekday date)) "  ")
        (map #(format "%2s" %)
               (range 1 (inc (total-days-in-month date))))
        (repeat (- 42 (total-days-in-month date)
                      (dec (first-weekday date)) ) "  ")))))


(defn oct-1582-string
  "Returns a formatted list of strings of the days of the month of October 1582."
  [date]
  (map #(join " " %)
    (partition 7
      (concat
         (repeat (dec (first-weekday date)) "  ")
         (map #(format "%2s" %)
                 (concat (range 1 5) 
                         (range 15 (inc (total-days-in-month date)))))
         (repeat (- 42 
                    (count (concat (range 1 5) 
		                   (range 15
                                          (inc (total-days-in-month date)))))
                    (dec (first-weekday date)) ) "  ")))))


(defn center-string 
  "Returns a string that is WIDTH long with STRING centered in it."
  [string width]
  (let [pad (- width (count string))
        lpad (quot pad 2) 
        rpad (- pad (quot pad 2))]
    (if (<= pad 0)
    string
    (str (apply str (repeat lpad " "))  
          string 
         (apply str (repeat rpad " "))))))


(defn calc-columns 
  "Calculates the number of columns given the width in CHARACTERS and the 
   MARGIN SIZE."
  [characters margin-size]
  (loop [cols 0 excess  characters ]
    (if (>= excess  col-width)
        (recur (inc cols) (- excess (+ margin-size col-width)))
         cols)))

(defn month-vector 
  "Returns a vector with the month name, day-row and days
   formatted for printing."
  [date]
  (vec (concat 
         (vector (center-string (month-to-word (month date)) col-width))
         (vector day-row)
 		 (if (and (= 1582 (.get date (java.util.Calendar/YEAR)))
		 		  (= 9 (month date)))
		     (oct-1582-string date)
                     (normal-date-string date)))))

(defn year-vector [date]
  "Returns a 2d vector of all the months in the year of DATE."
  (loop [m [] c (month date)]
    (if (= c 11 )
        (conj m (month-vector date)) 
        (recur (conj m (month-vector date))
               (do (.add date (java.util.Calendar/MONTH ) 1)
                   (month date))))))

(defn print-months
  "Prints the months to standard output with NCOLS and MARGIN."
  [ v ncols margin]
  (doseq [r (range (Math/ceil (/ 12 ncols)))]
    (do (doseq [i (range 8)]
          (do (doseq [c (range (* r ncols) (* (+ r 1) ncols))
		               :while (< c 12)]
              (printf (str (apply str (repeat margin " ")) "%s")
		      (get-in v [c i])))
          (println)))
        (println))))

(defn print-cal
  "(print-cal [year [width [margin]]])
   Prints out the calendar for a given YEAR with WIDTH characters wide and
   with MARGIN spaces between months."
  ([] 
     (print-cal 1969 80 2))
  ([year] 
     (print-cal year 80 2))
  ([year width]
   (print-cal year width 2))
  ([year width margin]
     (assert (>= width (count day-row)) "Width should be more than 20.")
     (assert (> margin 0) "Margin needs to be more than 0.")
     (let [date (new java.util.GregorianCalendar year 0 1)
           column-count (calc-columns width margin)
           total-size (+ (* column-count (count day-row))
                         (* (dec column-count) margin))]
       (println (center-string "[Snoopy Picture]" total-size))
       (println (center-string (str year) total-size))
       (println)
       (print-months (year-vector date) column-count margin))))