///|
/// A general name (RFC 5280, 4.2.1.6).
///
/// The address forms that this library does not model (`otherName`,
/// `x400Address` and `ediPartyName`) keep their raw DER content.
pub(all) enum GeneralName {
  OtherName(Bytes)
  Rfc822Name(String)
  DnsName(String)
  X400Address(Bytes)
  DirectoryName(Name)
  EdiPartyName(Bytes)
  UniformResourceIdentifier(String)
  IpAddress(Bytes)
  RegisteredId(@asn1.ObjectIdentifier)
}

///|
/// A short kind label for a general name, used in summaries.
pub fn GeneralName::kind(self : GeneralName) -> String {
  match self {
    OtherName(_) => "otherName"
    Rfc822Name(_) => "email"
    DnsName(_) => "DNS"
    X400Address(_) => "x400"
    DirectoryName(_) => "dirname"
    EdiPartyName(_) => "ediParty"
    UniformResourceIdentifier(_) => "URI"
    IpAddress(_) => "IP"
    RegisteredId(_) => "RID"
  }
}

///|
/// Renders a general name in the form used by certificate display tools.
pub fn GeneralName::to_string(self : GeneralName) -> String {
  match self {
    OtherName(bytes) => "#otherName: " + @hex.encode(bytes)
    Rfc822Name(name) => name
    DnsName(name) => name
    X400Address(bytes) => "#x400Address: " + @hex.encode(bytes)
    DirectoryName(name) => name.to_string()
    EdiPartyName(bytes) => "#ediPartyName: " + @hex.encode(bytes)
    UniformResourceIdentifier(uri) => uri
    IpAddress(bytes) => format_ip(bytes)
    RegisteredId(oid) => oid.to_string()
  }
}

///|
/// Formats a 4 or 16 octet IP address.
fn format_ip(bytes : Bytes) -> String {
  if bytes.length() == 4 {
    bytes.unsafe_get(0).to_int().to_string() +
    "." +
    bytes.unsafe_get(1).to_int().to_string() +
    "." +
    bytes.unsafe_get(2).to_int().to_string() +
    "." +
    bytes.unsafe_get(3).to_int().to_string()
  } else {
    @hex.encode(bytes)
  }
}

///|
/// The named bits of the keyUsage extension (RFC 5280, 4.2.1.3).
pub(all) enum KeyUsageBit {
  DigitalSignature
  NonRepudiation
  KeyEncipherment
  DataEncipherment
  KeyAgreement
  KeyCertSign
  CrlSign
  EncipherOnly
  DecipherOnly
}

///|
/// The bit index of a key usage, matching the order in RFC 5280.
fn KeyUsageBit::index(self : KeyUsageBit) -> Int {
  match self {
    DigitalSignature => 0
    NonRepudiation => 1
    KeyEncipherment => 2
    DataEncipherment => 3
    KeyAgreement => 4
    KeyCertSign => 5
    CrlSign => 6
    EncipherOnly => 7
    DecipherOnly => 8
  }
}

///|
/// The display name of a key usage bit.
pub fn KeyUsageBit::name(self : KeyUsageBit) -> String {
  match self {
    DigitalSignature => "digitalSignature"
    NonRepudiation => "nonRepudiation"
    KeyEncipherment => "keyEncipherment"
    DataEncipherment => "dataEncipherment"
    KeyAgreement => "keyAgreement"
    KeyCertSign => "keyCertSign"
    CrlSign => "cRLSign"
    EncipherOnly => "encipherOnly"
    DecipherOnly => "decipherOnly"
  }
}

///|
/// The keyUsage extension (RFC 5280, 4.2.1.3).
pub struct KeyUsage {
  value : Bytes
}

///|
/// Whether a named bit is set.
pub fn KeyUsage::has(self : KeyUsage, bit : KeyUsageBit) -> Bool {
  let index = bit.index()
  let octet = index / 8
  if octet >= self.value.length() {
    return false
  }
  ((self.value.unsafe_get(octet).to_int() >> (7 - index % 8)) & 1) != 0
}

///|
/// Renders the set bits as a comma separated list.
pub fn KeyUsage::to_string(self : KeyUsage) -> String {
  let names : Array[String] = []
  for
    bit in [
      DigitalSignature,
      NonRepudiation,
      KeyEncipherment,
      DataEncipherment,
      KeyAgreement,
      KeyCertSign,
      CrlSign,
      EncipherOnly,
      DecipherOnly,
    ] {
    if self.has(bit) {
      names.push(bit.name())
    }
  }
  names.join(", ")
}

///|
/// The basicConstraints extension (RFC 5280, 4.2.1.9).
pub struct BasicConstraints {
  ca : Bool
  path_len : Int?
}

///|
/// The authorityKeyIdentifier extension (RFC 5280, 4.2.1.1).
pub struct AuthorityKeyIdentifier {
  key_identifier : Bytes?
  authority_cert_issuer : Array[GeneralName]?
  authority_cert_serial : @bigint.BigInt?
}

