#!/usr/bin/env Rscript
# uuid256.R — reference implementation of README.md (UUID256, random layout) in base R (4.x, no packages).
#
#   Rscript uuid256.R                    # self-tests + exact duplicate check over 1,000,000 ids
#   Rscript uuid256.R -n 200000 -i 3     # 2e5 ids with 3 planted duplicates (proves detection)
#   Rscript uuid256.R -g [count]         # just print one (or count) new ids, nothing else
#
#   source("uuid256.R")                  # (when sourced, nothing runs; the functions are defined)
#   id  <- uuid256_generate()            # raw[32], §5.1: /dev/urandom (OS CSPRNG), ver=4 / var=10 applied
#   txt <- uuid256_to_canonical(id)      # §3.1: "0001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1f"
#   b   <- uuid256_parse(txt)            # §6: strict; uuid256_parse(txt, strict = FALSE) lenient; stop()s with class "uuid256_parse_error"
#
# Randomness (§5.2): /dev/urandom — base R has no CSPRNG (sample()/runif() are Mersenne Twister and MUST NOT be used).
# Bulk check: one urandom read for all ids, vectorised §4, ids as 64-hex strings, duplicated() (hash-based) — exact.

UUID256_VERSION  <- 4L
UUID256_STRICT_RE <- "^[0-9a-f]{16}-[0-9a-f]{8}-4[0-9a-f]{7}-[89ab][0-9a-f]{7}-[0-9a-f]{24}$"
UUID256_NIL <- as.raw(rep(0L, 32))                                   # §8
UUID256_MAX <- as.raw(rep(255L, 32))

# §5.2 — n bytes from the OS CSPRNG
uuid256_os_random <- function(n) {
  con <- file("/dev/urandom", "rb", raw = TRUE); on.exit(close(con))   # raw = TRUE: device, not a regular file
  out <- raw(0)
  while (length(out) < n) out <- c(out, readBin(con, "raw", n - length(out)))
  out
}

# §4 — force version nibble (byte 13, 1-based) and variant bits (byte 17); vectorised over k ids laid out consecutively
uuid256_set_ver_var <- function(b, k = length(b) %/% 32L) {
  i12 <- 13L + 32L * (seq_len(k) - 1L); i16 <- i12 + 4L
  b[i12] <- as.raw(bitwOr(bitwAnd(as.integer(b[i12]), 0x0FL), bitwShiftL(UUID256_VERSION, 4L)))   # hex digit 24 = '4'
  b[i16] <- as.raw(bitwOr(bitwAnd(as.integer(b[i16]), 0x3FL), 0x80L))                              # hex digit 32 in [89ab]
  b
}

# §5.1 — one id (raw[32], big-endian: [1] most significant)
uuid256_generate <- function() uuid256_set_ver_var(uuid256_os_random(32L), 1L)

# §6 — ver == 4 && var == 10
uuid256_is_strict <- function(b) length(b) == 32L && bitwShiftR(as.integer(b[13]), 4L) == UUID256_VERSION && bitwShiftR(as.integer(b[17]), 6L) == 2L

# §3.1 — canonical text: 16-8-8-8-24, lowercase, 68 chars
uuid256_to_canonical <- function(b) {
  if (length(b) != 32L) stop("uuid256: value must be exactly 32 bytes")
  h <- paste(as.character(b), collapse = "")                          # as.character(raw) is lowercase hex
  paste(substr(h, 1, 16), substr(h, 17, 24), substr(h, 25, 32), substr(h, 33, 40), substr(h, 41, 64), sep = "-")
}

# §6 — parse canonical (68) or compact (64), any case → raw[32]
uuid256_parse <- function(s, strict = TRUE) {
  err <- function(kind, msg) stop(structure(class = c("uuid256_parse_error", "error", "condition"), list(message = paste0("uuid256: ", msg), call = NULL, kind = kind)))
  t <- tolower(s)
  if (nchar(t) == 68L) {
    if (any(substring(t, c(17, 26, 35, 44), c(17, 26, 35, 44)) != "-")) err("hyphen", "hyphen at wrong position")
    t <- gsub("-", "", t, fixed = TRUE)
  } else if (nchar(t) != 64L) err("length", "wrong length")
  if (!grepl("^[0-9a-f]{64}$", t)) err("char", "invalid character")
  b <- as.raw(strtoi(substring(t, seq(1, 63, 2), seq(2, 64, 2)), 16L))
  if (strict && !uuid256_is_strict(b)) err("version", "unsupported version/variant")
  b
}

