///|
/// A decoded ASN.1 value.
pub(all) enum Der {
  /// A primitive value: its tag and a view of its content octets.
  Primitive(Tag, BytesView)
  /// A constructed value: its tag and its decoded children, in order.
  Constructed(Tag, Array[Der])
}

///|
/// The tag of this value.
pub fn Der::tag(self : Der) -> Tag {
  match self {
    Primitive(tag, _) | Constructed(tag, _) => tag
  }
}

///|
/// Whether this value is constructed.
pub fn Der::is_constructed(self : Der) -> Bool {
  self.tag().constructed
}

///|
/// Returns the raw content octets of a primitive value.
pub fn Der::content(self : Der) -> BytesView raise DerError {
  match self {
    Primitive(_, content) => content
    Constructed(tag, _) =>
      raise InvalidValue("expected a primitive value, got " + tag.to_string())
  }
}

///|
/// Returns the children of a constructed value.
pub fn Der::children(self : Der) -> Array[Der] raise DerError {
  match self {
    Constructed(_, children) => children
    Primitive(tag, _) =>
      raise InvalidValue("expected a constructed value, got " + tag.to_string())
  }
}

///|
/// Returns the `index`-th child of a constructed value.
pub fn Der::child(self : Der, index : Int) -> Der raise DerError {
  let children = self.children()
  match children.get(index) {
    Some(child) => child
    None =>
      raise InvalidValue("child index " + index.to_string() + " out of range")
  }
}

///|
/// Renders the value as an indented tree, handy for debugging and for the
/// command line tools built on top of this package.
pub fn Der::to_string(self : Der) -> String {
  let builder = StringBuilder()
  render_der(self, 0, builder)
  builder.to_string()
}

///|
fn render_der(value : Der, depth : Int, builder : StringBuilder) -> Unit {
  for _ in 0.. {
      builder.write_string(tag.to_string())
      builder.write_string(" ")
      builder.write_string(content.length().to_string())
      builder.write_string("B ")
      builder.write_string(hex_preview(content))
    }
    Constructed(tag, children) => {
      builder.write_string(tag.to_string())
      builder.write_string(" {")
      builder.write_string(children.length().to_string())
      builder.write_string("}")
      for child in children {
        builder.write_string("\n")
        render_der(child, depth + 1, builder)
      }
    }
  }
}

///|
/// Formats up to eight content octets as space separated hex for `to_string`.
fn hex_preview(content : BytesView) -> String {
  let limit = if content.length() < 8 { content.length() } else { 8 }
  let encoded = @hex.encode(content[:limit])
  let builder = StringBuilder()
  let mut index = 0
  while index < encoded.length() {
    if index > 0 {
      builder.write_string(" ")
    }
    builder.write_substring(encoded, index, 2)
    index = index + 2
  }
  if content.length() > limit {
    builder.write_string(" ..")
  }
  builder.to_string()
}