///|
/// A decoded extension value, for the extensions that this library understands.
pub(all) enum ExtensionValue {
  SubjectAltName(Array[GeneralName])
  IssuerAltName(Array[GeneralName])
  KeyUsage(KeyUsage)
  BasicConstraints(BasicConstraints)
  ExtendedKeyUsage(Array[@asn1.ObjectIdentifier])
  SubjectKeyIdentifier(Bytes)
  AuthorityKeyIdentifier(AuthorityKeyIdentifier)
  /// An extension this library does not decode; the raw value is on the
  /// `Extension`.
  Other
}

///|
/// Parses a general name.
fn parse_general_name(value : @asn1.Der) -> GeneralName raise X509Error {
  let tag = value.tag()
  if tag.cls != @asn1.ContextSpecific {
    raise Malformed("GeneralName must use a context specific tag")
  }
  match tag.number {
    0 => GeneralName::OtherName(content_of(value))
    1 => GeneralName::Rfc822Name(ascii_of(value))
    2 => GeneralName::DnsName(ascii_of(value))
    3 => GeneralName::X400Address(content_of(value))
    4 => GeneralName::DirectoryName(parse_implicit_name(value))
    5 => GeneralName::EdiPartyName(content_of(value))
    6 => GeneralName::UniformResourceIdentifier(ascii_of(value))
    7 => GeneralName::IpAddress(content_of(value))
    8 =>
      GeneralName::RegisteredId(
        @asn1.decode_object_identifier(content_of(value)) catch {
          err => raise InvalidDer(err.to_string())
        },
      )
    _ => raise Malformed("unknown GeneralName tag " + tag.number.to_string())
  }
}

///|
/// The content octets of a primitive value, copied into an owned buffer.
fn content_of(value : @asn1.Der) -> Bytes {
  match value {
    @asn1.Primitive(_, content) => @buffer.from_bytes(content).to_bytes()
    @asn1.Constructed(_, children) => {
      let writer = @asn1.DerWriter::new()
      for child in children {
        let encoded = child.encode() catch { _ => b"" }
        writer.add(encoded[:])
      }
      writer.to_content()
    }
  }
}

///|
/// Decodes the content octets of an implicitly tagged IA5String.
fn ascii_of(value : @asn1.Der) -> String raise X509Error {
  @asn1.decode_ia5_string(content_of(value)[:]) catch {
    err => raise InvalidDer(err.to_string())
  }
}

///|
/// Decodes `[4] directoryName`, which is an implicitly tagged RDNSequence.
fn parse_implicit_name(value : @asn1.Der) -> Name raise X509Error {
  match value {
    @asn1.Constructed(_, children) => {
      let rdns : Array[RelativeDistinguishedName] = []
      for child in children {
        let atv_elements = child.as_sequence() catch {
          err => raise InvalidDer(err.to_string())
        }
        let attributes : Array[AttributeTypeAndValue] = []
        for atv in atv_elements {
          let parts = atv.as_sequence() catch {
            err => raise InvalidDer(err.to_string())
          }
          if parts.length() != 2 {
            raise Malformed("AttributeTypeAndValue must have two elements")
          }
          attributes.push({
            oid: parts[0].as_object_identifier() catch {
              err => raise InvalidDer(err.to_string())
            },
            value: attribute_value_text(parts[1]),
          })
        }
        rdns.push({ attributes, })
      }
      { rdns, }
    }
    @asn1.Primitive(_, _) =>
      raise Malformed("directoryName must be a constructed value")
  }
}

///|
/// Parses a GeneralNames sequence (the value of subjectAltName and friends).
fn parse_general_names(value : @asn1.Der) -> Array[GeneralName] raise X509Error {
  let children = value.as_sequence() catch {
    err => raise InvalidDer(err.to_string())
  }
  let names : Array[GeneralName] = []
  for child in children {
    names.push(parse_general_name(child))
  }
  names
}

///|
/// Parses the keyUsage extension.
fn parse_key_usage(value : @asn1.Der) -> KeyUsage raise X509Error {
  let (_, bits) = value.as_bit_string() catch {
    err => raise InvalidDer(err.to_string())
  }
  { value: bits, }
}

///|
/// Parses the basicConstraints extension.
fn parse_basic_constraints(
  value : @asn1.Der,
) -> BasicConstraints raise X509Error {
  let children = value.as_sequence() catch {
    err => raise InvalidDer(err.to_string())
  }
  let mut ca = false
  let mut path_len : Int? = None
  let mut index = 0
  if index < children.length() &&
    children[index].tag().number == @asn1.TAG_BOOLEAN {
    ca = children[index].as_boolean() catch {
      err => raise InvalidDer(err.to_string())
    }
    index = index + 1
  }
  if index < children.length() {
    let encoded = children[index].as_integer() catch {
      err => raise InvalidDer(err.to_string())
    }
    path_len = Some(encoded.to_int())
    index = index + 1
  }
  if index < children.length() {
    raise Malformed("basicConstraints has unexpected trailing elements")
  }
  { ca, path_len, }
}

