///|
pub fn codec_escape_description(value : String) -> String {
  let first = value.replace(old="\\"[:], new="\\\\"[:])
  let second = first.replace(old="\""[:], new="\\\""[:])
  let third = second.replace(old="\n"[:], new="\\n"[:])
  third.replace(old=","[:], new="\\,"[:])
}

///|
pub fn codec_unescape_description(value : String) -> String {
  let builder = StringBuilder()
  let mut escaped = false
  for ch in value {
    if escaped {
      match ch {
        'n' => builder.write_char('\n')
        _ => builder.write_char(ch)
      }
      escaped = false
    } else if ch == '\\' {
      escaped = true
    } else {
      builder.write_char(ch)
    }
  }
  if escaped {
    builder.write_char('\\')
  }
  builder.to_string()
}

///|
pub fn codec_quote(value : String) -> String {
  "\"\{codec_escape_description(value)}\""
}

///|
pub fn codec_unquote(value : String) -> String {
  if value.length() >= 2 && value.has_prefix("\"") && value.has_suffix("\"") {
    codec_unescape_description(
      value.unsafe_substring(start=1, end=value.length() - 1),
    )
  } else {
    value
  }
}

///|
pub fn codec_split_escaped(value : String, sep : Char) -> Array[String] {
  let values : Array[String] = []
  let mut current = StringBuilder()
  let mut escaped = false
  for ch in value {
    if escaped {
      current.write_char(ch)
      escaped = false
    } else if ch == '\\' {
      escaped = true
    } else if ch == sep {
      values.push(current.to_string())
      current = StringBuilder()
    } else {
      current.write_char(ch)
    }
  }
  if escaped {
    current.write_char('\\')
  }
  values.push(current.to_string())
  values
}

///|
pub fn codec_join_escaped(values : Array[String], sep : Char) -> String {
  values
  .map(fn(value) { value.replace(old="\\"[:], new="\\\\"[:]) })
  .join(sep.to_string())
}

///|
pub fn codec_is_missing(value : String) -> Bool {
  value == "." || value == ""
}

///|
pub fn codec_normalize_missing(value : String) -> String {
  if codec_is_missing(value) {
    "."
  } else {
    value
  }
}

///|
pub fn codec_parse_bool(value : String) -> Bool? {
  match value {
    "true" | "1" | "yes" => Some(true)
    "false" | "0" | "no" => Some(false)
    _ => None
  }
}

///|
pub fn codec_format_bool(value : Bool) -> String {
  if value {
    "true"
  } else {
    "false"
  }
}

///|
pub fn codec_parse_int_list(value : String) -> Array[Int?] {
  codec_split_escaped(value, ',').map(fn(item) { parse_codec_int(item) })
}

///|
fn parse_codec_int(value : String) -> Int? {
  if codec_is_missing(value) {
    None
  } else {
    let mut total = 0
    for ch in value {
      if ch < '0' || ch > '9' {
        return None
      }
      total = total * 10 + ch.to_int() - '0'.to_int()
    }
    Some(total)
  }
}

///|
pub fn codec_format_int_list(values : Array[Int?]) -> String {
  values
  .map(fn(value) {
    match value {
      Some(n) => n.to_string()
      None => "."
    }
  })
  .join(",")
}

///|
pub fn codec_parse_float_list(value : String) -> Array[Double?] {
  codec_split_escaped(value, ',').map(fn(item) {
    if codec_is_missing(item) {
      None
    } else {
      Some(@string.parse_double(item[:])) catch {
        _ => None
      }
    }
  })
}

///|
pub fn codec_format_float_list(values : Array[Double?]) -> String {
  values
  .map(fn(value) {
    match value {
      Some(n) => n.to_string()
      None => "."
    }
  })
  .join(",")
}

///|
pub fn codec_pad(
  values : Array[String],
  width : Int,
  filler : String,
) -> Array[String] {
  let output = values
  while output.length() < width {
    output.push(filler)
  }
  output
}

///|
pub fn codec_truncate(values : Array[String], width : Int) -> Array[String] {
  if width <= 0 {
    []
  } else if values.length() <= width {
    values
  } else {
    values[:width].to_owned()
  }
}

///|
pub fn codec_join_nonempty(values : Array[String], sep : String) -> String {
  values.filter(fn(value) { value != "" }).join(sep)
}