///|
/// A 128-bit identifier as RFC 9562 lays it out. Compared, hashed and
/// ordered by its bytes, so a sorted list of time-ordered ids is in time
/// order.
pub struct Uuid {
  priv hi : UInt64
  priv lo : UInt64
} derive(Eq, Compare, Hash, Debug)

///|
pub extend Uuid with Eq::{equal, not_equal}

///|
pub extend Uuid with Compare::{compare, op_lt, op_le, op_ge, op_gt}

///|
pub extend Uuid with Hash::{hash, hash_combine}

///|
pub extend Uuid with Debug::{to_repr}

///|
/// Why text or bytes are not a UUID.
pub(all) suberror Refused {
  /// Text that is not 32 hexadecimal digits once braces, hyphens and the
  /// `urn:uuid:` prefix are set aside; `at` is the offending character.
  Malformed(at~ : Int32, reason~ : String)
  /// Bytes of the wrong length.
  Size(want~ : Int32, got~ : Int32)
} derive(Eq, Debug)

///|
pub extend Refused with Eq::{equal, not_equal}

///|
pub extend Refused with Debug::{to_repr}

///|
pub impl Show for Refused with fn output(self, logger) {
  logger.write_string(
    match self {
      Malformed(at~, reason~) => "\{reason} at \{at}"
      Size(want~, got~) => "\{want} bytes wanted, \{got} given"
    },
  )
}

///|
pub extend Refused with Show::{output, to_string}

///|
/// Who an identifier's variant bits say it belongs to.
pub(all) enum Variant {
  /// Reserved for NCS backward compatibility.
  Ncs
  /// RFC 9562, the variant every version here writes.
  Rfc
  /// Reserved for Microsoft backward compatibility.
  Microsoft
  /// Reserved for future definition.
  Future
} derive(Eq, Debug)

///|
pub extend Variant with Eq::{equal, not_equal}

///|
pub extend Variant with Debug::{to_repr}

///|
pub impl Show for Variant with fn output(self, logger) {
  logger.write_string(
    match self {
      Ncs => "Ncs"
      Rfc => "Rfc"
      Microsoft => "Microsoft"
      Future => "Future"
    },
  )
}

///|
pub extend Variant with Show::{output, to_string}

///|
/// The identifier from its high and low 64 bits.
pub fn Uuid::of_parts(hi : UInt64, lo : UInt64) -> Uuid {
  { hi, lo, }
}

///|
/// The high and low 64 bits.
pub fn Uuid::parts(self : Uuid) -> (UInt64, UInt64) {
  (self.hi, self.lo)
}

///|
/// The identifier from its 16 bytes, most significant first.
pub fn Uuid::of_bytes(bytes : BytesView) -> Uuid raise Refused {
  if bytes.length() != 16 {
    raise Size(want=16, got=bytes.length())
  }
  of16(bytes)
}

///|
fn of16(bytes : BytesView) -> Uuid {
  let mut hi = 0UL
  let mut lo = 0UL
  for i in 0..<8 {
    hi = (hi << 8) | bytes[i].to_uint64()
    lo = (lo << 8) | bytes[i + 8].to_uint64()
  }
  { hi, lo, }
}

///|
/// The 16 bytes, most significant first.
pub fn Uuid::bytes(self : Uuid) -> Bytes {
  Bytes::makei(16, i => {
    let word = if i < 8 { self.hi } else { self.lo }
    (word >> (8 * (7 - i % 8))).to_byte()
  })
}

///|
let hex_digits : String = "0123456789abcdef"

///|
/// The 32 hexadecimal digits, lowercase, without hyphens.
pub fn Uuid::hex(self : Uuid) -> String {
  let out = StringBuilder()
  for word in [self.hi, self.lo] {
    for shift = 60; shift >= 0; shift = shift - 4 {
      out.write_char(
        hex_digits[((word >> shift) & 0xFUL).to_int()].unsafe_to_char(),
      )
    }
  }
  out.to_string()
}

///|
/// `urn:uuid:` and the canonical text, as RFC 9562 names an identifier.
pub fn Uuid::urn(self : Uuid) -> String {
  "urn:uuid:" + self.to_string()
}

///|
/// The canonical text: 8-4-4-4-12 lowercase hexadecimal digits.
pub impl Show for Uuid with fn output(self, logger) {
  let h = self.hex()
  logger.write_string(
    "\{h[0:8]}-\{h[8:12]}-\{h[12:16]}-\{h[16:20]}-\{h[20:32]}",
  )
}

///|
pub extend Uuid with Show::{output, to_string}

///|
fn hex_value(c : Char) -> Int32? {
  match c {
    '0'..<='9' => Some(c.to_int() - '0'.to_int())
    'a'..<='f' => Some(c.to_int() - 'a'.to_int() + 10)
    'A'..<='F' => Some(c.to_int() - 'A'.to_int() + 10)
    _ => None
  }
}