# ---------------------------------------------------------------------------
if (sys.nframe() == 0L) {   # run only as a script, not when sourced

kind_of <- function(s, strict = TRUE) tryCatch({ uuid256_parse(s, strict); "ok" }, uuid256_parse_error = function(e) e$kind)
commas <- function(v) format(v, big.mark = ",", scientific = FALSE, trim = TRUE)

self_test <- function() {
  fails <- 0L
  vectors <- list(
    c("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "0001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1f"),
    c("fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0", "fffefdfcfbfaf9f8-f7f6f5f4-43f2f1f0-afeeedec-ebeae9e8e7e6e5e4e3e2e1e0"))
  for (i in seq_along(vectors)) {
    raw <- vectors[[i]][1]; expected <- vectors[[i]][2]
    b <- uuid256_set_ver_var(uuid256_parse(raw, strict = FALSE), 1L)
    txt <- uuid256_to_canonical(b)
    ok <- txt == expected && uuid256_is_strict(b) && identical(uuid256_parse(txt), b) && identical(uuid256_parse(toupper(txt)), b) &&
          grepl(UUID256_STRICT_RE, txt) && kind_of(raw, FALSE) == "ok" && kind_of(raw) == "version"        # raw compact: lenient ok, strict rejects
    cat(sprintf("  spec §11 vector %d: %s  %s\n", i, if (ok) "PASS" else "FAIL", txt))
    if (!ok) fails <- fails + 1L
  }
  nil_txt <- uuid256_to_canonical(UUID256_NIL)
  ok <- kind_of("0001020304050607_08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1f") == "hyphen" &&
        kind_of("0001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1") == "length" &&
        kind_of("0001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1g") == "char" &&
        kind_of(nil_txt) == "version" && kind_of(nil_txt, FALSE) == "ok" && identical(uuid256_parse(nil_txt, FALSE), UUID256_NIL) &&
        uuid256_to_canonical(UUID256_MAX) == paste(strrep("f", 16), strrep("f", 8), strrep("f", 8), strrep("f", 8), strrep("f", 24), sep = "-")
  cat(sprintf("  parser rules (§3.2/§6):  %s\n", if (ok) "PASS" else "FAIL"))
  if (!ok) fails <- fails + 1L
  for (i in 1:3) {
    b <- uuid256_generate(); txt <- uuid256_to_canonical(b)
    ok <- uuid256_is_strict(b) && grepl(UUID256_STRICT_RE, txt) && identical(uuid256_parse(txt), b)
    cat(sprintf("  uuid256_generate(): %s  %s\n", txt, if (ok) "ok" else "BAD"))
    if (!ok) fails <- fails + 1L
  }
  fails
}

main <- function(args) {
  n <- 1000000L; planted <- 0L
  if (length(args) >= 2L && args[1L] %in% c("-p", "-P")) {                    # parse mode: -p strict, -P lenient
    r <- tryCatch(uuid256_parse(args[2L], strict = args[1L] == "-p"), uuid256_parse_error = function(e) e)
    if (inherits(r, "uuid256_parse_error")) { cat("error ", r$kind, "\n", sep = ""); return(1L) }
    cat("ok ", paste(as.character(r), collapse = ""), "\n", sep = ""); return(0L)
  }
  i <- 1L
  while (i <= length(args)) {
    a <- args[i]
    if (a == "-g") {                                                        # just print ids: -g [count]
      cnt <- if (i + 1L <= length(args)) suppressWarnings(as.integer(args[i + 1L])) else NA
      if (is.na(cnt) || cnt < 0L) cnt <- 1L                                    # -g N: exactly N (0 allowed); junk → 1
      for (k in seq_len(cnt)) cat(uuid256_to_canonical(uuid256_generate()), "\n", sep = "")
      return(0L)
    } else if (a == "-n" && i + 1L <= length(args)) { i <- i + 1L; n <- as.integer(args[i]) }
    else if (a == "-i" && i + 1L <= length(args)) { i <- i + 1L; planted <- as.integer(args[i]) }
    else { cat("usage: Rscript uuid256.R [-g [count]] [-n count] [-i planted_dups]\n"); return(2L) }
    i <- i + 1L
  }
  if (is.na(n) || n < 2L || planted < 0L || planted > n %/% 2L) { cat("-n must be >= 2 and -i at most n/2\n"); return(2L) }

  cat("UUID256 reference implementation (R) — README.md (256-bit random, 16-8-8-8-24 text)\n\n")
  cat("Self-tests:\n")
  if (self_test() > 0L) { cat("  self-test FAILED — aborting\n"); return(1L) }

  K <- 20000L
  t0 <- proc.time()[["elapsed"]]; for (k in seq_len(K)) uuid256_generate()
  per_call <- (proc.time()[["elapsed"]] - t0) / K
  cat(sprintf("\nuuid256_generate() per-call cost: %.2f µs → %.3f M ids/s single-threaded (1e9 ids would take ~%.0f min just to generate; opening /dev/urandom per call dominates)\n",
              per_call * 1e6, 1 / per_call / 1e6, 1e9 * per_call / 60))

  cat(sprintf("\nBulk exact duplicate check: n=%s ids, planted duplicates=%d\n", commas(n), planted))
  t0 <- proc.time()[["elapsed"]]
  buf <- uuid256_set_ver_var(uuid256_os_random(n * 32L), n)              # §5.2 one read, §4 vectorised
  t1 <- proc.time()[["elapsed"]]
  for (k in seq_len(planted) - 1L) {                                       # id[dst] := id[src]
    src <- k * ((n %/% 2L) %/% planted); dst <- n - 1L - src            # stride: distinct pairs for every k < planted <= n/2
    buf[dst * 32L + 1:32] <- buf[src * 32L + 1:32]
    cat(sprintf("  planted: id[%s] := id[%s]\n", commas(dst), commas(src)))
  }
  i12 <- 13L + 32L * (0:(n - 1L)); i16 <- i12 + 4L
  bad <- sum(bitwShiftR(as.integer(buf[i12]), 4L) != UUID256_VERSION | bitwShiftR(as.integer(buf[i16]), 6L) != 2L)
  hexm <- matrix(as.character(buf), ncol = 32L, byrow = TRUE)              # n × 32 hex byte strings
  ids <- do.call(paste0, as.data.frame(hexm, stringsAsFactors = FALSE))    # n × 64-hex strings, vectorised
  dup_idx <- which(duplicated(ids))                                        # exact (hash-based)
  t2 <- proc.time()[["elapsed"]]
  for (j in dup_idx) {
    i0 <- match(ids[j], ids)                                               # first occurrence
    cat(sprintf("  DUPLICATE  id[%s] == id[%s]  %s\n", commas(i0 - 1L), commas(j - 1L), uuid256_to_canonical(uuid256_parse(ids[j], strict = FALSE))))
  }
  step <- max(1L, n %/% 1000L); rt_ok <- 0L
  for (i0 in seq(1L, n, by = step)) { id <- buf[(i0 - 1L) * 32L + 1:32]; if (identical(uuid256_parse(uuid256_to_canonical(id)), id)) rt_ok <- rt_ok + 1L }

  cat("\n==== RESULT ====\n")
  cat(sprintf("ids generated:                   %s   (/dev/urandom + §4 in %.1fs; hex + duplicated() in %.1fs)\n", commas(n), t1 - t0, t2 - t1))
  cat(sprintf("version/variant violations:      %s\n", commas(bad)))
  cat(sprintf("text round-trips (sampled):      %s ok\n", commas(rt_ok)))
  cat(sprintf("FULL 256-bit DUPLICATES:         %s%s\n", commas(length(dup_idx)),
              if (planted > 0L) sprintf("   (planted: %d — %s)", planted, if (length(dup_idx) == planted) "all detected" else "COUNT MISMATCH") else ""))
  if (length(dup_idx) == 0L) cat(sprintf("  → no duplicates among %s ids\n", commas(n)))
  log2p <- 2 * log2(n) - 251
  cat(sprintf("expected P(any collision) §5.3:  n²/2²⁵¹ ≈ 2^%.1f ≈ %.1e\n", log2p, 2^log2p))
  if (length(dup_idx) == planted) 0L else 1L
}

quit(status = main(commandArgs(trailingOnly = TRUE)))
}
