(ns caesar-cipher
 (:require (clojure.contrib [str-utils2 :as s]))
 (:use clojure.test ))

;; forward declaring just so I can order the functions how I want...

(defn rot13 "rot13 implementation"
  [msg] (rot-msg msg 13))

(defn rot-msg
  "Cipher a message via Caesar rotation.
     message - String to be encoded.
     i - factor by which to encode the message."
  [message i]
  (s/map-str #(rot-char % i) message))

(defn rot-char
  "applies caesar cipher to a single character. Only ciphers
   ascii a-z and A-Z, leaving other characters unchanged.
   c - a single ascii character.
   i - the factor by which to rotate the character."
  [c i] {:pre [(char? c) (number? i)]}
    ;; cheating a bit here since the ascii-table won't change
    (in-range? c \A \Z) (rot-letter c i \A \Z \@)
    (in-range? c \a \z) (rot-letter c i \a \z \`)
    :default c))

(defn in-range? [c start end]
  (if (or (.equals c start) (.equals c end))
    (let [compared-to-start (compare c start)
          compared-to-end (compare c end)]
      (or (= 0 compared-to-start)
          (= 0 compared-to-end)
          (and (pos? compared-to-start) (neg? compared-to-end))))))

(defn rot-letter [c i start end start-minus-1]
  (let [rotated-char (char (+ (int c) i))]
    (if (in-range? rotated-char start end)
      (let [overflow (- (int rotated-char) (int end))]
        (char (+ (int start-minus-1) overflow))))))

;; Some tests - TODO: learn about Clojure unit tests...

(defn test-rot13 []
  (let [message "a MeSsAgE"
        encoded (rot13 message)]
    (is (= message (rot13 encoded)))))

(defn do-all-rotations [msg]
  (loop [i 0]
      (if (= i 26)
        (println "done")
          (println (rot-msg msg i))
          (recur (inc i))))))

(defn rotate-alphabet []
  (let [uppercase-alphabet "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        lowercase-alphabet "abcdefghijklmnopqrstuvwxyz"]
      (do-all-rotations uppercase-alphabet)
      (do-all-rotations lowercase-alphabet))))