///|
/// ASN.1 tag class (X.690 8.1.2.2).
pub(all) enum TagClass {
  Universal
  Application
  ContextSpecific
  Private
} derive(Eq, @debug.Debug)

///|
pub fn TagClass::to_bits(self : TagClass) -> Int {
  match self {
    Universal => 0
    Application => 1
    ContextSpecific => 2
    Private => 3
  }
}

///|
pub fn TagClass::from_bits(bits : Int) -> TagClass {
  match bits {
    0 => Universal
    1 => Application
    2 => ContextSpecific
    _ => Private
  }
}

///|
/// An ASN.1 identifier octet(s) decoded into its components.
pub struct BerTag {
  class : TagClass
  constructed : Bool
  number : Int
} derive(Eq, @debug.Debug)

///|
pub fn BerTag::new(
  class : TagClass,
  constructed : Bool,
  number : Int,
) -> BerTag {
  { class, constructed, number }
}

///|
pub fn BerTag::is_constructed(self : BerTag) -> Bool {
  self.constructed
}

///|
/// First identifier octet. When `number >= 31` the low five bits are `0b11111`
/// and the full number follows in long form.
pub fn BerTag::first_octet(self : BerTag) -> Int {
  let class = self.class.to_bits() << 6
  let constructed = if self.constructed { 0x20 } else { 0 }
  if self.number < 31 {
    class | constructed | self.number
  } else {
    class | constructed | 0x1F
  }
}

///|
pub fn BerTag::to_string(self : BerTag) -> String {
  let class = match self.class {
    Universal => "universal"
    Application => "application"
    ContextSpecific => "context"
    Private => "private"
  }
  let kind = if self.constructed { "constructed" } else { "primitive" }
  "\{class} \{kind} tag \{self.number}"
}

///|
/// Short helper for constructing universal tags.
pub fn universal_tag(number : Int, constructed : Bool) -> BerTag {
  BerTag::new(TagClass::Universal, constructed, number)
}

///|
/// Short helper for constructing context-specific tags.
pub fn context_tag(number : Int, constructed : Bool) -> BerTag {
  BerTag::new(TagClass::ContextSpecific, constructed, number)
}

///|
/// Short helper for constructing application tags.
pub fn application_tag(number : Int, constructed : Bool) -> BerTag {
  BerTag::new(TagClass::Application, constructed, number)
}

// Universal tag numbers (X.690 8.1.2.4, 8.5-8.23).

///|
let tag_eoc : Int = 0

///|
let tag_boolean : Int = 1

///|
let tag_integer : Int = 2

///|
let tag_bit_string : Int = 3

///|
let tag_octet_string : Int = 4

///|
let tag_null : Int = 5

///|
let tag_oid : Int = 6

///|
let tag_enumerated : Int = 10

///|
let tag_utf8_string : Int = 12

///|
let tag_sequence : Int = 16

///|
let tag_set : Int = 17

///|
let tag_ia5_string : Int = 22