(ns com.manigfeald.surmise
  (:refer-clojure :exclude [int vector list hash-map map keyword
                            char boolean byte bytes sequence
                            shuffle not-empty symbol namespace
                            set sorted-set uuid double let short])
  (:require [clojure.test.check.generators :as g]
            [clojure.core :as cc]
            [clojure.test.check.rose-tree :as rose]
            [clojure.test.check.random :as rand]
            [clojure.test.check.properties :as props]))

(set! *warn-on-reflection* true)
;; (set! *unchecked-math* :warn-on-boxed)

(declare trace rose-tree-1 regenerate)

;; Monad combinators
;;
;; test.check is built on two monads, one for generators and one for
;; rose trees. this adds a 3rd alternative monad for creating
;; generators, which are valid test.check generators, but also contain
;; free monad values that describe the generator. free monad just
;; because I wasn't sure what I was going to end up with.
(defn pure [value]
  (assoc (g/return value) ::generator [:pure value]))

(defn make-gen
  "Given the free structure of a surmise generator, make a test.check generator"
  [g]
  (assoc (#'g/make-gen
          (fn [rnd size]
            (cc/let [[l v] (trace g rnd size)]
              (rose-tree-1 v l g size))))
         ::generator g))

(defn fmap [f m]
  (assert (fn? f))
  (assert (::generator m) m)
  (make-gen [:fmap f (::generator m)]))

(defn join [m]
  (assert (::generator m) m)
  (make-gen [:join (::generator m)]))

(defn bind [m f]
  (assert (::generator m))
  (join (fmap f m)))

(defn error [msg]
  (assoc (#'g/make-gen
          (fn [_rnd _size]
            (throw (ex-info msg {}))))
         ::generator [:error msg]))

(def large-integer
  "Generate a long between Long/MIN_VALUE and Long/MAX_VALUE inclusive, shrinks towards 0"
  (assoc g/large-integer ::generator [:draw]))

(defn sized [f]
  (assoc (g/sized f) ::generator [:get-size f]))

;; Generators
;;
;; mostly built on choose, trying to follow the names used in
;; test.check, but shrinking behavior may be a little different

