///|
pub fn decode_base64(tag : String, raw : String) -> Bytes raise ParseError {
  let values : Array[Int32] = []
  let mut seen_pad = false
  for c in raw {
    if c.is_whitespace() {
      continue
    }
    if c is '=' {
      seen_pad = true
      values.push(-1)
      continue
    }
    if seen_pad {
      raise ParseError::InvalidTagValue(tag~, value=raw)
    }
    match base64_value(c) {
      None => raise ParseError::InvalidTagValue(tag~, value=raw)
      Some(v) => values.push(v)
    }
  }
  let mut len = values.length()
  if len == 0 {
    return Bytes::from_array([])
  }
  if len % 4 != 0 {
    if seen_pad {
      raise ParseError::InvalidTagValue(tag~, value=raw)
    }
    let rem = len % 4
    if rem == 1 {
      raise ParseError::InvalidTagValue(tag~, value=raw)
    } else if rem == 2 {
      values.push(-1)
      values.push(-1)
    } else {
      values.push(-1)
    }
    len = values.length()
  }
  let out : Array[Byte] = []
  let mut i = 0
  while i < len {
    let v0 = values[i]
    let v1 = values[i + 1]
    let v2 = values[i + 2]
    let v3 = values[i + 3]
    if v0 < 0 || v1 < 0 {
      raise ParseError::InvalidTagValue(tag~, value=raw)
    }
    let b0 = (v0 << 2) | (v1 >> 4)
    out.push(b0.to_byte())
    if v2 < 0 {
      if v3 >= 0 {
        raise ParseError::InvalidTagValue(tag~, value=raw)
      }
      i = i + 4
      continue
    }
    let b1 = ((v1 & 0x0f) << 4) | (v2 >> 2)
    out.push(b1.to_byte())
    if v3 < 0 {
      i = i + 4
      continue
    }
    let b2 = ((v2 & 0x03) << 6) | v3
    out.push(b2.to_byte())
    i = i + 4
  }
  Bytes::from_array(out)
}

///|
pub fn encode_base64(bytes : Bytes) -> String {
  let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  let sb = StringBuilder::new()
  let len = bytes.length()
  let mut i = 0
  while i + 2 < len {
    let b0 = bytes[i].to_int()
    let b1 = bytes[i + 1].to_int()
    let b2 = bytes[i + 2].to_int()
    let x0 = (b0 & 0xfc) >> 2
    let x1 = ((b0 & 0x03) << 4) | ((b1 & 0xf0) >> 4)
    let x2 = ((b1 & 0x0f) << 2) | ((b2 & 0xc0) >> 6)
    let x3 = b2 & 0x3f
    sb.write_char(alphabet.get_char(x0).unwrap())
    sb.write_char(alphabet.get_char(x1).unwrap())
    sb.write_char(alphabet.get_char(x2).unwrap())
    sb.write_char(alphabet.get_char(x3).unwrap())
    i = i + 3
  }
  let rem = len - i
  if rem == 1 {
    let b0 = bytes[i].to_int()
    let x0 = (b0 & 0xfc) >> 2
    let x1 = (b0 & 0x03) << 4
    sb.write_char(alphabet.get_char(x0).unwrap())
    sb.write_char(alphabet.get_char(x1).unwrap())
    sb.write_char('=')
    sb.write_char('=')
  } else if rem == 2 {
    let b0 = bytes[i].to_int()
    let b1 = bytes[i + 1].to_int()
    let x0 = (b0 & 0xfc) >> 2
    let x1 = ((b0 & 0x03) << 4) | ((b1 & 0xf0) >> 4)
    let x2 = (b1 & 0x0f) << 2
    sb.write_char(alphabet.get_char(x0).unwrap())
    sb.write_char(alphabet.get_char(x1).unwrap())
    sb.write_char(alphabet.get_char(x2).unwrap())
    sb.write_char('=')
  }
  sb.to_string()
}

///|
fn base64_value(c : Char) -> Int32? {
  if c.is_ascii_uppercase() {
    Some(c.to_int() - 'A'.to_int())
  } else if c.is_ascii_lowercase() {
    Some(c.to_int() - 'a'.to_int() + 26)
  } else if c.is_ascii_digit() {
    Some(c.to_int() - '0'.to_int() + 52)
  } else if c is '+' {
    Some(62)
  } else if c is '/' {
    Some(63)
  } else {
    None
  }
}