// Base64URL encoding/decoding (RFC 4648)
// URL-safe alphabet: A-Za-z0-9-_ (no padding)

let base64url_chars : FixedArray[Int] = [
  65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,  // A-M
  78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,  // N-Z
  97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109,  // a-m
  110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122,  // n-z
  48, 49, 50, 51, 52, 53, 54, 55, 56, 57,  // 0-9
  45, 95  // - _
]

fn get_encode_char(index: Int) -> String {
  index.unsafe_to_char().to_string()
}

fn get_decode_value(ch: Int) -> Int {
  if ch >= 65 && ch <= 90 {
    // A-Z -> 0-25
    ch - 65
  } else if ch >= 97 && ch <= 122 {
    // a-z -> 26-51
    ch - 97 + 26
  } else if ch >= 48 && ch <= 57 {
    // 0-9 -> 52-61
    ch - 48 + 52
  } else if ch == 45 {
    // '-' -> 62
    62
  } else if ch == 95 {
    // '_' -> 63
    63
  } else {
    -1
  }
}

fn string_to_bytes_filtered(s: String) -> Array[Int] {
  let raw = @utf8.encode(s)
  let result : Array[Int] = []
  for i in 0.. String {
  let mut result = ""
  let len = data.length()
  let mut i = 0

  while i + 2 < len {
    let b0 = data[i].reinterpret_as_int()
    let b1 = data[i + 1].reinterpret_as_int()
    let b2 = data[i + 2].reinterpret_as_int()

    let c0 = (b0 >> 2) & 0x3F
    let c1 = ((b0 & 0x03) << 4) | ((b1 >> 4) & 0x0F)
    let c2 = ((b1 & 0x0F) << 2) | ((b2 >> 6) & 0x03)
    let c3 = b2 & 0x3F

    result = result + get_encode_char(base64url_chars[c0])
    result = result + get_encode_char(base64url_chars[c1])
    result = result + get_encode_char(base64url_chars[c2])
    result = result + get_encode_char(base64url_chars[c3])

    i = i + 3
  }

  let remaining = len - i
  if remaining == 2 {
    let b0 = data[i].reinterpret_as_int()
    let b1 = data[i + 1].reinterpret_as_int()

    let c0 = (b0 >> 2) & 0x3F
    let c1 = ((b0 & 0x03) << 4) | ((b1 >> 4) & 0x0F)
    let c2 = (b1 & 0x0F) << 2

    result = result + get_encode_char(base64url_chars[c0])
    result = result + get_encode_char(base64url_chars[c1])
    result = result + get_encode_char(base64url_chars[c2])
  } else if remaining == 1 {
    let b0 = data[i].reinterpret_as_int()

    let c0 = (b0 >> 2) & 0x3F
    let c1 = (b0 & 0x03) << 4

    result = result + get_encode_char(base64url_chars[c0])
    result = result + get_encode_char(base64url_chars[c1])
  }

  result
}

pub fn base64url_encode_string(s: String) -> String {
  let bytes = string_to_bytes_filtered(s)
  let uints : Array[UInt] = []
  for b in bytes {
    uints.push(b.reinterpret_as_uint())
  }
  base64url_encode(uints)
}

pub fn base64url_decode(encoded: String) -> Array[UInt] {
  let chars = string_to_bytes_filtered(encoded)
  let result : Array[UInt] = []
  let len = chars.length()
  let mut i = 0

  while i + 3 < len {
    let c0 = get_decode_value(chars[i])
    let c1 = get_decode_value(chars[i + 1])
    let c2 = get_decode_value(chars[i + 2])
    let c3 = get_decode_value(chars[i + 3])

    if c0 < 0 || c1 < 0 || c2 < 0 || c3 < 0 {
      break
    }

    let b0 = ((c0 << 2) | (c1 >> 4)) & 0xFF
    let b1 = ((c1 << 4) | (c2 >> 2)) & 0xFF
    let b2 = ((c2 << 6) | c3) & 0xFF

    result.push(b0.reinterpret_as_uint())
    result.push(b1.reinterpret_as_uint())
    result.push(b2.reinterpret_as_uint())

    i = i + 4
  }

  let remaining = len - i
  if remaining == 3 {
    let c0 = get_decode_value(chars[i])
    let c1 = get_decode_value(chars[i + 1])
    let c2 = get_decode_value(chars[i + 2])

    if c0 >= 0 && c1 >= 0 && c2 >= 0 {
      let b0 = ((c0 << 2) | (c1 >> 4)) & 0xFF
      let b1 = ((c1 << 4) | (c2 >> 2)) & 0xFF
      result.push(b0.reinterpret_as_uint())
      result.push(b1.reinterpret_as_uint())
    }
  } else if remaining == 2 {
    let c0 = get_decode_value(chars[i])
    let c1 = get_decode_value(chars[i + 1])

    if c0 >= 0 && c1 >= 0 {
      let b0 = ((c0 << 2) | (c1 >> 4)) & 0xFF
      result.push(b0.reinterpret_as_uint())
    }
  }

  result
}

pub fn base64url_decode_to_string(encoded: String) -> String {
  let bytes = base64url_decode(encoded)
  let mut result = ""
  for b in bytes {
    result = result + b.reinterpret_as_int().unsafe_to_char().to_string()
  }
  result
}