///|
fn bytes_hex_digit(n : Int32) -> Char {
  if n < 10 {
    ('0'.to_int() + n).to_char().unwrap()
  } else {
    ('a'.to_int() + (n - 10)).to_char().unwrap()
  }
}

///|
fn bytes_hex_byte(value : Int32) -> String {
  let buf = StringBuilder()
  let hi = (value >> 4) & 0xF
  let lo = value & 0xF
  buf.write_char(bytes_hex_digit(hi))
  buf.write_char(bytes_hex_digit(lo))
  buf.to_string()
}

///|
fn bytes_repr(values : Array[Int32]) -> String {
  let buf = StringBuilder()
  buf.write_string("b'")
  for byte in values {
    let v = byte & 0xFF
    if v == 0x5c {
      buf.write_string("\\\\")
    } else if v == 0x27 {
      buf.write_string("\\'")
    } else if v == 0x09 {
      buf.write_string("\\t")
    } else if v == 0x0a {
      buf.write_string("\\n")
    } else if v == 0x0d {
      buf.write_string("\\r")
    } else if v >= 0x20 && v <= 0x7e {
      buf.write_char(v.to_char().unwrap())
    } else {
      buf.write_string("\\x")
      buf.write_string(bytes_hex_byte(v))
    }
  }
  buf.write_char('\'')
  buf.to_string()
}

///|
fn complex_to_string(real : Double, imag : Double) -> String {
  if real == 0.0 {
    if imag == 0.0 {
      "0j"
    } else {
      imag.to_string() + "j"
    }
  } else {
    let sign = if imag >= 0.0 { "+" } else { "-" }
    let imag_abs = if imag < 0.0 { -imag } else { imag }
    "(" + real.to_string() + sign + imag_abs.to_string() + "j)"
  }
}

///|
fn value_to_string(value : @mpython.Value) -> String {
  match value {
    @mpython.Value::None => "None"
    @mpython.Value::Bool(v) => if v { "True" } else { "False" }
    @mpython.Value::Int(v) => v.to_string()
    @mpython.Value::Float(v) => v.to_string()
    @mpython.Value::Complex(real, imag) => complex_to_string(real, imag)
    @mpython.Value::Str(v) => v
    @mpython.Value::Bytes(values) => bytes_repr(values)
    @mpython.Value::ByteArray(values) => "bytearray(" + bytes_repr(values) + ")"
    @mpython.Value::MemoryView(values) =>
      "memoryview(" + bytes_repr(values) + ")"
    @mpython.Value::List(values) => {
      let buf = StringBuilder()
      buf.write_char('[')
      for i = 0; i < values.length(); i = i + 1 {
        if i > 0 {
          buf.write_string(", ")
        }
        buf.write_string(value_to_string(values[i]))
      }
      buf.write_char(']')
      buf.to_string()
    }
    @mpython.Value::Tuple(values) => {
      let buf = StringBuilder()
      buf.write_char('(')
      for i = 0; i < values.length(); i = i + 1 {
        if i > 0 {
          buf.write_string(", ")
        }
        buf.write_string(value_to_string(values[i]))
      }
      if values.length() == 1 {
        buf.write_string(",")
      }
      buf.write_char(')')
      buf.to_string()
    }
    @mpython.Value::Dict(_) => "{}"
    @mpython.Value::Set(values) => {
      let buf = StringBuilder()
      buf.write_char('{')
      for i = 0; i < values.length(); i = i + 1 {
        if i > 0 {
          buf.write_string(", ")
        }
        buf.write_string(value_to_string(values[i]))
      }
      buf.write_char('}')
      buf.to_string()
    }
    @mpython.Value::Function(func) => ""
    @mpython.Value::Class(klass) => ""
    @mpython.Value::Instance(inst) => "<" + inst.class.name + " object>"
    @mpython.Value::BoundMethod(bound_method) =>
      ""
  }
}

///|
fn print_run(run : @mpython.RunResult) -> Unit {
  if run.stdout.length() > 0 {
    @fs.write_string_to_file("/dev/stdout", run.stdout) catch {
      _ => ()
    }
  }
  if run.stderr.length() > 0 {
    @fs.write_string_to_file("/dev/stderr", run.stderr) catch {
      _ => ()
    }
  }
  match run.value {
    @mpython.Value::None => ()
    _ => println(value_to_string(run.value))
  }
}

///|
fn run_line(interpreter : @mpython.Interpreter, line : String) -> Unit {
  let trimmed = line.trim(chars=" \t").to_owned()
  if trimmed.length() == 0 {
    return
  }
  if trimmed == ":quit" || trimmed == ":exit" {
    @sys.exit(0)
  }
  match interpreter.exec_source(line) {
    Ok(run) => print_run(run)
    Err(err) => println(@mpython.format_runtime_error(err))
  }
}

///|
fn print_usage() -> Unit {
  println("usage: moon run cmd/repl -- [--stdlib PATH]")
}

///|
fn main {
  let args = @env.args()
  let stdlib_paths : Array[String] = []
  let mut i = 1
  while i < args.length() {
    let arg = args[i]
    if arg == "-h" || arg == "--help" {
      print_usage()
      @sys.exit(0)
    }
    if arg == "--stdlib" || arg == "--stdlib-path" {
      if i + 1 >= args.length() {
        println("missing value for " + arg)
        print_usage()
        @sys.exit(1)
      }
      stdlib_paths.push(args[i + 1])
      i += 2
      continue
    }
    println("unexpected argument: " + arg)
    print_usage()
    @sys.exit(1)
  }
  println("moonpython REPL (Ctrl-D to exit)")
  let source = @fs.read_file_to_string("/dev/stdin") catch { _ => "" }
  let config = @mpython.Config::for_cli(stdlib_paths, None, [""])
  let interpreter = @mpython.Interpreter::with_config(config)
  for line in source.split("\n") {
    run_line(interpreter, line.to_owned())
  }
}