///|
/// Parses the extKeyUsage extension.
fn parse_extended_key_usage(
  value : @asn1.Der,
) -> Array[@asn1.ObjectIdentifier] raise X509Error {
  let children = value.as_sequence() catch {
    err => raise InvalidDer(err.to_string())
  }
  let usages : Array[@asn1.ObjectIdentifier] = []
  for child in children {
    usages.push(
      child.as_object_identifier() catch {
        err => raise InvalidDer(err.to_string())
      },
    )
  }
  usages
}

///|
/// Parses the authorityKeyIdentifier extension.
fn parse_authority_key_identifier(
  value : @asn1.Der,
) -> AuthorityKeyIdentifier raise X509Error {
  let children = value.as_sequence() catch {
    err => raise InvalidDer(err.to_string())
  }
  let mut key_identifier : Bytes? = None
  let mut authority_cert_issuer : Array[GeneralName]? = None
  let mut authority_cert_serial : @bigint.BigInt? = None
  for child in children {
    let tag = child.tag()
    if tag.cls != @asn1.ContextSpecific {
      raise Malformed("authorityKeyIdentifier fields must be context specific")
    }
    match tag.number {
      0 => key_identifier = Some(content_of(child))
      1 => authority_cert_issuer = Some(parse_general_names(child))
      2 =>
        authority_cert_serial = Some(
          @asn1.decode_integer(content_of(child)[:]) catch {
            err => raise InvalidDer(err.to_string())
          },
        )
      _ =>
        raise Malformed(
          "unknown authorityKeyIdentifier field " + tag.number.to_string(),
        )
    }
  }
  { key_identifier, authority_cert_issuer, authority_cert_serial, }
}

///|
/// Decodes the extension value for the extensions this library understands.
pub fn Extension::decode(self : Extension) -> ExtensionValue raise X509Error {
  let dotted = self.oid.to_string()
  match dotted {
    "2.5.29.17" => SubjectAltName(parse_general_names(self.decode_value()))
    "2.5.29.18" => IssuerAltName(parse_general_names(self.decode_value()))
    "2.5.29.15" => KeyUsage(parse_key_usage(self.decode_value()))
    "2.5.29.19" =>
      BasicConstraints(parse_basic_constraints(self.decode_value()))
    "2.5.29.37" =>
      ExtendedKeyUsage(parse_extended_key_usage(self.decode_value()))
    "2.5.29.14" =>
      SubjectKeyIdentifier(
        self.decode_value().as_octet_string() catch {
          err => raise InvalidDer(err.to_string())
        },
      )
    "2.5.29.35" =>
      AuthorityKeyIdentifier(
        parse_authority_key_identifier(self.decode_value()),
      )
    _ => Other
  }
}

///|
/// Finds an extension by its dotted object identifier.
pub fn Certificate::extension(self : Certificate, oid : String) -> Extension? {
  for extension in self.tbs.extensions {
    if extension.oid.to_string() == oid {
      return Some(extension)
    }
  }
  None
}

///|
/// The subject alternative names, or an empty array when the extension is
/// absent.
pub fn Certificate::subject_alt_names(self : Certificate) -> Array[GeneralName] {
  let extension = match self.extension("2.5.29.17") {
    Some(extension) => extension
    None => return []
  }
  let decoded = extension.decode() catch { _ => Other }
  match decoded {
    SubjectAltName(names) => names
    _ => []
  }
}

///|
/// The key usage, or `None` when the extension is absent.
pub fn Certificate::key_usage(self : Certificate) -> KeyUsage? {
  let extension = match self.extension("2.5.29.15") {
    Some(extension) => extension
    None => return None
  }
  let decoded = extension.decode() catch { _ => Other }
  match decoded {
    KeyUsage(usage) => Some(usage)
    _ => None
  }
}

///|
/// The basic constraints, or `None` when the extension is absent.
pub fn Certificate::basic_constraints(self : Certificate) -> BasicConstraints? {
  let extension = match self.extension("2.5.29.19") {
    Some(extension) => extension
    None => return None
  }
  let decoded = extension.decode() catch { _ => Other }
  match decoded {
    BasicConstraints(constraints) => Some(constraints)
    _ => None
  }
}

///|
/// Whether the certificate is a CA, according to basicConstraints.
pub fn Certificate::is_ca(self : Certificate) -> Bool {
  match self.basic_constraints() {
    Some(constraints) => constraints.ca
    None => false
  }
}

///|
/// The extended key usages, or `None` when the extension is absent.
pub fn Certificate::extended_key_usage(
  self : Certificate,
) -> Array[@asn1.ObjectIdentifier]? {
  let extension = match self.extension("2.5.29.37") {
    Some(extension) => extension
    None => return None
  }
  let decoded = extension.decode() catch { _ => Other }
  match decoded {
    ExtendedKeyUsage(usages) => Some(usages)
    _ => None
  }
}