///|
/// A decoded OBJECT IDENTIFIER, stored as its arc values.
///
/// The first arc is always 0, 1, or 2 and the second arc is at most 39 when the
/// first arc is 0 or 1 (X.690, 8.19).
pub struct ObjectIdentifier {
  arcs : Array[Int64]
}

///|
/// Creates an object identifier from its arcs, validating the two leading arcs.
pub fn ObjectIdentifier::from_arcs(
  arcs : Array[Int64],
) -> ObjectIdentifier raise DerError {
  if arcs.length() < 2 {
    raise InvalidValue("OBJECT IDENTIFIER needs at least two arcs")
  }
  let first = arcs[0]
  let second = arcs[1]
  if first < 0L || first > 2L {
    raise InvalidValue("first OBJECT IDENTIFIER arc must be 0, 1, or 2")
  }
  if first < 2L && (second < 0L || second > 39L) {
    raise InvalidValue(
      "second arc must be at most 39 when the first arc is 0 or 1",
    )
  }
  { arcs, }
}

///|
/// The arcs of the identifier.
pub fn ObjectIdentifier::arcs(self : ObjectIdentifier) -> Array[Int64] {
  self.arcs
}

///|
/// Renders the identifier in dotted decimal notation, for example
/// `1.2.840.113549.1.1.1`.
pub fn ObjectIdentifier::to_string(self : ObjectIdentifier) -> String {
  let builder = StringBuilder()
  for i, arc in self.arcs {
    if i > 0 {
      builder.write_string(".")
    }
    builder.write_string(arc.to_string())
  }
  builder.to_string()
}

///|
/// Parses a dotted-decimal object identifier such as `2.5.29.17`.
pub fn ObjectIdentifier::from_string(
  text : StringView,
) -> ObjectIdentifier raise DerError {
  let arcs : Array[Int64] = []
  for part in text.split(".") {
    let arc_text = part.trim()
    if arc_text.is_empty() {
      raise InvalidValue("empty arc in object identifier")
    }
    let mut value = 0L
    for i in 0.. 0x39 {
        raise InvalidValue("object identifier arcs must be decimal digits")
      }
      if value > 0x0FFF_FFFF_FFFF_FFFFL {
        raise InvalidValue("object identifier arc is too large")
      }
      value = value * 10L + (code - 0x30).to_int64()
    }
    arcs.push(value)
  }
  ObjectIdentifier::from_arcs(arcs)
}

///|
/// Decodes the content octets of an OBJECT IDENTIFIER.
pub fn decode_object_identifier(
  content : BytesView,
) -> ObjectIdentifier raise DerError {
  if content.length() == 0 {
    raise InvalidValue("OBJECT IDENTIFIER has no content octets")
  }
  let (combined, next) = read_subidentifier(content, 0)
  let first = if combined < 40L { 0L } else if combined < 80L { 1L } else { 2L }
  let arcs : Array[Int64] = [first, combined - first * 40L]
  let mut index = next
  while index < content.length() {
    let (arc, after) = read_subidentifier(content, index)
    arcs.push(arc)
    index = after
  }
  { arcs, }
}

///|
/// Decodes one base-128 subidentifier starting at `at`, rejecting the
/// non-minimal leading `0x80` group that DER forbids.
fn read_subidentifier(
  content : BytesView,
  at : Int,
) -> (Int64, Int) raise DerError {
  let mut index = at
  let mut value = 0L
  let mut first = true
  while true {
    let byte = read_byte(content, index)
    if first && byte == 0x80 {
      raise InvalidValue("OBJECT IDENTIFIER subidentifier is not minimal")
    }
    if value > 0x00FF_FFFF_FFFF_FFFFL {
      raise InvalidValue("OBJECT IDENTIFIER subidentifier is too large")
    }
    value = (value << 7) | (byte & 0x7F).to_int64()
    index = index + 1
    first = false
    if (byte & 0x80) == 0 {
      break
    }
  }
  (value, index)
}