///|
/// Strict base64 encoder used by XML data nodes.
pub fn encode_base64(data : Bytes) -> String {
  let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  let out = StringBuilder()
  let mut i = 0
  while i < data.length() {
    let a = data[i].to_int()
    let b = if i + 1 < data.length() { data[i + 1].to_int() } else { 0 }
    let c = if i + 2 < data.length() { data[i + 2].to_int() } else { 0 }
    out.write_char(alphabet[a >> 2].unsafe_to_char())
    out.write_char(alphabet[((a & 3) << 4) | (b >> 4)].unsafe_to_char())
    if i + 1 < data.length() {
      out.write_char(alphabet[((b & 15) << 2) | (c >> 6)].unsafe_to_char())
    } else {
      out.write_char('=')
    }
    if i + 2 < data.length() {
      out.write_char(alphabet[c & 63].unsafe_to_char())
    } else {
      out.write_char('=')
    }
    i += 3
  }
  out.to_string()
}

///|
fn base64_digit(c : Int32) -> Int32 {
  if c >= 65 && c <= 90 {
    c - 65
  } else if c >= 97 && c <= 122 {
    c - 71
  } else if c >= 48 && c <= 57 {
    c + 4
  } else if c == 43 {
    62
  } else if c == 47 {
    63
  } else {
    -1
  }
}

///|
pub fn decode_base64(
  text : String,
  limits~ : Limits = Limits::default(),
) -> Bytes raise PlistError {
  limits.check()
  if text.length() > limits.max_bytes {
    raise Limit("base64 input budget")
  }
  let chars : Array[Int32] = []
  for c in text {
    let n = c.to_int()
    if n != 32 && n != 9 && n != 10 && n != 13 {
      chars.push(n)
    }
  }
  if chars.length() % 4 != 0 {
    raise Invalid("base64 length")
  }
  let out = @buffer.Buffer()
  let mut i = 0
  while i < chars.length() {
    let a = base64_digit(chars[i])
    let b = base64_digit(chars[i + 1])
    let c = base64_digit(chars[i + 2])
    let d = base64_digit(chars[i + 3])
    if a < 0 || b < 0 {
      raise Invalid("base64 alphabet")
    }
    let last = i + 4 == chars.length()
    if chars[i + 2] == 61 {
      if !last || chars[i + 3] != 61 || (b & 15) != 0 {
        raise Invalid("base64 padding")
      }
      out.write_byte(((a << 2) | (b >> 4)).to_byte())
    } else if chars[i + 3] == 61 {
      if !last || c < 0 || (c & 3) != 0 {
        raise Invalid("base64 padding")
      }
      out.write_byte(((a << 2) | (b >> 4)).to_byte())
      out.write_byte(((b << 4) | (c >> 2)).to_byte())
    } else {
      if c < 0 || d < 0 {
        raise Invalid("base64 alphabet")
      }
      out.write_byte(((a << 2) | (b >> 4)).to_byte())
      out.write_byte(((b << 4) | (c >> 2)).to_byte())
      out.write_byte(((c << 6) | d).to_byte())
    }
    i += 4
  }
  out.to_bytes()
}

///|
pub fn encode_hex(data : Bytes) -> String {
  let alphabet = "0123456789abcdef"
  let out = StringBuilder()
  for b in data {
    out.write_char(alphabet[b.to_int() >> 4].unsafe_to_char())
    out.write_char(alphabet[b.to_int() & 15].unsafe_to_char())
  }
  out.to_string()
}

///|
pub fn decode_hex(
  text : String,
  limits~ : Limits = Limits::default(),
) -> Bytes raise PlistError {
  limits.check()
  if text.length() > limits.max_bytes || text.length() % 2 != 0 {
    raise Limit("hex length or budget")
  }
  fn digit(c : Int32) -> Int32 raise PlistError {
    if c >= 48 && c <= 57 {
      c - 48
    } else if c >= 65 && c <= 70 {
      c - 55
    } else if c >= 97 && c <= 102 {
      c - 87
    } else {
      raise Invalid("hex digit")
    }
  }
  let out = @buffer.Buffer()
  let mut i = 0
  while i < text.length() {
    out.write_byte(
      ((digit(text[i].to_int()) << 4) | digit(text[i + 1].to_int())).to_byte(),
    )
    i += 2
  }
  out.to_bytes()
}