///|
/// Parses every PEM encoded certificate in a bundle (a file that may hold a
/// chain), skipping blocks with other labels such as private keys.
pub fn parse_certificate_bundle(
  text : StringView,
) -> Array[Certificate] raise X509Error {
  let blocks = @pem.decode_pem(text) catch {
    err => raise InvalidPem(err.to_string())
  }
  let certificates : Array[Certificate] = []
  for block in blocks {
    if block.label == "CERTIFICATE" {
      certificates.push(parse_certificate(block.data[:]))
    }
  }
  certificates
}

///|
/// The subject key identifier extension, when present.
pub fn Certificate::subject_key_identifier(self : Certificate) -> Bytes? {
  let extension = match self.extension("2.5.29.14") {
    Some(extension) => extension
    None => return None
  }
  let decoded = extension.decode() catch { _ => return None }
  match decoded {
    SubjectKeyIdentifier(identifier) => Some(identifier)
    _ => None
  }
}

///|
/// The authority key identifier extension, when present.
pub fn Certificate::authority_key_identifier(
  self : Certificate,
) -> AuthorityKeyIdentifier? {
  let extension = match self.extension("2.5.29.35") {
    Some(extension) => extension
    None => return None
  }
  let decoded = extension.decode() catch { _ => return None }
  match decoded {
    AuthorityKeyIdentifier(identifier) => Some(identifier)
    _ => None
  }
}

///|
/// The certificate in `pool` that issued this one.
///
/// Candidates must carry the issuer's subject name. When both sides have key
/// identifier extensions, an authority key identifier that matches the
/// candidate's subject key identifier wins; otherwise the first name match is
/// used. No signature is checked.
pub fn Certificate::issuer_in(
  self : Certificate,
  pool : Array[Certificate],
) -> Certificate? {
  let authority = self.authority_key_identifier()
  let mut fallback : Certificate? = None
  for candidate in pool {
    if candidate.raw == self.raw {
      continue
    }
    if candidate.tbs.subject.to_string() != self.tbs.issuer.to_string() {
      continue
    }
    match authority {
      Some(identifier) =>
        match identifier.key_identifier {
          Some(key_identifier) =>
            match candidate.subject_key_identifier() {
              Some(subject_identifier) =>
                if key_identifier == subject_identifier {
                  return Some(candidate)
                }
              None => ()
            }
          None => ()
        }
      None => ()
    }
    if fallback is None {
      fallback = Some(candidate)
    }
  }
  fallback
}

///|
/// Walks the issuer links inside `pool` and returns the chain, leaf first.
///
/// The walk stops at a self-signed certificate, when no issuer is found, or
/// when the next issuer is already in the chain. This is a structural walk, so
/// no signature is verified.
pub fn build_chain(
  leaf : Certificate,
  pool : Array[Certificate],
) -> Array[Certificate] {
  let chain : Array[Certificate] = [leaf]
  let mut current = leaf
  while !current.is_self_signed() {
    let issuer = match current.issuer_in(pool) {
      Some(issuer) => issuer
      None => return chain
    }
    for existing in chain {
      if existing.raw == issuer.raw {
        return chain
      }
    }
    chain.push(issuer)
    current = issuer
  }
  chain
}

///|
/// Renders one line per certificate, from leaf to root.
pub fn describe_chain(chain : Array[Certificate]) -> String {
  let buffer = StringBuilder()
  for i, certificate in chain {
    buffer.write_string(i.to_string())
    buffer.write_string(": ")
    buffer.write_string(certificate.tbs.subject.to_string())
    buffer.write_string("\n")
    buffer.write_string("   issued by ")
    buffer.write_string(certificate.tbs.issuer.to_string())
    buffer.write_string(
      if certificate.is_self_signed() {
        " (self-signed)"
      } else {
        ""
      },
    )
    buffer.write_string("\n")
  }
  buffer.to_string()
}