             [transit.read :as r]
             [transit.write :as w]
             [transit.generators :as gen]
-            [transit.corner-cases :as cc])
-  (:import [java.io PrintStream ByteArrayOutputStream ByteArrayInputStream
+            [transit.corner-cases :as cc]
+            [clojure.pprint :as pp])
+  (:import [java.io ByteArrayOutputStream ByteArrayInputStream
             BufferedInputStream BufferedOutputStream FileInputStream]
-           [org.apache.commons.codec.binary Hex]
-           [java.math MathContext]))
+           [org.apache.commons.codec.binary Hex]))
+
+(def ^:dynamic *style* false)
+
+(def styles {:reset "[0m"
+             :red "[31m"
+             :green "[32m"
+             :bright "[1m"})
+
+(defn with-style [style & strs]
+  (let [s (apply str (interpose " " strs))]
+    (if (and *style* (not= style :none))
+      (str \u001b (style styles) s \u001b (:reset styles))
+      s)))
 
 (def TIMEOUT
   "Timeout for roundtrip requests to native implementation"

                        node))
                data))

(defn roundtrip-transit
  "Given a process, a byte array of transit data and an encoding,
  roundtrip the transit data and return a map of test results. Throws
  an exception if sending transit data causes a timeout. The results
  map contains the expected and actual Clojure data and the test
  status: one of `:error` or `:success`."
  [proc transit-out encoding]
  (let [start (System/nanoTime)]
    (write-to-stream proc transit-out)
    (let [data-in (read-response proc TIMEOUT)
          end (System/nanoTime)
          data-out (read-transit transit-out encoding)]
      (if (= data-in ::timeout)
        (throw (ex-info "Response timeout" {:status :timeout :p proc :transit transit-out}))
        {:transit-expected (String. transit-out)
         :data-expected data-out
         :data-actual data-in
         :nano-time (- end start)
         ;; only checks the top level type
         :status (if (= (equalize data-out) (equalize data-in))
                   :success
                   :error)}))))

(defn roundtrip-edn
  "Given a process, an EDN form and an encoding, roundtrip the EDN
  data and return a map of test results. See `roundtrip-transit`."
  [proc edn encoding]
  (try
    (let [transit-out (write-transit edn encoding)]
      (roundtrip-transit proc transit-out encoding))
    (catch Throwable e
      (if (contains? #{:disconnected :timeout} (-> e ex-data :status) )
        (throw (ex-info (.getMessage e) (assoc (ex-data e) :edn edn)))
        (throw e)))))

(defn test-transit
  "Given a collection of transit byte arrays, a process and the
  transit enconding, roundtrip each transit message and return a
  sequence of results."
  [transits proc encoding]
  (mapv #(roundtrip-transit proc % encoding) transits))

(defn test-edn
  "Given a collection of EDN forms, a process and a transit
  enconding, roundtrip each form and return a sequence of results."
  [forms proc encoding]
  (mapv #(roundtrip-edn proc % encoding) forms))

(defn test-timing
  "Given a collection of transit byte arrays, a process and the
  transit enconding, record the time in milliseconds that it takes
  to roundtrip all of the transit messages."
  [transits proc encoding]
  (dotimes [x 200]
    (mapv #(roundtrip-transit proc % encoding) transits))
  (quot (reduce + (map :nano-time
                       (mapv #(roundtrip-transit proc % encoding) transits)))
        1000000))

(def extension {:json ".json"
                :msgpack ".mp"})

(defn exemplar-transit
  "Given an encoding, return a collection of transit byte arrays in
  that encoding. The byte arrays are loaded from the exemplar files in
  the `transit` repository."
  [encoding]
  (map #(read-bytes %)
       (filter #(and (.isFile %) (.endsWith (.getName %) (extension encoding)))
               (file-seq (io/file "../transit/simple-examples")))))


              :desc "exemplar file"
              :input transit-exemplars
              :path [:tests :exemplar-file]
              :test #(test-each proc %)}
             {:pred (constantly true)
              :desc "EDN corner case"
              :input (mapv #(edn->test-input % encoding) cc/forms)
              :path [:tests :corner-case-edn]
              :test #(test-each proc %)}
             {:pred (fn [_ e _] (= e :json))
              :desc "JSON transit corner case"
              :input (mapv #(transit->test-input (.getBytes %) encoding) cc/transit-json)
              :path [:tests :corner-case-transit-json]
              :test #(test-each proc %)}
             {:pred (fn [_ _ o] (:gen o))
              :desc "generated EDN"
              :input (mapv #(edn->test-input % encoding) (:generated-forms opts))
              :path [:tests :generated-edn]
              :test #(test-each proc %)}
             {:pred (fn [_ _ o] (:time o))
              :desc "timing"
              :input transit-exemplars
              :path [:time]
              :test #(let [ms (test-timing proc %)]
                       {:ms ms
                        :count (count transit-exemplars)
                        :encoding encoding})}])))
