(ns path-to-answer-sheet
  (:use [runner.koans :only [ordered-koans]]
        [path-to-enlightenment :only [meditations fancy-assert __ ___]]
        [clojure.string :only [join split trim] :as string]))

(def answers
  {"equalities" {"__" [true
                       2
                       7
                       5
                       4/2
                       false
                       6/3
                       3]}

   "lists" {"__" [1 2 3 4 5
                  1
                  [2 3 4 5]
                  ()
                  [:a :b :c :d :e]
                  [0 :a :b :c :d :e]
                  :a
                  [:b :c :d :e]
                  "No dice!"
                  ()]}

   "vectors" {"__" [1
                    []
                    [1]
                    [nil]
                    2
                    [333]
                    :peanut
                    :jelly
                    :jelly
                    [:butter :and]
                    3]}

   "sets" {"__" [nil
                 3
                 #{1 2 3 4 5}
                 #{1 2 3 4 5}
                 #{2 3}
                 #{1 4}]}

   "maps" {"__" [{}
                 0
                 1
                 2
                 2
                 1
                 1
                 "Vancouver"
                 nil
                 :key-not-found
                 true
                 false
                 "February"
                 1 "January"
                 2006 2010 2014
                 "Vancouver"]}

   "functions" {"__" [20
                      10 5
                      30 2
                      15
                      20 '*]
                "___" ['(fn [f] (f 5))
                       '(fn [f] (f 5))]}

   "conditionals" {"__" [:a
                         []
                         nil
                         :glory
                         4 6 :your-road
                         ''doom 0
                         :cocked-pistol
                         :say-what?]}

   "higher_order_functions" {"__" [4 8 12
                                   '(* x x)
                                   [false false true false false]
                                   ()
                                   [:anything :goes :here]
                                   '(< x 31)
                                   '(* 10 x) '(< x 4)
                                   24
                                   100
                                   '(count a) '(count b)]}

   "runtime_polymorphism" {"__" ['(str (:name a) " eats veggies.")
                                 '(str (:name a) " eats animals.")
                                 '(str "I don't know what " (:name a) " eats.")
                                 "Hello World!"
                                 "Hello, you silly world."
                                 "Hello to this group: Peter, Paul, Mary!" ]}

   "lazy_sequences" {"__" [[1 2 3 4]
                           [0 1 2 3 4]
                           10
                           95
                           '(range 20)
                           :a]
                     "___" ['(fn [x] :foo)]}

   "sequence_comprehensions" {"__" [[0 1 2 3 4 5]
                                    '(* index index)
                                    '(range 10)
                                    '(odd? index) '(* index index)
                                    '[row column]
                                    ]}

   "creating_functions" {"__" [true false true
                               4
                              :a :b :c :d
                              :c :d
                              4
                              8]
                        "___" ['(complement nil?)
                               'multiply-by-5
                               '(comp dec square)]}

   "recursion" {"__" [true
                      'acc
                      '(loop [coll coll
                              acc ()]
                         (if (seq coll)
                           (recur (rest coll) (conj acc (first coll)))
                           acc))
                      '(loop [n n
                              acc 1]
                         (if (zero? n)
                             acc
                             (recur (dec n) (* acc n))))]
                "___" ['not]}

   "destructuring" {"__" [":bar:foo"
                          '(format (str "First comes %s, "
                                        "then comes %s, "
                                        "then comes %s with the baby carriage")
                                   a b c)
                          '(apply str
                             (interpose " "
                               (apply list
                                  first-name
                                  last-name
                                  (interleave (repeat "aka") aliases))))
                          '{:original-parts full-name
                            :named-parts {:first first-name :last last-name}}
                          '(str street-address ", " city ", " state)
                          'city 'state
                            '(str street-address ", " city ", " state)]
                    "___" ['(fn [[fname lname]
                                 {:keys [street-address city state]}]
                              (str fname " " lname ", "
                                   street-address ", " city ", " state))
                           ]}
   "refs" {"__" ["hello"
                 "hello"
                 "better"
                 "better!!!"
                 '(dosync (ref-set the-world 0))
                 '(map :jerry [@the-world @bizarro-world])
                 ]
           "___" ['(fn [x] (+ 20 x))]}

   "atoms" {"__" [0
                  1
                  '(swap! atomic-clock (partial + 4))
                  20
                  20
                  'atomic-clock 20 :fin
                  ]}

   "macros" {"__" ['~(first form)
                   '~(nth form 2)
                   'form
                   '(drop 2 form)
                   "Hello, Macros!"
                   10
                   ''(+ 9 1)
                   ''(* 10 2)
                   ''(+ 10 (2 * 3))]}

   "datatypes" {"__" ['(print
                         (str "You're really the "
                              (.category this)
                              ", " recipient "... sorry."))
                      "peace"
                      "literature"
                      "physics"
                      nil
                      [true false]
                      (str "Congratulations on your Best Picture Oscar, "
                           "Evil Alien Conquerors!")]}

   "java_interop" {"__" ['java.lang.String
                         "SELECT * FROM"
                         10
                         1024
                         ]
                   "___" ['#(.toUpperCase %)
                          ]
                   }

   })

(defn replace-with [s k replacements]
  (let [unreplaced-texts (split s (re-pattern (str "\\b" k "\\b")))]
    (join
      (butlast
        (interleave
          unreplaced-texts
          (concat (map pr-str replacements) (repeat k)))))))

(defn koan-text [koan]
  (slurp (str "src/koans/" koan ".clj")))

(defn answers-for [koan sym]
  ((answers koan {}) sym []))

(defn fill-in-answers [text koan sym]
  (replace-with text sym (answers-for koan sym)))

(defn print-non-failing-error [koan]
  (println (str "\n" koan ".clj is passing without filling in the blanks")))

(defmacro ensure-failure [& forms]
  (let [pairs (partition 2 forms)
        tests (map (fn [[doc# code#]]
                     `(if (try
                           (fancy-assert ~code# ~doc#)
                           false
                           (catch AssertionError e# true)
                           (catch Exception e# true))
                       :pass
                       (throw (AssertionError. (pr-str ~doc# ~code#)))))
                   pairs)]
    `(do ~@tests)))

(defn ensure-failing-without-answers []
  (if (every?
        (fn [koan]
          (let [form (koan-text koan)
                form (string/replace form "(meditations" "(ensure-failure")
                fake-err (java.io.PrintStream. (java.io.ByteArrayOutputStream.))
                real-err System/err
                result (try
                         (load-string form)
                         true
                         (catch AssertionError e (prn e) false)
                         (catch Exception e (prn e) false))]
            (if result
              :pass
              (print-non-failing-error koan))))
        ordered-koans)
    (println "\nTests all fail before the answers are filled in.")))

(defn ensure-passing-with-answers []
  (try
    (dorun (map
      (fn [koan]
        (load-string
          (-> (koan-text koan)
              (fill-in-answers koan "__")
              (fill-in-answers koan "___"))))
      ordered-koans))
    (println "\nAll tests pass after the answers are filled in.")

    (catch Exception e
      (println "\nAnswer sheet fail: " e)
      (.printStackTrace e)
      (println "Answer sheet fail"))))

(defn run []
  (ensure-failing-without-answers)
  (ensure-passing-with-answers))