///|
pub fn encode_polyline(
  points : ArrayView[Point],
  precision? : Int = 5,
) -> Result[String, RouteError] {
  match validate_precision(precision) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  match validate_points(points) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let scale = pow10_int(precision).to_double()
  let out = StringBuilder(size_hint=points.length() * 10)
  let mut prev_lat = 0
  let mut prev_lon = 0
  for point in points {
    let lat = (point.lat * scale).round().to_int()
    let lon = (point.lon * scale).round().to_int()
    write_polyline_value(out, lat - prev_lat)
    write_polyline_value(out, lon - prev_lon)
    prev_lat = lat
    prev_lon = lon
  }
  Ok(out.to_string())
}

///|
pub fn decode_polyline(
  encoded : String,
  precision? : Int = 5,
) -> Result[Array[Point], RouteError] {
  match validate_precision(precision) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let scale = pow10_int(precision).to_double()
  let points = Array::new()
  let mut index = 0
  let mut lat = 0
  let mut lon = 0
  while index < encoded.length() {
    let (delta_lat, next_index) = match read_polyline_value(encoded, index) {
      Ok(pair) => pair
      Err(err) => return Err(err)
    }
    index = next_index
    if index >= encoded.length() {
      return Err(MalformedPolyline(pos=index, reason="missing longitude delta"))
    }
    let (delta_lon, next_index) = match read_polyline_value(encoded, index) {
      Ok(pair) => pair
      Err(err) => return Err(err)
    }
    index = next_index
    lat = lat + delta_lat
    lon = lon + delta_lon
    let point = Point(lat.to_double() / scale, lon.to_double() / scale)
    match validate_point(point, points.length()) {
      Ok(_) => points.push(point)
      Err(err) => return Err(err)
    }
  }
  Ok(points)
}

///|
pub fn encode_polyline5(
  points : ArrayView[Point],
) -> Result[String, RouteError] {
  encode_polyline(points, precision=5)
}

///|
pub fn decode_polyline5(encoded : String) -> Result[Array[Point], RouteError] {
  decode_polyline(encoded, precision=5)
}

///|
fn write_polyline_value(out : StringBuilder, delta : Int) -> Unit {
  let mut value = delta * 2
  if delta < 0 {
    value = -value - 1
  }
  while value >= 0x20 {
    out.write_char((0x20 + value % 0x20 + 63).unsafe_to_char())
    value = value / 0x20
  }
  out.write_char((value + 63).unsafe_to_char())
}

///|
fn read_polyline_value(
  encoded : String,
  start : Int,
) -> Result[(Int, Int), RouteError] {
  let mut index = start
  let mut factor = 1
  let mut result = 0
  let mut groups = 0
  while index < encoded.length() {
    if groups >= 7 {
      return Err(MalformedPolyline(pos=index, reason="delta is too large"))
    }
    let code = encoded[index].to_int() - 63
    if code < 0 || code > 63 {
      return Err(
        MalformedPolyline(
          pos=index,
          reason="character is outside polyline alphabet",
        ),
      )
    }
    result = result + code % 0x20 * factor
    factor = factor * 0x20
    index = index + 1
    groups = groups + 1
    if code < 0x20 {
      let delta = if result % 2 == 1 { -(result + 1) / 2 } else { result / 2 }
      return Ok((delta, index))
    }
  }
  Err(MalformedPolyline(pos=start, reason="unterminated delta"))
}