///|
/// An attribute from a certification request (RFC 2986, 4.1).
///
/// The values are kept as decoded DER because their shape depends on the
/// attribute type.
pub struct Attribute {
  oid : @asn1.ObjectIdentifier
  values : Array[@asn1.Der]
}

///|
/// The subject, public key and attributes being requested (RFC 2986, 4.1).
pub struct CertificationRequestInfo {
  version : Int
  subject : Name
  subject_public_key_info : SubjectPublicKeyInfo
  attributes : Array[Attribute]
}

///|
/// A PKCS#10 certification request (RFC 2986, 4.2).
pub struct CertificationRequest {
  /// The original DER encoding, so a request can be round tripped.
  raw : Bytes
  info : CertificationRequestInfo
  signature_algorithm : AlgorithmIdentifier
  signature : Bytes
}

///|
/// Finds an attribute by its dotted object identifier.
pub fn CertificationRequest::attribute(
  self : CertificationRequest,
  oid : String,
) -> Attribute? {
  for attribute in self.info.attributes {
    if attribute.oid.to_string() == oid {
      return Some(attribute)
    }
  }
  None
}

///|
/// The extensions requested through the `extensionRequest` attribute, or an
/// empty array when that attribute is absent or malformed.
pub fn CertificationRequest::requested_extensions(
  self : CertificationRequest,
) -> Array[Extension] {
  let attribute = match self.attribute("1.2.840.113549.1.9.14") {
    Some(attribute) => attribute
    None => return []
  }
  let values = attribute.values
  if values.length() == 0 {
    return []
  }
  parse_extension_list(values[0]) catch {
    _ => []
  }
}

///|
/// The subject alternative names requested with the certificate.
pub fn CertificationRequest::subject_alt_names(
  self : CertificationRequest,
) -> Array[GeneralName] {
  for extension in self.requested_extensions() {
    if extension.oid.to_string() == "2.5.29.17" {
      let decoded = extension.decode() catch { _ => Other }
      match decoded {
        SubjectAltName(names) => return names
        _ => ()
      }
    }
  }
  []
}

///|
/// Renders a summary of the request.
pub fn CertificationRequest::to_string(self : CertificationRequest) -> String {
  let buffer = StringBuilder()
  buffer.write_string("Certification request\n")
  buffer.write_string("  Version: ")
  buffer.write_string(self.info.version.to_string())
  buffer.write_string("\n")
  buffer.write_string("  Subject: ")
  buffer.write_string(self.info.subject.to_string())
  buffer.write_string("\n")
  buffer.write_string("  Public key: ")
  buffer.write_string(key_description(self.info.subject_public_key_info))
  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("  Signature: ")
  buffer.write_string(self.signature.length().to_string())
  buffer.write_string(" bytes\n")
  buffer.write_string("  Attributes (")
  buffer.write_string(self.info.attributes.length().to_string())
  buffer.write_string(")\n")
  for attribute in self.info.attributes {
    buffer.write_string("    ")
    buffer.write_string(request_attribute_display_name(attribute.oid))
    buffer.write_string(" (")
    buffer.write_string(attribute.oid.to_string())
    buffer.write_string(")\n")
  }
  let extensions = self.requested_extensions()
  if extensions.length() > 0 {
    buffer.write_string("  Requested extensions (")
    buffer.write_string(extensions.length().to_string())
    buffer.write_string(")\n")
    for extension in 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")
    }
  }
  let alternative_names = self.subject_alt_names()
  if alternative_names.length() > 0 {
    buffer.write_string("  Subject alternative names (")
    buffer.write_string(alternative_names.length().to_string())
    buffer.write_string(")\n")
    for name in alternative_names {
      buffer.write_string("    ")
      buffer.write_string(name.kind())
      buffer.write_string(": ")
      buffer.write_string(name.to_string())
      buffer.write_string("\n")
    }
  }
  buffer.to_string()
}

///|
/// Parses one `Attribute` of a certification request.
fn parse_request_attribute(value : @asn1.Der) -> Attribute raise X509Error {
  let children = value.as_sequence() catch {
    err => raise InvalidDer(err.to_string())
  }
  if children.length() != 2 {
    raise Malformed("Attribute must have exactly two elements")
  }
  let oid = children[0].as_object_identifier() catch {
    err => raise InvalidDer(err.to_string())
  }
  let values = children[1].as_sequence() catch {
    err => raise InvalidDer(err.to_string())
  }
  { oid, values, }
}

///|
/// Parses the CertificationRequestInfo.
fn parse_certification_request_info(
  value : @asn1.Der,
) -> CertificationRequestInfo raise X509Error {
  let children = value.as_sequence() catch {
    err => raise InvalidDer(err.to_string())
  }
  if children.length() != 4 {
    raise Malformed("CertificationRequestInfo must have four elements")
  }
  let version = children[0].as_integer() catch {
    err => raise InvalidDer(err.to_string())
  }
  if version.to_int() != 0 {
    raise Malformed("only PKCS#10 version 1 requests are supported")
  }
  let subject = parse_name(children[1])
  let subject_public_key_info = parse_subject_public_key_info(children[2])
  let attributes : Array[Attribute] = []
  match children[3] {
    @asn1.Constructed(tag, items) => {
      if tag.cls != @asn1.ContextSpecific || tag.number != 0 {
        raise Malformed("the attribute list must use the [0] tag")
      }
      for item in items {
        attributes.push(parse_request_attribute(item))
      }
    }
    @asn1.Primitive(_, _) =>
      raise Malformed("the attribute list must be a constructed value")
  }
  { version: 0, subject, subject_public_key_info, attributes, }
}

///|
/// Parses a certification request from its DER encoding.
pub fn parse_certification_request(
  bytes : BytesView,
) -> CertificationRequest 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("CertificationRequest must have three elements")
  }
  let info = parse_certification_request_info(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, info, signature_algorithm, signature, }
}

///|
/// Parses a certification request from PEM text.
pub fn parse_pem_certification_request(
  text : StringView,
) -> CertificationRequest raise X509Error {
  let block = @pem.decode_pem_single(text) catch {
    err => raise InvalidPem(err.to_string())
  }
  parse_certification_request(block.data[:])
}