 (ns simple-check.core
-  (:require [simple-check.generators :as gen]))
+  (:require [simple-check.generators :as gen]
+            [clojure.test :as ct]))
 
 (defn- run-test
   [property args]

                 (catch Throwable t t))]
    [result vars]))

(declare shrink-loop)

(defn quick-check
  [num-tests property-fun & args]
  (loop [so-far 0]
    (if (= so-far num-tests)
      {:result true :num-tests so-far}
      (let [[result vars] (run-test property-fun args)]
        (cond
          (instance? Throwable result) {:result result
                                        :num-tests so-far
                                        :fail (vec vars)
                                        :shrunk (shrink-loop property-fun
                                                             (gen/tuple args)
                                                             (vec vars))}
          result (recur (inc so-far))
          :default {:result false
                    :num-tests so-far
                    :fail (vec vars)
                    :shrunk (shrink-loop property-fun
                                         (gen/tuple args)
                                         (vec vars))})))))

(defmacro forall [bindings expr]
  `(let [~@bindings]

                (recur tail head (inc total-nodes-visited) depth false)
                (recur children head (inc total-nodes-visited) (inc depth) true)))))))))

(defn- report-trial
  [property-fun so-far num-tests]
  (ct/report {:type ::trial
              ::property property-fun
              ::trial [so-far num-tests]}))

(defn- failure
  [property-fun result trial-number args failing-params]
  ;; TODO this is wrong, makes it impossible to clojure.test quickchecks that
  ;; should fail...
  #_(ct/report (if (instance? Throwable result)
               {:type :error
                :message (.getMessage result)
                :actual result}
               {:type :fail
                :expected true
                :actual result}))
  (ct/report {:type ::shrinking
              ::property property-fun
              ::params (vec failing-params)})
  (let [ret {:result result
             :num-tests trial-number
             :fail (vec failing-params)}]
    (assoc ret :shrunk (shrink-loop property-fun
                                    (gen/tuple args)
                                    (vec failing-params)))))

;; clojure.test reporting -----------------------------------------------------

(def ^:dynamic *report-trials*
  "Controls whether property trials should be reported via clojure.test/report.
  Valid values include:
  
  * false - no reporting of trials (default)
  * a function - will be passed a clojure.test/report-style map containing
      :simple-check.core/property and :simple-check.core/trial slots
  * true - provides quickcheck-style trial reporting (dots) via
      `trial-report-dots`

  (Note that all reporting requires running `quick-check` within the scope of a
  clojure.test run (via `test-ns`, `test-all-vars`, etc.)
  
  Reporting functions offered by simple-check include `trial-report-dots` and
  `trial-report-periodic` (which prints more verbose trial progress information
  every `*trial-report-period*` milliseconds."
  false)
(def ^:dynamic *report-shrinking*
  "If true, a verbose report of the property being tested, the
  failing return value, and the arguments provoking that failure is emitted
  prior to the start of the shrinking search."
  false)
(def ^:dynamic *trial-report-period*
  "Milliseconds between reports emitted by `trial-report-periodic`."
  10000)

(def ^:private last-trial-report (atom 0))

(let [begin-test-var-method (get-method ct/report :begin-test-var)]
  (defmethod ct/report :begin-test-var [m]
    (reset! last-trial-report (System/currentTimeMillis))
    (when begin-test-var-method (begin-test-var-method m))))

(defn- get-property-name
  [{property-fun ::property :as report-map}]
  (or (-> property-fun meta :name) (ct/testing-vars-str report-map)))

(defn trial-report-periodic
  "Intended to be bound as the value of `*report-trials*`; will emit a verbose
  status every `*trial-report-period*` milliseconds, like this one:
  
  Passing trial 3286 / 5000 for (your-test-var-name-here) (:)"
  [m]
  (let [t (System/currentTimeMillis)]
    (when (> (- t *trial-report-period*) @last-trial-report)
      (ct/with-test-out
        (println "Passing trial" (-> m ::trial first) "/" (-> m ::trial second)
                 "for" (get-property-name m)))
      (reset! last-trial-report t))))

(defn trial-report-dots
  "Intended to be bound as the value of `*report-trials*`; will emit a single
  dot every 1000 trials reported."
  [{[so-far total] ::trial}]
  (when (pos? so-far)
    (when (zero? (mod so-far 1000))
      (print ".")
      (flush))
    (when (== so-far total) (println))))

(defmethod ct/report ::trial [m]
  (when-let [trial-report-fn (and *report-trials*
                                  (if (true? *report-trials*)
                                    trial-report-dots
                                    *report-trials*))]
    (trial-report-fn m)))

(defmethod ct/report ::shrinking [m]
  (when *report-shrinking*
    (ct/with-test-out
      (println "Shrinking" (get-property-name m) "starting with parameters" (::params m)))))

;; Generators -----------------------------------------------------------------

