///|
/// Python's `repr()` of a `str`.
fn py_str_repr(s : String) -> String {
  let quote = if s.contains("'") && !s.contains("\"") { '"' } else { '\'' }
  let sb = StringBuilder()
  sb.write_char(quote)
  for c in s {
    if c == quote || c == '\\' {
      sb.write_char('\\')
      sb.write_char(c)
    } else if c == '\n' {
      sb.write_string("\\n")
    } else if c == '\r' {
      sb.write_string("\\r")
    } else if c == '\t' {
      sb.write_string("\\t")
    } else if c.to_int() < 0x20 || (c.to_int() >= 0x7f && c.to_int() < 0xa0) {
      let hex = c.to_int().to_string(radix=16)
      sb.write_string(if hex.length() < 2 { "\\x0" + hex } else { "\\x" + hex })
    } else {
      sb.write_char(c)
    }
  }
  sb.write_char(quote)
  sb.to_string()
}

///|
fn py_number_repr(d : Double, repr : String?) -> String {
  if repr is Some(r) && !r.contains(".") && !r.contains("e") && !r.contains("E") {
    return r
  }
  if d == d.floor() && d.abs() < 1.0e16 {
    d.to_int64().to_string()
  } else {
    d.to_string()
  }
}

///|
/// Python's `repr()` of the deserialized JSON value (`json.loads` output),
/// e.g. `{'foo': [1, True, None]}`.
fn py_repr(j : Json) -> String {
  let sb = StringBuilder()
  write_py_repr(sb, j)
  sb.to_string()
}

///|
fn write_py_repr(sb : StringBuilder, j : Json) -> Unit {
  match j {
    Null => sb.write_string("None")
    True => sb.write_string("True")
    False => sb.write_string("False")
    Number(d, repr~) => sb.write_string(py_number_repr(d, repr))
    String(s) => sb.write_string(py_str_repr(s))
    Array(xs) => {
      sb.write_char('[')
      for i, x in xs {
        if i > 0 {
          sb.write_string(", ")
        }
        write_py_repr(sb, x)
      }
      sb.write_char(']')
    }
    Object(m) => {
      sb.write_char('{')
      let mut first = true
      for k, v in m {
        if !first {
          sb.write_string(", ")
        }
        first = false
        sb.write_string(py_str_repr(k))
        sb.write_string(": ")
        write_py_repr(sb, v)
      }
      sb.write_char('}')
    }
  }
}

///|
/// Python's `str()` of a deserialized JSON value (strings are not quoted).
fn py_str(j : Json) -> String {
  match j {
    String(s) => s
    _ => py_repr(j)
  }
}

///|
/// Python truthiness of a deserialized JSON value.
fn py_truthy(j : Json) -> Bool {
  match j {
    Null | False => false
    True => true
    Number(d, ..) => d != 0.0
    String(s) => s != ""
    Array(xs) => !xs.is_empty()
    Object(m) => !m.is_empty()
  }
}