// Python `str.format` for the export templates (`code_format`).

///|
fn is_format_align(c : Char) -> Bool {
  c == '<' || c == '>' || c == '^' || c == '='
}

///|
/// Apply a Python format spec (`[[fill]align][0][width][.precision][s]`) to
/// a string value.
fn py_format_spec(value : String, spec : String) -> String {
  let chars : Array[Char] = spec.iter().collect()
  let n = chars.length()
  let mut i = 0
  let mut fill = ' '
  let mut align : Char? = None
  if n >= 2 && is_format_align(chars[1]) {
    fill = chars[0]
    align = Some(chars[1])
    i = 2
  } else if n >= 1 && is_format_align(chars[0]) {
    align = Some(chars[0])
    i = 1
  }
  // sign and alternate form are not meaningful for strings
  while i < n &&
        (
          chars[i] == '+' ||
          chars[i] == '-' ||
          chars[i] == ' ' ||
          chars[i] == '#'
        ) {
    i += 1
  }
  if i < n && chars[i] == '0' && align is None {
    fill = '0'
    i += 1
  }
  let mut width = 0
  while i < n && chars[i] >= '0' && chars[i] <= '9' {
    width = width * 10 + (chars[i].to_int() - '0'.to_int())
    i += 1
  }
  let mut precision : Int? = None
  if i < n && chars[i] == '.' {
    i += 1
    let mut p = 0
    while i < n && chars[i] >= '0' && chars[i] <= '9' {
      p = p * 10 + (chars[i].to_int() - '0'.to_int())
      i += 1
    }
    precision = Some(p)
  }
  let value_chars : Array[Char] = value.iter().collect()
  let value_chars = match precision {
    Some(p) if p < value_chars.length() => value_chars[0:p].to_owned()
    _ => value_chars
  }
  let text = String::from_array(value_chars)
  let padding = width - value_chars.length()
  if padding <= 0 {
    return text
  }
  let fill_str = fill.to_string()
  match align {
    Some('>') => fill_str.repeat(padding) + text
    Some('^') => {
      let left = padding / 2
      fill_str.repeat(left) + text + fill_str.repeat(padding - left)
    }
    _ => text + fill_str.repeat(padding)
  }
}

///|
/// Python `str.format` with named fields: `{name}`, `{name!r}`,
/// `{name:spec}` (string format specs: fill, alignment, width, precision),
/// and `{{` / `}}` escapes. Unknown fields are left as they are.
pub fn py_format(template : String, fields : Map[String, String]) -> String {
  let buf = StringBuilder()
  let n = template.length()
  let mut i = 0
  while i < n {
    let c = template[i]
    if c == '{' {
      if i + 1 < n && template[i + 1] == '{' {
        buf.write_char('{')
        i += 2
        continue
      }
      match template.view(start_offset=i).find("}") {
        Some(j) => {
          let field = py_slice(template, i + 1, i + j)
          let (name_conv, spec) = match field.find(":") {
            Some(k) =>
              (
                py_slice(field, 0, k),
                Some(py_slice(field, k + 1, field.length())),
              )
            None => (field, None)
          }
          let (name, conversion) = match name_conv.find("!") {
            Some(k) =>
              (
                py_slice(name_conv, 0, k),
                Some(py_slice(name_conv, k + 1, name_conv.length())),
              )
            None => (name_conv, None)
          }
          match fields.get(name) {
            Some(v) => {
              let v = match conversion {
                Some("r") | Some("a") => py_repr(v)
                _ => v
              }
              let v = match spec {
                Some(spec) => py_format_spec(v, spec)
                None => v
              }
              buf.write_string(v)
            }
            None => buf.write_string(py_slice(template, i, i + j + 1))
          }
          i += j + 1
        }
        None => {
          buf.write_char('{')
          i += 1
        }
      }
    } else if c == '}' && i + 1 < n && template[i + 1] == '}' {
      buf.write_char('}')
      i += 2
    } else {
      buf.write_char(c.to_int().unsafe_to_char())
      i += 1
    }
  }
  buf.to_string()
}