#!/usr/bin/env -S clojure -M
;; uuid256.clj — reference implementation of README.md (UUID256, random layout) in Clojure 1.11+ (JDK only, no deps).
;;
;;   clojure -M uuid256.clj                   ; self-tests + exact duplicate check over 1,000,000 ids
;;   clojure -M uuid256.clj -n 200000 -i 3    ; 2e5 ids with 3 planted duplicates (proves detection)
;;   clojure -M uuid256.clj -g [count]        ; just print one (or count) new ids, nothing else
;;   (macOS/Homebrew: export JAVA_HOME=/opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home)
;;
;;   (uuid256/generate)                       ; byte[32], §5.1: SecureRandom, ver=4 / var=10 applied
;;   (uuid256/to-canonical id)                ; §3.1: "0001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1f"
;;   (uuid256/parse txt) (uuid256/parse txt false)   ; §6: strict / lenient → byte[32]; throws ex-info {:kind :length|:hyphen|:char|:version}
;;
;; Bulk check: one SecureRandom fill for all ids, §4 applied to each, then a HashMap keyed by ByteBuffer (content
;; equality) → first index; a repeat is a duplicate.  Exact.

(ns uuid256
  (:import [java.security SecureRandom]
           [java.nio ByteBuffer]
           [java.util HashMap Locale]))

