(ns example-one.core)

(def suits [:hearts :spades :clubs :diamonds])
(def ranks [:2 :3 :4 :5 :6 :7 :8 :9 :10 :jack :queen :king :ace])

(def deck (vec (for [s suits, r ranks] [s r])))

(set! *warn-on-reflection* true)

(defprotocol ImmutableRandomNumberGenerator
  (next-long [self])
  (next-state [self]))

(defrecord LaggedFibonacciGenerator [
  ^long low-backref
  ^long high-backref
  ^long current-index
  ^longs state
  ]
  ImmutableRandomNumberGenerator
  (next-long ^long [self]
    (aget ^longs (:state self) (:current-index self)))
  (next-state ^LaggedFibonacciGenerator [self]
    (let [next-index (inc (:current-index self))
          low-backref (:low-backref self)
          high-backref (:high-backref self)
          low-index (mod (- next-index low-backref) high-backref)
          high-index (mod (- next-index high-backref) high-backref)
          old-state (:state self)
          low-value (aget old-state low-index)
          high-value (aget old-state high-index)
          new-state (aclone old-state)]
      (aset-long new-state next-index (unchecked-add ^long low-value ^long high-value))
      (LaggedFibonacciGenerator. low-backref high-backref next-index new-state))))

(defn- make-rng ^LaggedFibonacciGenerator []
  (let [seed-prng (java.security.SecureRandom.)
        init-state (long-array (repeatedly 55 #(.nextLong seed-prng)))]
    (LaggedFibonacciGenerator. 24 55 0 init-state)))

(defn shuffle-deck
  "Creates a shuffled deck of cards"
  []
  (loop [deck (transient deck), i (dec (count deck)), rng (make-rng)]
    (if (zero? i)
      (persistent! deck)
      (let [j (mod (next-long rng) i)]
        (recur (assoc! deck i (deck j) j (deck i))
               (dec i)
               (next-state rng))))))

(let [shuffled (shuffle-deck)]
  (println (count shuffled))
  (println shuffled))