///|
/// Text in any of the spellings in use: canonical, with or without
/// hyphens, in braces, with the `urn:uuid:` prefix; any case.
pub fn parse(text : StringView) -> Uuid raise Refused {
  let chars = text.iter().collect()
  let mut start = 0
  let mut end = chars.length()
  for prefix in ["urn:", "uuid:"] {
    if end - start >= prefix.length() &&
      String::from_array(chars[start:start + prefix.length()]).to_lower() ==
      prefix {
      start += prefix.length()
    }
  }
  while start < end && chars[start] == '{' {
    start += 1
  }
  while end > start && chars[end - 1] == '}' {
    end -= 1
  }
  let mut hi = 0UL
  let mut lo = 0UL
  let mut digits = 0
  for i in start..= 32 {
      raise Malformed(at=i, reason="more than 32 hexadecimal digits")
    }
    if digits < 16 {
      hi = (hi << 4) | v.to_uint64()
    } else {
      lo = (lo << 4) | v.to_uint64()
    }
    digits += 1
  }
  if digits != 32 {
    raise Malformed(at=end, reason="\{digits} hexadecimal digits, not 32")
  }
  { hi, lo, }
}

///|
/// The variant its bits name.
pub fn Uuid::variant(self : Uuid) -> Variant {
  let bits = self.lo >> 61
  if (bits & 0b100UL) == 0UL {
    Ncs
  } else if (bits & 0b010UL) == 0UL {
    Rfc
  } else if (bits & 0b001UL) == 0UL {
    Microsoft
  } else {
    Future
  }
}

///|
/// The version, 1 to 8 for the ones RFC 9562 defines; `None` when the
/// variant is not RFC 9562's, where the version bits mean nothing.
pub fn Uuid::version(self : Uuid) -> Int32? {
  if self.variant() == Rfc {
    Some(((self.hi >> 12) & 0xFUL).to_int())
  } else {
    None
  }
}

///|
/// The timestamp the fields hold: 100-nanosecond intervals since
/// 1582-10-15 for versions 1 and 6, milliseconds since 1970 for version 7,
/// the version 1 reading of the fields otherwise.
pub fn Uuid::time(self : Uuid) -> Int64 {
  let time_mid = (self.hi >> 16) & 0xFFFFUL
  let t = match self.version() {
    Some(6) => (self.hi >> 32 << 28) | (time_mid << 12) | (self.hi & 0xFFFUL)
    Some(7) => self.hi >> 16
    _ => ((self.hi & 0xFFFUL) << 48) | (time_mid << 32) | (self.hi >> 32)
  }
  t.reinterpret_as_int64()
}

///|
/// The 14-bit clock sequence of versions 1 and 6.
pub fn Uuid::clock_seq(self : Uuid) -> Int32 {
  ((self.lo >> 48) & 0x3FFFUL).to_int()
}

///|
/// The 48-bit node of versions 1 and 6.
pub fn Uuid::node(self : Uuid) -> Int64 {
  (self.lo & 0xFFFFFFFFFFFFUL).reinterpret_as_int64()
}

///|
/// The nil identifier, all zeros.
pub let nil : Uuid = { hi: 0UL, lo: 0UL, }

///|
/// The max identifier, all ones.
pub let max : Uuid = { hi: 0xFFFFFFFFFFFFFFFFUL, lo: 0xFFFFFFFFFFFFFFFFUL, }

///|
/// The namespaces RFC 9562 assigns for names that are domain names, URLs,
/// ISO OIDs and X.500 distinguished names.
pub let namespace_dns : Uuid = {
  hi: 0x6ba7b8109dad11d1UL,
  lo: 0x80b400c04fd430c8UL,
}

///|
pub let namespace_url : Uuid = {
  hi: 0x6ba7b8119dad11d1UL,
  lo: 0x80b400c04fd430c8UL,
}

///|
pub let namespace_oid : Uuid = {
  hi: 0x6ba7b8129dad11d1UL,
  lo: 0x80b400c04fd430c8UL,
}

///|
pub let namespace_x500 : Uuid = {
  hi: 0x6ba7b8149dad11d1UL,
  lo: 0x80b400c04fd430c8UL,
}

///|
/// The canonical text.
pub impl ToJson for Uuid with fn to_json(self) {
  Json::string(self.to_string())
}

///|
/// Any spelling `parse` reads.
pub impl @json.FromJson for Uuid with fn from_json(json, path) {
  guard json is String(text) else {
    raise @json.JsonDecodeError((path, "a UUID is written as text"))
  }
  parse(text) catch {
    e => raise @json.JsonDecodeError((path, e.to_string()))
  }
}

///|
pub extend Uuid with ToJson::{to_json}

///|
pub extend Uuid with @json.FromJson::{from_json}