///|
/// A revoked certificate entry (RFC 5280, 5.3).
pub struct RevokedCertificate {
  serial_number : @bigint.BigInt
  revocation_date : @asn1.Asn1Time
  extensions : Array[Extension]
}

///|
/// The signed body of a certificate revocation list (RFC 5280, 5.1.2).
pub struct TbsCertList {
  version : Int
  signature_algorithm : AlgorithmIdentifier
  issuer : Name
  this_update : @asn1.Asn1Time
  next_update : @asn1.Asn1Time?
  revoked_certificates : Array[RevokedCertificate]
  extensions : Array[Extension]
}

///|
/// A certificate revocation list (RFC 5280, 5.1).
pub struct CertificateList {
  /// The original DER encoding, so a CRL can be round tripped.
  raw : Bytes
  tbs : TbsCertList
  signature_algorithm : AlgorithmIdentifier
  signature : Bytes
}

///|
/// The name of a revocation reason code (RFC 5280, 5.3.1).
pub fn revocation_reason_name(code : Int) -> String {
  match code {
    0 => "unspecified"
    1 => "keyCompromise"
    2 => "cACompromise"
    3 => "affiliationChanged"
    4 => "superseded"
    5 => "cessationOfOperation"
    6 => "certificateHold"
    8 => "removeFromCRL"
    9 => "privilegeWithdrawn"
    10 => "aACompromise"
    _ => "unknown"
  }
}

///|
/// The revocation reason from the `cRLReason` entry extension, when present.
pub fn RevokedCertificate::reason(self : RevokedCertificate) -> String? {
  for extension in self.extensions {
    if extension.oid.to_string() == "2.5.29.21" {
      let code = decode_extension_integer(extension) catch { _ => return None }
      return Some(revocation_reason_name(code))
    }
  }
  None
}

///|
/// Decodes an extension whose value is a bare INTEGER or ENUMERATED.
fn decode_extension_integer(extension : Extension) -> Int raise X509Error {
  let value = extension.decode_value()
  let content = value.content() catch {
    err => raise InvalidDer(err.to_string())
  }
  (@asn1.decode_integer(content) catch {
    err => raise InvalidDer(err.to_string())
  }).to_int()
}

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

///|
/// The CRL number from the `cRLNumber` extension, when present.
pub fn CertificateList::crl_number(self : CertificateList) -> @bigint.BigInt? {
  let extension = match self.extension("2.5.29.20") {
    Some(extension) => extension
    None => return None
  }
  let value = extension.decode_value() catch { _ => return None }
  let content = value.content() catch { _ => return None }
  Some(@asn1.decode_integer(content)) catch {
    _ => None
  }
}

///|
/// The entry for a serial number, or `None` when the certificate is not
/// revoked.
pub fn CertificateList::is_revoked(
  self : CertificateList,
  serial_number : @bigint.BigInt,
) -> RevokedCertificate? {
  for entry in self.tbs.revoked_certificates {
    if entry.serial_number == serial_number {
      return Some(entry)
    }
  }
  None
}

///|
/// Renders a summary of the CRL.
pub fn CertificateList::to_string(self : CertificateList) -> String {
  let buffer = StringBuilder()
  buffer.write_string("Certificate revocation list\n")
  buffer.write_string("  Version: ")
  buffer.write_string(self.tbs.version.to_string())
  buffer.write_string("\n")
  buffer.write_string("  Signature algorithm: ")
  buffer.write_string(self.signature_algorithm.name())
  buffer.write_string(" (")
  buffer.write_string(self.signature_algorithm.algorithm.to_string())
  buffer.write_string(")\n")
  buffer.write_string("  Issuer: ")
  buffer.write_string(self.tbs.issuer.to_string())
  buffer.write_string("\n")
  buffer.write_string("  This update: ")
  buffer.write_string(self.tbs.this_update.to_string())
  buffer.write_string("\n")
  match self.tbs.next_update {
    Some(next) => {
      buffer.write_string("  Next update: ")
      buffer.write_string(next.to_string())
      buffer.write_string("\n")
    }
    None => ()
  }
  buffer.write_string("  Revoked certificates (")
  buffer.write_string(self.tbs.revoked_certificates.length().to_string())
  buffer.write_string(")\n")
  for entry in self.tbs.revoked_certificates {
    buffer.write_string("    serial ")
    buffer.write_string(entry.serial_number.to_string())
    buffer.write_string(" revoked ")
    buffer.write_string(entry.revocation_date.to_string())
    match entry.reason() {
      Some(reason) => {
        buffer.write_string(" reason=")
        buffer.write_string(reason)
      }
      None => ()
    }
    buffer.write_string("\n")
  }
  if self.tbs.extensions.length() > 0 {
    buffer.write_string("  CRL extensions (")
    buffer.write_string(self.tbs.extensions.length().to_string())
    buffer.write_string(")\n")
    for extension in self.tbs.extensions {
      buffer.write_string("    ")
      buffer.write_string(extension.name())
      buffer.write_string(" (")
      buffer.write_string(extension.oid.to_string())
      buffer.write_string(") critical=")
      buffer.write_string(if extension.critical { "true" } else { "false" })
      buffer.write_string("\n")
    }
  }
  match self.crl_number() {
    Some(number) => {
      buffer.write_string("  CRL number: ")
      buffer.write_string(number.to_string())
      buffer.write_string("\n")
    }
    None => ()
  }
  buffer.write_string("  Signature: ")
  buffer.write_string(self.signature.length().to_string())
  buffer.write_string(" bytes\n")
  buffer.to_string()
}

