///|
/// Encode one point as a line-protocol row.
pub fn encode_point(point : Point) -> Result[String, String] {
  match validate_point(point) {
    Ok(_) => ()
    Err(error) => return Err(error)
  }

  let out = StringBuilder()
  out.write_string(escape_measurement(point.measurement))

  for i in 0.. 0 {
      out.write_char(',')
    }
    out.write_string(escape_key_like(key))
    out.write_char('=')
    out.write_string(field_value_to_protocol(value))
  }

  match point.timestamp {
    Some(timestamp) => {
      out.write_char(' ')
      out.write_string(timestamp.to_string())
    }
    None => ()
  }

  Ok(out.to_string())
}

///|
/// Validate point invariants required by InfluxDB Line Protocol.
pub fn validate_point(point : Point) -> Result[Unit, String] {
  if point.measurement.is_empty() {
    return Err("measurement must not be empty")
  }
  if point.fields.is_empty() {
    return Err("at least one field is required")
  }
  for i in 0..
        if value.is_nan() || value.is_inf() {
          return Err("float field must be finite: \{key}")
        }
      _ => ()
    }
    for j in 0.. Result[String, String] {
  let out = StringBuilder()
  for i in 0.. 0 {
      out.write_char('\n')
    }
    match encode_point(points[i]) {
      Ok(line) => out.write_string(line)
      Err(error) => return Err(error)
    }
  }
  Ok(out.to_string())
}

///|
/// Parse one line-protocol row.
pub fn parse_line(line : String) -> Result[Point, String] {
  if line.is_empty() {
    return Err("line must not be empty")
  }

  let comma = find_unescaped(line, 0, 44)
  let space = find_unescaped(line, 0, 32)
  let measurement_end = match (comma, space) {
    (Some(a), Some(b)) => if a < b { a } else { b }
    (Some(a), None) => a
    (None, Some(b)) => b
    (None, None) => return Err("line must contain fields")
  }
  if measurement_end == 0 {
    return Err("measurement must not be empty")
  }

  let measurement = unescape_measurement(line[0:measurement_end].to_owned())
  let mut cursor = measurement_end
  let mut point = new_point(measurement)

  if cursor < line.length() && line.at(cursor).to_int() == 44 {
    match parse_tag_set(line, cursor, point) {
      Ok((updated, next)) => {
        point = updated
        cursor = next
      }
      Err(error) => return Err(error)
    }
  }

  if cursor >= line.length() || line.at(cursor).to_int() != 32 {
    return Err("tags must be followed by a space")
  }
  let field_start = cursor + 1
  let timestamp_start = find_field_unescaped(line, field_start, 32)
  let field_end = match timestamp_start {
    Some(index) => index
    None => line.length()
  }
  if field_end <= field_start {
    return Err("at least one field is required")
  }
  match parse_field_set(line[field_start:field_end].to_owned()) {
    Ok(fields) =>
      for field in fields {
        point.fields.push(field)
      }
    Err(error) => return Err(error)
  }

  match timestamp_start {
    Some(index) => {
      let raw = line[index + 1:line.length()].to_owned()
      if raw.is_empty() {
        return Err("timestamp must not be empty")
      }
      match try_parse_int64(raw) {
        Ok(timestamp) => point = { ..point, timestamp: Some(timestamp), }
        Err(error) => return Err(error)
      }
    }
    None => ()
  }

  Ok(point)
}

///|
/// Parse a newline-delimited line-protocol payload.
pub fn parse_lines(text : String) -> Result[Array[Point], String] {
  let points : Array[Point] = Array(capacity=0)
  for line in text.split("\n") {
    let clean = line.trim().to_owned()
    if clean.is_empty() {
      continue
    }
    match parse_line(clean) {
      Ok(point) => points.push(point)
      Err(error) => return Err(error)
    }
  }
  Ok(points)
}