(def ^Number n (-' Long/MAX_VALUE Long/MIN_VALUE))


(declare choose')

(defn choose-within-range-centered-on-zero ^long [^long lower ^long upper ^long i]
  (cc/let [scale (/ (-' upper lower) n)]
    (long (Math/round (cc/double (+' (*' (-' (long i) Long/MIN_VALUE) scale) lower))))))

(defn choose-within-offside-range ^long [^long lower ^long upper ^long n ^long size]
  (cc/let [offset (-' lower)
           olower (+' lower offset)
           oupper (+' upper offset)
           width (-' oupper olower)
           slide (long (Math/round (/ width 2.0)))
           nlower (-' olower slide)
           nupper (-' oupper slide)]
    (cc/let [i (choose' nlower nupper n size)]
      ;; zig zag encoding folds the negative side of the number
      ;; line into the positive, so the middle (0) that we
      ;; shrinking towards becomes the lower end
      (long
       (-
        (bit-xor (bit-shift-right i 63)
                 (bit-shift-left i 1))
        offset)))))

(defn choose-with-range-with-elements-inside ^long [^long lower ^long upper ^long n ^long size]
  (if (> upper 0 lower)
    (choose-within-range-centered-on-zero (max lower (- size)) (min upper size) n)
    (choose-within-offside-range lower upper n size)))

(defn choose-within-range-with-distinct-bounds ^long [^long lower ^long upper ^long n ^long size]
  (cc/let [diff (-' upper lower)]
    (if (= diff 1)
      (if (bit-test n 1) upper lower)
      (choose-with-range-with-elements-inside lower upper n size))))

;; #:com.manigfeald.surmise{:l [-9125064582277025061 -7798280036296055815], :s 3}
(defn choose' ^long [^long lower ^long upper ^long n ^long size]
  (if (= lower upper)
    lower
    (choose-within-range-with-distinct-bounds lower upper n size)))

(defn choose
  "Generates a long between lower and upper inclusive. If 0 is between
  lower and upper shrinks towards 0, otherwise thrinks towards lower"
  [^long lower ^long upper]
  (sized
   (fn [s]
     (fmap
      #(choose' lower upper % s)
      large-integer))))

(def byte
  "Generates a byte between Byte/MIN_VALUE and Byte/MAX_VALUE inclusive, shrinks towards 0"
  (fmap cc/byte (choose Byte/MIN_VALUE Byte/MAX_VALUE)))

(def short
  "Generates a short between Short/MIN_VALUE and Short/MAX_VALUE inclusive, shrinks towards 0"
  (fmap cc/short (choose Short/MIN_VALUE Short/MAX_VALUE)))

(def int
  "Generates a int between Integer/MIN_VALUE and Integer/MAX_VALUE inclusive, shrinks towards 0"
  (fmap cc/int (choose Integer/MIN_VALUE Integer/MAX_VALUE)))

(def boolean
  "Generates a boolean, shrinks to false"
  (fmap cc/boolean (choose 0 1)))

(def char
  "Generates random characters, shrinks towards \0"
  (fmap (fn [x] (cc/char (bit-and x 0xffff))) (choose 0 Short/MAX_VALUE)))

;; shrinking may be nuts
;; (def float (fmap (fn [i] (Float/intBitsToFloat i)) int))

(def double (fmap (fn [i] (Double/longBitsToDouble i)) large-integer))

(defn such-that [g p]
  ((fn such-that* [g p i]
     (if (> i 100)
       (error "such that failed")
       (bind g (fn [v] (if (p v) (pure v) (such-that* g p (inc i)))))))
   g
   p
   0))

(defn one-of [generators]
  (bind (choose 0 (dec (count generators)))
        (fn [i]
          (nth generators i))))

(def small-integer (choose Long/MIN_VALUE Long/MAX_VALUE))

(defn tuple
  ([]
   (pure []))
  ([a]
   (bind a (fn [v] (pure [v]))))
  ([a b]
   (bind a (fn [av] (bind b (fn [bv] (pure [av bv]))))))
  ([a b c & more]
   (bind (tuple a b)
         (fn [abt]
           (bind (apply tuple c more)
                 (fn [morev] (pure (into abt morev))))))))

(def nat (choose 0 Long/MAX_VALUE))

(defn vector [g n]
  (apply tuple (repeat n g)))

(defn set [g]
  (bind nat
        (fn [f]
          (fmap (partial into #{})
                (apply tuple (repeat f g))))))

(defn list [g]
  (bind nat
        (fn [n]
          (fmap
           (partial apply cc/list)
           (apply tuple (repeat n g))))))

(defn bytes [g]
  (fmap (fn [bs] (into-array Byte/TYPE bs))
        (list byte)))

(def uuid (fmap
           (fn [[least most]]
             (java.util.UUID. most least))
           (tuple large-integer large-integer)))

(def ratio (fmap
            (fn [[a b]] (/ a (inc b)))
            (tuple small-integer nat)))

(defn frequency [ps]
  (bind (choose 0 (apply + -1 (cc/map first ps)))
        (fn [i]
          (reduce
           (fn [a [p g]]
             (cc/let [a (+ a p)]
               (if (> a i)
                 (reduced g)
                 a)))
           0
           ps))))

(defmacro for-all
  "Creates a property for checking, like test.check's for-all. Unlike
  test.check's for-all binding is sequential."
  [bindings & body]
  (cc/let [bindings (partition-all 2 bindings)]
    `(props/for-all*
      ~[(reduce
         (fn [a [n g]]
           `(bind ~g (fn [y#] (fmap (fn [x#] (conj x# y#)) (cc/let [~n y#] ~a) ))))
         `(pure ())
         (reverse bindings))]
      (fn [[~@(cc/map first bindings)]]
        ~@body))))

(defmacro let
  [bindings & body]
  (cc/let [bindings (partition-all 2 bindings)]
    (reduce
     (fn [a [n g]]
       `(bind ~g (fn [~n] ~a)))
     `(cc/let [r# (do ~@body)]
        (if (g/generator? r#)
          r#
          (pure r#)))
     (reverse bindings))))

(defn elements [coll]
  (assert (seq coll))
  (cc/let [v (vec (seq coll))]
    (fmap
     #(nth v %)
     (choose 0 (dec (count v))))))

;; Interpreters
;;
;; Two interpreters that run the generator constructing free
;; monad. Trace runs the generator and returns a pair of a vector of
;; all the random longs used while generating and the value
;; generated. Regenerate takes a vector of longs and a generator and
;; generates a value using the supplied longs, returns [:just
;; the-value] or [:error msg].
(defn interpop [{:keys [l c k r size] :as m}]
  (case (first c)
    :fmap (cc/let [[_ f x] c]
            (assoc m
                   :c x
                   :k (fn [m']
                        (assoc m'
                               :c [:just (f (-> m' :c second))]
                               :k k))))
    :error (assoc m :halted true)
    :join (cc/let [[_ x] c]
            (assoc m
                   :c x
                   :k (fn [m']
                        (assoc m'
                               :c (-> m' :c second ::generator)
                               :k k))))
    :just (if k
            (k m)
            (assoc m :halted true))
    :draw (if-not (seq r)
            (assoc m
                   :halted true
                   :c [:error "no more random"])
            (assoc m
                   :l (conj l (first r))
                   :c [:just (first r)]
                   :r (rest r)))
    :pure (cc/let [[_ v] c]
            (assoc m
                   :c [:just v]))
    :get-size (cc/let [[_ f] c]
                (assoc m :c (::generator (f size))))))

(defn trace
  "Tracing interpreter, collects used random values"
  ([op rnd size]
   (cc/let [[rnd' rnd''] (rand/split rnd)]
     (loop [rnd rnd'
            m {:l (vector-of :long)
               :c op
               :k nil
               :r (cc/list (rand/rand-long rnd''))
               :size size}]
       (cc/let [m' (interpop m)]
         (cond (and (:halted m') (= :just (first (:c m'))))
               [(:l m') (second (:c m'))]
               (and (:halted m') (= :error (first (:c m'))))
               (throw (ex-info (second (:c m')) {}))
               (not (seq (:r m')))
               (cc/let [[rnd' rnd''] (rand/split rnd)]
                 (recur rnd' (assoc m' :r (cc/list (rand/rand-long rnd'')))))
               :else
               (recur rnd m')))))))

(defn regenerate
  "Execute using already generated list of random values"
  ([l op size]
   (assert op)
   (loop [m {:l []
             :c op
             :k nil
             :r l
             :size size}]
     (cc/let [m' (interpop m)]
       (cond (:halted m')
             (:c m')
             :else
             (recur m'))))))

;; Shrinking
;;
;; basically given a vector of longs, create a rose tree where the
;; children shrink each long towards 0, and shrink the vector as a
;; whole towards being empty. the exact stategies of shrinking the
;; longs toward 0 should be informed by how they are parsed by key
;; generators like choose.

;; (defprotocol ShrinkPass
;;   (init [s])
;;   (applicable? [s state value])
;;   (app [s state value])
;;   (next-state [s state])
;;   (cost [_]))

;; (deftype HalfShrink []
;;   ShrinkPass
;;   (init [_s] true)
;;   (applicable? [_s state value]
;;     (and state (or (> -1000 value) (> value 1000))))
;;   (app [_s _state value]
;;     (unchecked-subtract (long value) (quot (long value) 2)))
;;   (next-state [_s _state] false)
;;   (cost [_] 0.25))

;; (deftype AddShrink []
;;   ShrinkPass
;;   (init [_s] 1000)
;;   (applicable? [_s state value]
;;     (and (not (zero? state)) (neg? value)))
;;   (app [_s state value]
;;     (unchecked-add value state))
;;   (next-state [_s state]
;;     (dec state))
;;   (cost [_] 1.0))

;; (deftype SubtractShrink []
;;   ShrinkPass
;;   (init [_s] 1000)
;;   (applicable? [_s state value]
;;     (and (not (zero? state)) (pos? value)))
;;   (app [_s state value]
;;     (unchecked-subtract value state))
;;   (next-state [_s state]
;;     (dec state))
;;   (cost [_] 1.0))

;; (def shrink-passes
;;   [(->HalfShrink)
;;    (->AddShrink)
;;    (->SubtractShrink)])

;; (defn shrinky-dink [l i g size]
;;   (assert (vector? l))
;;   (loop [passes (seq shrink-passes)
;;          state (init (first passes))
;;          value (nth l i)
;;          l l
;;          success false
;;          v nil
;;          it 0]
;;     #_(prn (first passes) it value)
;;     (if (> it 10000)
;;       (do
;;         (println "too much shrinky-dink")
;;         (throw (ex-info "too much shrinky-dink"
;;                         {:passes passes
;;                          :state state
;;                          :value value
;;                          :l l
;;                          :success success
;;                          :v v
;;                          :it it})))
;;       (if (and success (= 1000 it))
;;         [l v]
;;         (if (and (zero? value) (not success))
;;           nil
;;           (if (applicable? (first passes) state value)
;;             (cc/let [new-value (app (first passes) state value)]
;;               (if (or (and (neg? value) (> 1 new-value value))
;;                       (and (pos? value) (> value new-value -1)))
;;                 (cc/let [new-l (assoc l i new-value)
;;                          [t new-v] (regenerate new-l g size)]
;;                   (if (= :just t)
;;                     [new-l new-v]
;;                     #_(recur (seq shrink-passes) (init (first shrink-passes)) new-value new-l true new-v (inc it))
;;                     (recur passes (next-state (first passes) state) value l success v (inc it))))
;;                 (recur passes (next-state (first passes) state) value l success v (inc it))))
;;             (cc/let [passes (rest passes)]
;;               (if (seq passes)
;;                 (recur passes (init (first passes)) value l success v (inc it))
;;                 (when success
;;                   [l v])))))))))


;; (defn shrinky-dink [l i g size]
;;   (assert (vector? l))
;;   (cc/let [v (nth l i)
;;            threshold (if (neg? v)
;;                        ...
;;                        (Long/highestOneBit v))]
;;     (loop [passes (seq shrink-passes)
;;            state (init (first passes))
;;            value (nth l i)
;;            l l
;;            success false
;;            v nil
;;            it 0]
;;       #_(prn (first passes) it value)
;;       (if (> it 10000)
;;         (do
;;           (println "too much shrinky-dink")
;;           (throw (ex-info "too much shrinky-dink"
;;                           {:passes passes
;;                            :state state
;;                            :value value
;;                            :l l
;;                            :success success
;;                            :v v
;;                            :it it})))
;;         (if (and (zero? value) (not success))
;;           nil
;;           (if (applicable? (first passes) state value)
;;             (cc/let [new-value (app (first passes) state value)]
;;               (if (or (and (neg? value) (> 1 new-value value))
;;                       (and (pos? value) (> value new-value -1)))
;;                 (cc/let [new-l (assoc l i new-value)
;;                          [t new-v] (regenerate new-l g size)]
;;                   (if (= :just t)
;;                     (recur (seq shrink-passes) (init (first shrink-passes)) new-value new-l true new-v (inc it))
;;                     (recur passes (next-state (first passes) state) value l success v (inc it))))
;;                 (recur passes (next-state (first passes) state) value l success v (inc it))))
;;             (cc/let [passes (rest passes)]
;;               (if (seq passes)
;;                 (recur passes (init (first passes)) value l success v (inc it))
;;                 (when success
;;                   [l v])))))))))


;; (defn rose-tree-2 [v l g size make-tree]
;;   (letfn [(popping [new-v new-l success?]
;;             (when (seq new-l)
;;               (cc/let [new-new-l (pop new-l)
;;                        [t new-new-v] (regenerate new-new-l g size)]
;;                 (if (= :just t)
;;                   (recur new-new-v new-new-l true)
;;                   (if success?
;;                     (cc/list (rose-tree-2 new-v new-l g size make-tree))
;;                     (parallel))))))
;;           (parallel []
;;             (when (seq l)
;;               (cc/for [i (range (count l))
;;                        :let [r (shrinky-dink l i g size)]
;;                        :when r
;;                        :let [[new-l new-v] r]]
;;                 (rose-tree-2 new-v new-l g size make-tree))))]
;;     (make-tree
;;      (if (instance? clojure.lang.IObj v)
;;        (vary-meta v assoc ::l l ::s size)
;;        v)
;;      (popping nil l false))))


(defn rose-tree-3 [l make-tree]
  (letfn [(make-rose [l make-tree k]
            (make-tree
             l
             #_(if (instance? clojure.lang.IObj v)
               (vary-meta v assoc ::l l ::s size)
               v)
             (wide 0 l k)))
          (wide [i l k]
            (narrow i l (partial (fn f [ii l]
                                   (when-not (= ii i)
                                     (narrow (mod ii (count l))
                                             l
                                             (partial f (inc ii)))))
                                 (inc i))))
          (narrow [i l k]
            (let [n (nth l i)]
              (if (zero? n)
                (k l)
                (concat (for [n' (cons 0
                                       (->> (if (neg? n) -1 1)
                                            (iterate #(* % 2))
                                            (cc/map #(bit-or % (bit-and n 1)))
                                            (cc/map #(bit-or % (bit-shift-left (unsigned-bit-shift-right n 63) 63)))
                                            (take-while
                                             (if (neg? n)
                                               #(> % n)
                                               #(> n %)))))]
                          (make-rose (assoc l i n') make-tree k)) (k l)))))]
    (make-rose l make-tree (fn [l] nil))))

(defn rose-tree-1 [v l g size]
  (assert (vector? g))
  (assert (keyword? (nth g 0)))
  (rose-tree-2 v l g size rose/make-rose))

(comment

  (load-file "/home/kevin/Sync/src/surmise/src/com/manigfeald/surmise.clj")
  (load-file "/home/kevin/Sync/src/surmise/test/com/manigfeald/surmise_test.clj")

  (in-ns 'com.manigfeald.surmise)

  (in-ns 'com.manigfeald.surmise-test)

  (run-tests)

  (def main-thread (Thread/currentThread))
  
  (.interrupt main-thread)

  (rose-tree-2
   nil
   [-8 4]
   (:com.manigfeald.surmise/generator (list large-integer))
   Long/MAX_VALUE
   cc/vector)

  (do
    (with-out-str
      (prn
       (rose-tree-2
        nil
        (vec (range -2 2))
        (:com.manigfeald.surmise/generator (list byte))
        Long/MAX_VALUE
        cc/vector)))
    nil)

  
  (prn (com.manigfeald.surmise/shrinky-dink [-1] 0 (:com.manigfeald.surmise/generator com.manigfeald.surmise/large-integer) Long/MAX_VALUE))
  (shrinky-dink [133154750] 0 (:com.manigfeald.surmise/generator large-integer) Long/MAX_VALUE)


  (prn (com.manigfeald.surmise/shrinky-dink [-9125064582277025061]
                                            0
                                            (:com.manigfeald.surmise/generator nat)
                                            3))
  
  (prn (com.manigfeald.surmise/shrinky-dink [-1]
                                            0
                                            (:com.manigfeald.surmise/generator nat)
                                            3))

  )

Generated At Tue Dec 13 13:36:00 2022 PST original