///|
/// Reads one byte from `view` at `at`, raising `UnexpectedEof` past the end.
fn read_byte(view : BytesView, at : Int) -> Int raise DerError {
  match view.get(at) {
    Some(byte) => byte.to_int()
    None => raise UnexpectedEof
  }
}

///|
/// Decodes the identifier octets starting at `at`, returning the tag and the
/// index immediately after it.
///
/// Handles both the low-tag-number form and the multi-octet high-tag-number
/// form, rejecting a leading `0x80` group as required by DER (X.690, 8.1.2.4).
fn parse_tag(view : BytesView, at : Int) -> (Tag, Int) raise DerError {
  let first = read_byte(view, at)
  let cls = match first >> 6 {
    0 => Universal
    1 => Application
    2 => ContextSpecific
    _ => Private
  }
  let constructed = (first & 0x20) != 0
  let number = first & 0x1F
  if number != 0x1F {
    return (Tag::new(cls, number, constructed~), at + 1)
  }
  let mut index = at + 1
  let mut value = 0
  let mut first_group = true
  while true {
    let byte = read_byte(view, index)
    if first_group && (byte & 0x7F) == 0 {
      raise InvalidTag(byte)
    }
    // Guard before shifting so the 32-bit accumulator cannot overflow.
    if value > 0x00FF_FFFF {
      raise InvalidTag(byte)
    }
    value = (value << 7) | (byte & 0x7F)
    index = index + 1
    first_group = false
    if (byte & 0x80) == 0 {
      break
    }
  }
  (Tag::new(cls, value, constructed~), index)
}

///|
/// Decodes the length octets starting at `at`, returning the content length and
/// the index immediately after it.
///
/// DER requires the definite form with the minimal number of octets; the
/// indefinite form (`0x80`) and non-minimal encodings are rejected.
fn parse_length(view : BytesView, at : Int) -> (Int, Int) raise DerError {
  let first = read_byte(view, at)
  if first < 0x80 {
    return (first, at + 1)
  }
  if first == 0x80 {
    raise IndefiniteLength
  }
  let count = first & 0x7F
  if count > 4 {
    raise LengthTooLong(count)
  }
  let mut value = 0L
  for i in 0.. 0x7FFF_FFFFL {
    raise LengthTooLong(count)
  }
  let length = value.to_int()
  let leading = read_byte(view, at + 1)
  if count > 1 && leading == 0 {
    raise NonMinimalLength(length)
  }
  if count == 1 && length < 0x80 {
    raise NonMinimalLength(length)
  }
  (length, at + 1 + count)
}

///|
/// A cursor that decodes tag-length-value elements from a `BytesView`.
///
/// The reader never copies: content views point into the original buffer.
pub struct DerReader {
  input : BytesView
  mut pos : Int
}

///|
/// Creates a reader over `input`.
pub fn DerReader::new(input : BytesView) -> DerReader {
  { input, pos: 0, }
}

///|
/// Creates a reader over a byte buffer.
pub fn DerReader::from_bytes(input : Bytes) -> DerReader {
  DerReader::new(input[:])
}

///|
/// Absolute offset of the cursor inside the underlying byte buffer.
pub fn DerReader::offset(self : DerReader) -> Int {
  self.input.start_offset() + self.pos
}

///|
/// Number of bytes left to read.
pub fn DerReader::remaining(self : DerReader) -> Int {
  self.input.length() - self.pos
}

///|
/// Whether the cursor has reached the end of the input.
pub fn DerReader::is_empty(self : DerReader) -> Bool {
  self.remaining() == 0
}

///|
/// Returns the tag of the next element without consuming it.
pub fn DerReader::peek_tag(self : DerReader) -> Tag raise DerError {
  parse_tag(self.input, self.pos).0
}

///|
/// Reads one element and returns its tag together with a view of its content
/// octets. The cursor advances past the element.
pub fn DerReader::read_tlv(self : DerReader) -> (Tag, BytesView) raise DerError {
  let (tag, after_tag) = parse_tag(self.input, self.pos)
  let (length, after_length) = parse_length(self.input, after_tag)
  if length > self.input.length() - after_length {
    raise Truncated(length)
  }
  let end = after_length + length
  let content = self.input[after_length:end]
  self.pos = end
  (tag, content)
}

///|
/// Reads one element, asserting that it is a primitive universal value with the
/// given tag number, and returns its content octets.
pub fn DerReader::read_expected(
  self : DerReader,
  number : Int,
) -> BytesView raise DerError {
  let (tag, content) = self.read_tlv()
  if tag.cls != Universal || tag.number != number {
    raise ExpectedTag(Tag::new(Universal, number).to_string(), tag.to_string())
  }
  content
}

///|
/// Decodes one element, recursing into constructed values.
pub fn DerReader::read_element(self : DerReader) -> Der raise DerError {
  let (tag, content) = self.read_tlv()
  if tag.constructed {
    Constructed(tag, DerReader::new(content).read_all())
  } else {
    Primitive(tag, content)
  }
}

///|
/// Decodes every remaining element in the input.
pub fn DerReader::read_all(self : DerReader) -> Array[Der] raise DerError {
  let elements : Array[Der] = []
  while self.remaining() > 0 {
    elements.push(self.read_element())
  }
  elements
}

///|
/// Decodes exactly one element, raising `TrailingData` if anything follows it.
pub fn DerReader::read_single(self : DerReader) -> Der raise DerError {
  let value = self.read_element()
  if self.remaining() > 0 {
    raise TrailingData(self.remaining())
  }
  value
}