///|
/// Return the first unescaped occurrence of an ASCII code, or None.
fn find_unescaped(value : String, start : Int32, target : Int32) -> Int32? {
  let mut escaped = false
  for i in start.. Int32? {
  let mut escaped = false
  let mut in_string = false
  for i in start.. Result[(Point, Int32), String] {
  let content_start = start + 1
  let end = match find_unescaped(line, content_start, 32) {
    Some(index) => index
    None => line.length()
  }
  if end == content_start {
    return Err("tag set must not be empty")
  }
  match parse_pairs(line[content_start:end].to_owned()) {
    Ok(tags) => {
      let updated = point
      for tag in tags {
        updated.tags.push(tag)
      }
      Ok((updated, end))
    }
    Err(error) => Err(error)
  }
}

///|
/// Parse tag or field pairs.
fn parse_pairs(content : String) -> Result[Array[(String, String)], String] {
  let pairs : Array[(String, String)] = Array(capacity=0)
  let mut start = 0
  while start < content.length() {
    let end = match find_field_unescaped(content, start, 44) {
      Some(index) => index
      None => content.length()
    }
    if end == start {
      return Err("empty tag or field pair")
    }
    let raw = content[start:end].to_owned()
    let eq_index = match find_field_unescaped(raw, 0, 61) {
      Some(index) => index
      None => return Err("tag or field pair must contain '='")
    }
    if eq_index == 0 || eq_index + 1 >= raw.length() {
      return Err("tag or field key/value must not be empty")
    }
    let key = raw[0:eq_index].to_owned()
    let value = raw[eq_index + 1:raw.length()].to_owned()
    pairs.push((unescape_key_like(key), unescape_key_like(value)))
    start = if end == content.length() { end } else { end + 1 }
  }
  Ok(pairs)
}

///|
/// Parse the field set.
fn parse_field_set(
  content : String,
) -> Result[Array[(String, FieldValue)], String] {
  let fields : Array[(String, FieldValue)] = Array(capacity=0)
  let mut start = 0
  while start < content.length() {
    let end = match find_field_unescaped(content, start, 44) {
      Some(index) => index
      None => content.length()
    }
    if end == start {
      return Err("empty field pair")
    }
    let raw = content[start:end].to_owned()
    let eq_index = match find_field_unescaped(raw, 0, 61) {
      Some(index) => index
      None => return Err("field pair must contain '='")
    }
    if eq_index == 0 || eq_index + 1 >= raw.length() {
      return Err("field key/value must not be empty")
    }
    let key = raw[0:eq_index].to_owned()
    let value = raw[eq_index + 1:raw.length()].to_owned()
    match parse_field_value(value) {
      Ok(parsed) => fields.push((unescape_key_like(key), parsed))
      Err(error) => return Err(error)
    }
    start = if end == content.length() { end } else { end + 1 }
  }
  Ok(fields)
}

///|
/// Convert a field value to line-protocol text.
fn field_value_to_protocol(value : FieldValue) -> String {
  match value {
    IntValue(value) => value.to_string() + "i"
    UIntValue(value) => value.to_string() + "u"
    DoubleValue(value) => value.to_string()
    StringValue(value) => "\"" + escape_string_field(value) + "\""
    BoolValue(value) => if value { "true" } else { "false" }
  }
}

///|
/// Parse an InfluxDB field value.
fn parse_field_value(raw : String) -> Result[FieldValue, String] {
  if raw.has_prefix("\"") {
    if raw.length() < 2 || !raw.has_suffix("\"") {
      return Err("unterminated string field")
    }
    return Ok(
      StringValue(unescape_string_field(raw[1:raw.length() - 1].to_owned())),
    )
  }

  let lower = raw.to_lower()
  if lower == "t" || lower == "true" {
    return Ok(BoolValue(true))
  }
  if lower == "f" || lower == "false" {
    return Ok(BoolValue(false))
  }

  if raw.has_suffix("i") {
    let numeric = raw[0:raw.length() - 1].to_owned()
    return match try_parse_int64(numeric) {
      Ok(value) => Ok(IntValue(value))
      Err(error) => Err(error)
    }
  }
  if raw.has_suffix("u") {
    let numeric = raw[0:raw.length() - 1].to_owned()
    return match try_parse_uint64(numeric) {
      Ok(value) => Ok(UIntValue(value))
      Err(error) => Err(error)
    }
  }

  match try_parse_double(raw) {
    Ok(value) => Ok(DoubleValue(value))
    Err(error) => Err(error)
  }
}

///|
/// Parse an Int64 without leaking parser exceptions.
fn try_parse_int64(text : String) -> Result[Int64, String] {
  Ok(@string.parse_int64(text)) catch {
    _ => Err("invalid integer \{text}")
  }
}

///|
/// Parse a UInt64 without leaking parser exceptions.
fn try_parse_uint64(text : String) -> Result[UInt64, String] {
  Ok(@string.parse_uint64(text)) catch {
    _ => Err("invalid unsigned integer \{text}")
  }
}

///|
/// Parse a Double without leaking parser exceptions.
fn try_parse_double(text : String) -> Result[Double, String] {
  Ok(@string.parse_double(text)) catch {
    _ => Err("invalid float \{text}")
  }
}