///|
/// Decodes the content octets of a BOOLEAN.
///
/// DER requires `0xFF` for true and `0x00` for false; any other value is
/// rejected.
pub fn decode_boolean(content : BytesView) -> Bool raise DerError {
  if content.length() != 1 {
    raise InvalidValue("BOOLEAN must have exactly one content octet")
  }
  match content.unsafe_get(0).to_int() {
    0x00 => false
    0xFF => true
    _ => raise InvalidValue("BOOLEAN must be 0x00 or 0xFF in DER")
  }
}

///|
/// Decodes the content octets of a NULL, which must be empty.
pub fn decode_null(content : BytesView) -> Unit raise DerError {
  if content.length() != 0 {
    raise InvalidValue("NULL must have empty content")
  }
}

///|
/// Decodes the content octets of an INTEGER into an arbitrary-precision integer.
///
/// DER INTEGERs use two's complement, big-endian encoding with at least one
/// content octet.
pub fn decode_integer(content : BytesView) -> @bigint.BigInt raise DerError {
  if content.length() == 0 {
    raise InvalidValue("INTEGER must have at least one content octet")
  }
  if content.length() > 1 {
    let first = content.unsafe_get(0).to_int()
    let second = content.unsafe_get(1).to_int()
    if first == 0x00 && (second & 0x80) == 0 {
      raise InvalidValue("INTEGER is not minimally encoded")
    }
    if first == 0xFF && (second & 0x80) != 0 {
      raise InvalidValue("INTEGER is not minimally encoded")
    }
  }
  let raw = @bigint.BigInt::from_octets(content)
  if (content.unsafe_get(0).to_int() & 0x80) == 0 {
    return raw
  }
  let modulus = @bigint.BigInt::from_int(1) << (8 * content.length())
  raw - modulus
}

///|
/// Decodes the value bits of a BIT STRING.
///
/// The first content octet is the number of unused trailing bits of the last
/// byte (0-7); the remaining octets hold the value with those bits cleared, as
/// required by X.690, 11.2.
pub fn decode_bit_string(content : BytesView) -> (Int, Bytes) raise DerError {
  if content.length() == 0 {
    raise InvalidValue("BIT STRING must have at least one content octet")
  }
  let unused = content.unsafe_get(0).to_int()
  if unused > 7 {
    raise InvalidValue("BIT STRING unused bit count must be 0-7")
  }
  let value = @buffer.from_bytes(content[1:]).to_bytes()
  if value.length() == 0 && unused != 0 {
    raise InvalidValue("BIT STRING unused bit count must be 0 when empty")
  }
  if value.length() > 0 {
    let last = value.unsafe_get(value.length() - 1).to_int()
    if unused > 0 && (last & ((1 << unused) - 1)) != 0 {
      raise InvalidValue("BIT STRING has non-zero unused bits")
    }
  }
  (unused, value)
}

///|
/// Decodes a UTF8String.
pub fn decode_utf8_string(content : BytesView) -> String raise DerError {
  @utf8.decode(content) catch {
    @utf8.Malformed(_) => raise InvalidValue("invalid UTF-8 in UTF8String")
  }
}

///|
/// Decodes a PrintableString, whose alphabet is ASCII (X.680, 41).
pub fn decode_printable_string(content : BytesView) -> String raise DerError {
  decode_ascii(content, "PrintableString")
}

///|
/// Decodes an IA5String, whose alphabet is the 128 ASCII characters (X.680, 43).
pub fn decode_ia5_string(content : BytesView) -> String raise DerError {
  decode_ascii(content, "IA5String")
}

///|
/// Decodes a NumericString, whose alphabet is the digits and the space.
pub fn decode_numeric_string(content : BytesView) -> String raise DerError {
  for i in 0.. 0x39) {
      raise InvalidValue(
        "NumericString contains a character outside its alphabet",
      )
    }
  }
  decode_ascii(content, "NumericString")
}

///|
/// Decodes a VisibleString, whose alphabet is the printable ASCII range.
pub fn decode_visible_string(content : BytesView) -> String raise DerError {
  for i in 0.. 0x7E {
      raise InvalidValue(
        "VisibleString contains a character outside its alphabet",
      )
    }
  }
  decode_ascii(content, "VisibleString")
}

///|
/// Decodes a BMPString, which is UTF-16BE without a byte order mark.
///
/// BMPString is UCS-2, so characters outside the basic multilingual plane
/// (which UTF-16 encodes as surrogate pairs) are rejected.
pub fn decode_bmp_string(content : BytesView) -> String raise DerError {
  let decoded = @utf16.decode(content, endianness=@utf16.Big) catch {
    @utf16.Malformed(_) => raise InvalidValue("invalid UTF-16 in BMPString")
  }
  for ch in decoded {
    if Char::to_int(ch) > 0xFFFF {
      raise InvalidValue("BMPString contains a character outside the BMP")
    }
  }
  decoded
}

///|
/// Decodes a UniversalString, which is UTF-32BE.
pub fn decode_universal_string(content : BytesView) -> String raise DerError {
  if content.length() % 4 != 0 {
    raise InvalidValue("UniversalString length must be a multiple of four")
  }
  let builder = StringBuilder()
  let mut index = 0
  while index < content.length() {
    let code = (content.unsafe_get(index).to_int() << 24) |
      (content.unsafe_get(index + 1).to_int() << 16) |
      (content.unsafe_get(index + 2).to_int() << 8) |
      content.unsafe_get(index + 3).to_int()
    if code < 0 {
      raise InvalidValue("UniversalString code point is out of range")
    }
    match code.to_char() {
      Some(ch) => builder.write_char(ch)
      None =>
        raise InvalidValue("UniversalString code point is not a scalar value")
    }
    index = index + 4
  }
  builder.to_string()
}

///|
/// Decodes an ASCII-only string type, raising `InvalidValue` for any byte above
/// `0x7F`.
fn decode_ascii(content : BytesView, kind : String) -> String raise DerError {
  let buffer = @buffer.Buffer(size_hint=content.length())
  for i in 0.. 0x7F {
      raise InvalidValue(kind + " contains a non-ASCII byte")
    }
    buffer.write_byte(byte.to_byte())
  }
  @utf8.decode_lossy(buffer.to_bytes()[:])
}