///|
let vlq_base_shift : Int = 5

///|
let vlq_base : Int = 1 << vlq_base_shift

///|
let vlq_base_mask : Int = vlq_base - 1

///|
let vlq_continuation_bit : Int = vlq_base

///|
fn base64_digit(value : Int) -> Char {
  match value {
    0..<26 => (value + 'A'.to_int()).unsafe_to_char()
    26..<52 => (value - 26 + 'a'.to_int()).unsafe_to_char()
    52..<62 => (value - 52 + '0'.to_int()).unsafe_to_char()
    62 => '+'
    63 => '/'
    _ => '?'
  }
}

///|
fn base64_value(ch : Char) -> Int? {
  let code = ch.to_int()
  if code >= 'A'.to_int() && code <= 'Z'.to_int() {
    Some(code - 'A'.to_int())
  } else if code >= 'a'.to_int() && code <= 'z'.to_int() {
    Some(code - 'a'.to_int() + 26)
  } else if code >= '0'.to_int() && code <= '9'.to_int() {
    Some(code - '0'.to_int() + 52)
  } else if ch == '+' {
    Some(62)
  } else if ch == '/' {
    Some(63)
  } else {
    None
  }
}

///|
fn to_vlq_signed(value : Int) -> Int {
  if value < 0 {
    (-value << 1) + 1
  } else {
    value << 1
  }
}

///|
fn from_vlq_signed(value : Int) -> Int {
  if (value & 1) == 1 {
    -(value >> 1)
  } else {
    value >> 1
  }
}

///|
pub fn encode_vlq(value : Int) -> String {
  let buf = StringBuilder()
  let mut vlq = to_vlq_signed(value)
  for ;; {
    let digit = vlq & vlq_base_mask
    vlq = vlq >> vlq_base_shift
    let digit = if vlq > 0 { digit | vlq_continuation_bit } else { digit }
    buf.write_char(base64_digit(digit))
    if vlq == 0 {
      break
    }
  }
  buf.to_string()
}

///|
pub fn decode_vlq(
  input : StringView,
  offset : Int,
) -> Result[(Int, Int), SourceMapError] {
  let mut result = 0
  let mut shift = 0
  let mut index = offset
  for ;; {
    if index >= input.length() {
      return Err(TruncatedVlq(offset~))
    }
    let ch = input.unsafe_get(index).unsafe_to_char()
    let digit = match base64_value(ch) {
      Some(value) => value
      None => return Err(InvalidBase64(char=ch, offset=index))
    }
    let continuation = (digit & vlq_continuation_bit) != 0
    let payload = digit & vlq_base_mask
    if shift >= 30 && payload > 1 {
      return Err(VlqOverflow(offset=index))
    }
    result = result | (payload << shift)
    index += 1
    shift += vlq_base_shift
    if !continuation {
      break
    }
  }
  Ok((from_vlq_signed(result), index))
}

///|
pub fn decode_vlq_values(
  input : StringView,
) -> Result[Array[Int], SourceMapError] {
  let values : Array[Int] = []
  let mut offset = 0
  while offset < input.length() {
    match decode_vlq(input, offset) {
      Ok((value, next)) => {
        values.push(value)
        offset = next
      }
      Err(err) => return Err(err)
    }
  }
  Ok(values)
}

///|
pub fn encode_vlq_values(values : ArrayView[Int]) -> String {
  let buf = StringBuilder()
  for value in values {
    buf.write_string(encode_vlq(value))
  }
  buf.to_string()
}