;;; advanced.clj       Dave Reed       4/1/26

(def pos-ints (rest (range)))
(def evens (filter even? pos-ints))
(def odds (filter odd? pos-ints))

(def div-by (comp zero? rem))

(defn multiples [n numlist]
  (filter (fn [x] (div-by x n)) numlist))

(defn hailstone-tail [num]
  (defn hailstone-help [N so-far]
    (cond (= N 1) so-far
          (even? N) (recur (/ N 2) (inc so-far))
          :else (recur (inc (* 3 N)) (inc so-far))))
  (hailstone-help num 1))

(def squares (map (fn [x] (* x x)) pos-ints))
(def powers-of-2 (map (fn [x] (int (Math/pow 2 x))) pos-ints))
(def hail (map hailstone-tail pos-ints))

(def bizz-buzz (map (fn [x] (cond (and (div-by x 3) (div-by x 5)) :bizz-buzz
                                       (div-by x 3) :bizz
                                       (div-by x 5) :buzz
                                       :else x))  pos-ints))

(defn lazy-nums [start] [start (delay (lazy-nums (inc start)))])
(def nums (lazy-nums 1))

(defn lazy-take [num-to-take num-stream]
  (defn take-help [num strm sofar]
    (if (zero? num)
      (reverse sofar)
      (recur (dec num) (force (second strm))
             (cons (first strm) sofar))))
  (take-help num-to-take num-stream '()))

(defn primes-to [maxNum]
  (defn primes-help [poss sofar]
    (if (empty? poss)
       (reverse sofar)
       (recur (filter (fn [x] (not (div-by x (first poss)))) poss)
              (cons (first poss) sofar))))
  (primes-help (range 2 (inc maxNum)) '()))

(defn sieve [s]
  [(first s) (delay (sieve (filter (fn [x] (not (div-by x (first s))))
                                   (rest s))))])

(def primes (sieve (rest pos-ints)))


(defn scale [x]
  (fn [val] (* val x)))

(defn my-comp [f1 f2]
  (fn [x] (f1 (f2 x))))

(def my-second (my-comp first rest))
(def my-last (my-comp first reverse))

(defn lockbox [message password]
  (fn [pass] (if (= pass password) message :LOCKED)))

(def secret (lockbox "Howdy" "foobar"))


(defn Die [num-sides]
  (fn [op]
    (cond (= op :roll) (inc (rand-int num-sides))
          (= op :get-sides) num-sides
          :else nil)))

(defprotocol DieInterface
  (roll [this])
  (get-sides [this])
  (get-rolls [this]))

(deftype Die [num-sides ^:unsynchronized-mutable num-rolls]
  DieInterface
  (roll [this]
    (do (set! num-rolls (inc num-rolls)) (inc (rand-int num-sides))) )
  (get-sides [this]
    num-sides)
  (get-rolls [this]
    num-rolls))

(defn make-die
  ([] (Die. 6 0))
  ([num-sides] (Die. num-sides 0)))