(def ^:const SIZE 32)
(def ^:const TEXT-LEN 68)
(def ^:const VERSION 4)
(def strict-re #"^[0-9a-f]{16}-[0-9a-f]{8}-4[0-9a-f]{7}-[89ab][0-9a-f]{7}-[0-9a-f]{24}$")
(def ^SecureRandom rng (SecureRandom.))                                   ; §5.2 — OS CSPRNG
(def NIL (byte-array SIZE))                                               ; §8
(def MAX (byte-array (repeat SIZE (unchecked-byte 0xFF))))

(defn set-ver-var!
  "§4 — force version nibble (byte 12) and variant bits (byte 16), in place at offset off. Returns b."
  (^bytes [^bytes b] (set-ver-var! b 0))
  (^bytes [^bytes b ^long off]
   (aset-byte b (+ off 12) (unchecked-byte (bit-or (bit-and (aget b (+ off 12)) 0x0F) (bit-shift-left VERSION 4))))  ; hex digit 24 = '4'
   (aset-byte b (+ off 16) (unchecked-byte (bit-or (bit-and (aget b (+ off 16)) 0x3F) 0x80)))                        ; hex digit 32 in [89ab]
   b))

(defn generate
  "§5.1 — a new id straight from the OS CSPRNG (byte[32], big-endian)."
  ^bytes []
  (let [b (byte-array SIZE)] (.nextBytes rng b) (set-ver-var! b)))

(defn strict?
  "§6 — ver == 4 && var == 10."
  [^bytes b]
  (and (= (alength b) SIZE)
       (= VERSION (bit-shift-right (bit-and (aget b 12) 0xFF) 4))
       (= 2 (bit-shift-right (bit-and (aget b 16) 0xFF) 6))))

(defn to-canonical
  "§3.1 — canonical text: 16-8-8-8-24, lowercase, 68 chars."
  ^String [^bytes b]
  (when (not= (alength b) SIZE) (throw (IllegalArgumentException. "UUID256 is exactly 32 bytes")))
  (let [sb (StringBuilder. TEXT-LEN)]
    (dotimes [i SIZE]
      (let [v (bit-and (aget b i) 0xFF)]
        (.append sb (Character/forDigit (bit-shift-right v 4) 16))
        (.append sb (Character/forDigit (bit-and v 0x0F) 16))
        (when (#{7 11 15 19} i) (.append sb \-))))                        ; after hex digits 16, 24, 32, 40
    (str sb)))

(defn to-compact ^String [^bytes b] (.replace (to-canonical b) "-" ""))

(defn parse
  "§6 — parse canonical (68) or compact (64) form, any case; strict checks ver/var. Throws ex-info with :kind."
  (^bytes [^String s] (parse s true))
  (^bytes [^String s strict]
   (let [t (.toLowerCase s Locale/ROOT)
         fail (fn [kind msg] (throw (ex-info (str "uuid256: " msg) {:kind kind})))
         hex (cond
               (= (count t) TEXT-LEN) (do (when-not (every? #(= \- (.charAt t %)) [16 25 34 43]) (fail :hyphen "hyphen at wrong position"))
                                          (.replace t "-" ""))
               (= (count t) 64) t
               :else (fail :length (str "wrong length " (count t))))]
     (when-not (re-matches #"[0-9a-f]{64}" hex) (fail :char "invalid character"))
     (let [b (byte-array SIZE)]
       (dotimes [i SIZE] (aset-byte b i (unchecked-byte (Integer/parseInt (subs hex (* 2 i) (+ 2 (* 2 i))) 16))))
       (when (and strict (not (strict? b))) (fail :version "unsupported version/variant"))
       b))))

;; ---------------------------------------------------------------------------
;; Self-tests (spec §11 vectors, §3.2/§6 parser rules, live generation)
;; ---------------------------------------------------------------------------
(defn- kind-of ([s] (kind-of s true)) ([s strict] (try (parse s strict) :ok (catch clojure.lang.ExceptionInfo e (:kind (ex-data e))))))
(defn- bytes= [^bytes a ^bytes b] (java.util.Arrays/equals a b))
(defn- commas [n] (String/format Locale/US "%,d" (to-array [(long n)])))

(defn self-test []
  (let [vectors [["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" "0001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1f"]
                 ["fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0" "fffefdfcfbfaf9f8-f7f6f5f4-43f2f1f0-afeeedec-ebeae9e8e7e6e5e4e3e2e1e0"]]
        f1 (count (remove true?
             (map-indexed (fn [i [raw expected]]
                            (let [b (set-ver-var! (parse raw false)) txt (to-canonical b)
                                  ok (and (= txt expected) (strict? b) (bytes= (parse txt) b) (bytes= (parse (.toUpperCase txt)) b)
                                          (re-matches strict-re txt) (= :ok (kind-of raw false)) (= :version (kind-of raw))   ; raw compact: lenient ok, strict rejects
                                          (= (to-compact b) (.replace txt "-" "")))]
                              (println (format "  spec §11 vector %d: %s  %s" (inc i) (if ok "PASS" "FAIL") txt))
                              (boolean ok)))
                          vectors)))
        nil-txt (to-canonical NIL)
        ok2 (and (= :hyphen (kind-of "0001020304050607_08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1f"))
                 (= :length (kind-of "0001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1"))
                 (= :char (kind-of "0001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1g"))
                 (= :version (kind-of nil-txt)) (= :ok (kind-of nil-txt false)) (bytes= (parse nil-txt false) NIL)
                 (= (to-canonical MAX) (clojure.string/join "-" (map #(apply str (repeat % \f)) [16 8 8 8 24]))))
        _ (println (format "  parser rules (§3.2/§6):  %s" (if ok2 "PASS" "FAIL")))
        f3 (count (remove true?
             (for [_ (range 3)]
               (let [b (generate) txt (to-canonical b)
                     ok (and (strict? b) (re-matches strict-re txt) (bytes= (parse txt) b))]
                 (println (format "  generate: %s  %s" txt (if ok "ok" "BAD")))
                 (boolean ok)))))]
    (+ f1 (if ok2 0 1) f3)))

;; ---------------------------------------------------------------------------
;; Bulk exact duplicate check
;; ---------------------------------------------------------------------------
(defn run [n planted]
  (println "UUID256 reference implementation (Clojure) — README.md (256-bit random, 16-8-8-8-24 text)\n")
  (println "Self-tests:")
  (when (pos? (self-test)) (println "  self-test FAILED — aborting") (System/exit 1))
  (let [k 200000 t0 (System/nanoTime)]
    (dotimes [_ k] (generate))
    (let [per-call (/ (- (System/nanoTime) t0) 1e9 k)]
      (println (format "\ngenerate per-call cost: %.2f µs → %.2f M ids/s single-threaded (1e9 ids would take ~%.0f min just to generate)"
                       (* per-call 1e6) (/ 1 per-call 1e6) (/ (* 1e9 per-call) 60)))))
  (println (format "\nBulk exact duplicate check: n=%s ids, planted duplicates=%d" (commas n) planted))
  (let [t0 (System/nanoTime)
        buf (byte-array (* n SIZE))
        _ (.nextBytes rng buf)                                                     ; §5.2, one call
        _ (dotimes [i n] (set-ver-var! buf (* i SIZE)))                             ; §4 on every id
        t1 (System/nanoTime)
        plants (for [kk (range planted)
                     :let [src (* kk (quot (quot n 2) planted))]]                    ; stride: distinct pairs for every k < planted <= n/2
                 [src (- n 1 src)])
        _ (doseq [[src dst] plants]                                                  ; id[dst] := id[src]
            (System/arraycopy buf (* src SIZE) buf (* dst SIZE) SIZE)
            (println (format "  planted: id[%s] := id[%s]" (commas dst) (commas src))))
        bad (count (filter (fn [i] (or (not= VERSION (bit-shift-right (bit-and (aget buf (+ (* i SIZE) 12)) 0xFF) 4))
                                       (not= 2 (bit-shift-right (bit-and (aget buf (+ (* i SIZE) 16)) 0xFF) 6)))) (range n)))
        seen (HashMap. (* 2 n))
        dups (persistent!
               (reduce (fn [acc i]
                         (let [key (ByteBuffer/wrap buf (* i SIZE) SIZE)]            ; content-based equals/hashCode
                           (if-let [j (.get seen key)] (conj! acc [j i]) (do (.put seen key (int i)) acc))))
                       (transient []) (range n)))
        t2 (System/nanoTime)
        id-at (fn [i] (java.util.Arrays/copyOfRange buf (* i SIZE) (* (inc i) SIZE)))]
    (doseq [[i j] dups] (println (format "  DUPLICATE  id[%s] == id[%s]  %s" (commas i) (commas j) (to-canonical (id-at i)))))
    (let [step (max 1 (quot n 1000))
          rt-ok (count (filter (fn [i] (let [id (id-at i)] (bytes= (parse (to-canonical id)) id))) (range 0 n step)))
          log2p (- (* 2 (/ (Math/log n) (Math/log 2))) 251)]
      (println "\n==== RESULT ====")
      (println (format "ids generated:                   %s   (SecureRandom + §4 in %.1fs; HashMap dedup in %.1fs)" (commas n) (/ (- t1 t0) 1e9) (/ (- t2 t1) 1e9)))
      (println (format "version/variant violations:      %s" (commas bad)))
      (println (format "text round-trips (sampled):      %s ok" (commas rt-ok)))
      (println (str (format "FULL 256-bit DUPLICATES:         %s" (commas (count dups)))
                    (when (pos? planted) (format "   (planted: %d — %s)" planted (if (= (count dups) planted) "all detected" "COUNT MISMATCH")))))
      (when (empty? dups) (println (format "  → no duplicates among %s ids" (commas n))))
      (println (format "expected P(any collision) §5.3:  n²/2²⁵¹ ≈ 2^%.1f ≈ %.1e" log2p (Math/pow 2 log2p)))
      (if (= (count dups) planted) 0 1))))

(defn -main [& args]
  (when (and (#{"-p" "-P"} (first args)) (second args))                                   ; parse mode: -p strict, -P lenient
    (let [k (kind-of (second args) (= "-p" (first args)))]
      (if (= k :ok) (println "ok" (to-compact (parse (second args) (= "-p" (first args))))) (println "error" (name k)))
      (System/exit (if (= k :ok) 0 1))))
  (if (= "-g" (first args))
    (let [c (try (Long/parseLong (second args)) (catch Exception _ 1)) count (if (neg? c) 1 c)]   ; -g N: exactly N (0 allowed); junk → 1
      (dotimes [_ count] (println (to-canonical (generate))))
      0)
    (let [pairs (partition-all 2 args)
          num (fn [s] (try (Long/parseLong s) (catch Exception _ nil)))
          bad (or (odd? (count args)) (some (fn [[k v]] (or (not (#{"-n" "-i"} k)) (nil? (num v)))) pairs))]
      (if bad
        (do (println "usage: clojure -M uuid256.clj [-g [count]] [-n count] [-i planted_dups] | -p|-P <text>") 2)
        (let [opts (into {} (map (fn [[k v]] [k (num v)]) pairs))
              n (get opts "-n" 1000000) planted (get opts "-i" 0)]
          (if (or (< n 2) (neg? planted) (> planted (quot n 2)))
            (do (println "-n must be >= 2 and -i at most n/2") 2)
            (run n planted)))))))

(System/exit (apply -main *command-line-args*))