///|
/// Parses one revoked certificate entry.
fn parse_revoked_certificate(
  value : @asn1.Der,
) -> RevokedCertificate raise X509Error {
  let children = value.as_sequence() catch {
    err => raise InvalidDer(err.to_string())
  }
  if children.length() < 2 || children.length() > 3 {
    raise Malformed(
      "a revoked certificate entry must have two or three elements",
    )
  }
  let serial_number = children[0].as_integer() catch {
    err => raise InvalidDer(err.to_string())
  }
  let revocation_date = children[1].as_time() catch {
    err => raise InvalidDer(err.to_string())
  }
  let extensions = if children.length() == 3 {
    parse_extension_list(children[2])
  } else {
    []
  }
  { serial_number, revocation_date, extensions, }
}

///|
/// Parses the TBSCertList.
fn parse_tbs_cert_list(value : @asn1.Der) -> TbsCertList raise X509Error {
  let children = value.as_sequence() catch {
    err => raise InvalidDer(err.to_string())
  }
  let mut index = 0
  let mut version = 1
  let first_tag = children[0].tag()
  if first_tag.cls == @asn1.Universal && first_tag.number == @asn1.TAG_INTEGER {
    let encoded = children[0].as_integer() catch {
      err => raise InvalidDer(err.to_string())
    }
    version = encoded.to_int() + 1
    if version < 1 || version > 2 {
      raise Malformed("unsupported CRL version")
    }
    index = 1
  }
  if children.length() < index + 3 {
    raise Malformed("TBSCertList is missing required fields")
  }
  let signature_algorithm = parse_algorithm_identifier(children[index])
  index = index + 1
  let issuer = parse_name(children[index])
  index = index + 1
  let this_update = children[index].as_time() catch {
    err => raise InvalidDer(err.to_string())
  }
  index = index + 1
  let mut next_update : @asn1.Asn1Time? = None
  if index < children.length() {
    let tag = children[index].tag()
    if tag.cls == @asn1.Universal &&
      (
        tag.number == @asn1.TAG_UTC_TIME ||
        tag.number == @asn1.TAG_GENERALIZED_TIME
      ) {
      next_update = Some(
        children[index].as_time() catch {
          err => raise InvalidDer(err.to_string())
        },
      )
      index = index + 1
    }
  }
  let revoked_certificates : Array[RevokedCertificate] = []
  if index < children.length() {
    let tag = children[index].tag()
    if tag.cls == @asn1.Universal && tag.number == @asn1.TAG_SEQUENCE {
      let entries = children[index].as_sequence() catch {
        err => raise InvalidDer(err.to_string())
      }
      for entry in entries {
        revoked_certificates.push(parse_revoked_certificate(entry))
      }
      index = index + 1
    }
  }
  let mut extensions : Array[Extension] = []
  if index < children.length() {
    let tag = children[index].tag()
    if tag.cls == @asn1.ContextSpecific && tag.number == 0 {
      let list = children[index].as_explicit(0) catch {
        err => raise InvalidDer(err.to_string())
      }
      extensions = parse_extension_list(list)
      index = index + 1
    }
  }
  if index < children.length() {
    raise Malformed("TBSCertList has unexpected trailing elements")
  }
  {
    version,
    signature_algorithm,
    issuer,
    this_update,
    next_update,
    revoked_certificates,
    extensions,
  }
}

///|
/// Parses a certificate revocation list from its DER encoding.
pub fn parse_certificate_list(
  bytes : BytesView,
) -> CertificateList raise X509Error {
  let raw = @buffer.from_bytes(bytes).to_bytes()
  let value = @asn1.DerReader::new(bytes).read_single() catch {
    err => raise InvalidDer(err.to_string())
  }
  let children = value.as_sequence() catch {
    err => raise InvalidDer(err.to_string())
  }
  if children.length() != 3 {
    raise Malformed("CertificateList must have three elements")
  }
  let tbs = parse_tbs_cert_list(children[0])
  let signature_algorithm = parse_algorithm_identifier(children[1])
  let (unused_bits, signature) = children[2].as_bit_string() catch {
    err => raise InvalidDer(err.to_string())
  }
  if unused_bits != 0 {
    raise Malformed("signature BIT STRING must have no unused bits")
  }
  { raw, tbs, signature_algorithm, signature, }
}

///|
/// Parses a certificate revocation list from PEM text.
pub fn parse_pem_certificate_list(
  text : StringView,
) -> CertificateList raise X509Error {
  let block = @pem.decode_pem_single(text) catch {
    err => raise InvalidPem(err.to_string())
  }
  parse_certificate_list(block.data[:])
}