(ns edition2022.day11
  (:require [clojure.math :as math]
            [clojure.string :as str])
  (:import (clojure.lang PersistentQueue)))

(def example "Monkey 0:\n  Starting items: 79, 98\n  Operation: new = old * 19\n  Test: divisible by 23\n    If true: throw to monkey 2\n    If false: throw to monkey 3\n\nMonkey 1:\n  Starting items: 54, 65, 75, 74\n  Operation: new = old + 6\n  Test: divisible by 19\n    If true: throw to monkey 2\n    If false: throw to monkey 0\n\nMonkey 2:\n  Starting items: 79, 60, 97\n  Operation: new = old * old\n  Test: divisible by 13\n    If true: throw to monkey 1\n    If false: throw to monkey 3\n\nMonkey 3:\n  Starting items: 74\n  Operation: new = old + 3\n  Test: divisible by 17\n    If true: throw to monkey 0\n    If false: throw to monkey 1\n")

(defn parse-arg [arg]
  (if (= arg "old")
    "%"
    arg))

(defn eval-operation [operation-str]
  (let [exp-str (str/replace operation-str #"  Operation: new = " "")
        symbols (str/split exp-str #" ")
        s (str/join " " [(second symbols) (parse-arg (first symbols)) (parse-arg (nth symbols 2))])]
    (eval (read-string (str "#(" s ")")))))

(defn parse-items [items-str]
  (->> (str/replace items-str #"  Starting items: " "")
       (#(str/split % #", "))
       (map read-string)
       (map bigint)
       (reduce conj PersistentQueue/EMPTY)
       ))

(defn parse-last-number [line]
  (->> (str/split line #" ")
       (last)
       (#(str/replace % #":" ""))
       (read-string)))

(defn parse-monkey [lines]
  (let [idx (parse-last-number (first lines))
        items (parse-items (second lines))
        operation (eval-operation (nth lines 2))
        div (parse-last-number (nth lines 3))
        true-monkey (parse-last-number (nth lines 4))
        false-monkey (parse-last-number (nth lines 5))]
    {:id           idx
     :items        items
     :operation    operation
     :div          div
     :true-monkey  true-monkey
     :false-monkey false-monkey}))

(defn parse-monkeys [input]
  (let [lines (str/split-lines input)]
    (->> (partition-by #(= "" %) lines)
         (remove #(= 1 (count %)))
         (map parse-monkey)
         (into []))))

(defn peek-item [monkey]
  (peek (monkey :items)))

(defn remove-item [monkey]
  (let [items (monkey :items)]
    (assoc monkey :items (pop items))))

(defn add-item [monkey item]
  (let [items (monkey :items)]
    (assoc monkey :items (conj items item))))

(defn compute-worry-level [monkey item]
  ;(math/floor-div ((monkey :operation) item) 3)
  ((monkey :operation) item)
  )

(defn choose-where-to-pass [monkey item]
  (if (= 0 (mod item (monkey :div)))
    (monkey :true-monkey)
    (monkey :false-monkey)))

(defn register-inspection [monkey]
  (assoc monkey :inspections (inc (get monkey :inspections 0))))

(defn pass-item [current-monkey monkeys]
  (let [item (peek-item current-monkey)]
    (if item
      (let [new-iem (compute-worry-level current-monkey item)
            idx-to-pass (choose-where-to-pass current-monkey new-iem)
            monkey-to-pass (nth monkeys idx-to-pass)]
        (recur (register-inspection (remove-item current-monkey)) (assoc monkeys idx-to-pass (add-item monkey-to-pass new-iem))))
      (assoc monkeys (current-monkey :id) current-monkey))))

(defn play-round [monkeys]
  (loop [m monkeys
         idx 0]
    (if (< idx (count m))
      (recur (pass-item (nth m idx) m) (inc idx))
      m)))

(defn play-rounds [monkeys rounds]
  (loop [m monkeys
         r 0]
    (if (< r rounds)
      (recur (play-round m) (inc r))
      m)))

(def example-monkeys (parse-monkeys example))
(def first-pass (pass-item (first example-monkeys) example-monkeys))

(defn solution1 [input]
  (->> (play-rounds (parse-monkeys input) 10000)
       (map :inspections)
       (sort)
       (reverse)
       (#(* (first %) (second %)))))

(comment
  (math/floor-div 5 4)
  ((eval-operation "new = old * 3") 4)
  (seq (parse-items "  Starting items: 79, 98"))
  (seq (conj (parse-items "  Starting items: 79, 98") 67))
  (conj (parse-items "  Starting items: 79, 98") 3)
  (parse-last-number "  Test: divisible by 5")
  (parse-last-number "Monkey 3:")
  (parse-monkeys example)
  (map #(seq (:items %)) first-pass)
  (map #(seq (:items %)) (pass-item (second first-pass) first-pass))
  (map #(seq (:items %)) (play-round (parse-monkeys example)))
  (map #(seq (:items %)) (play-rounds example-monkeys 20))
  (play-rounds example-monkeys 20)
  (solution1 example)
  (solution1 (slurp "resources/day11.in"))
  )