// mbt-diffview 命令行入口。

///|
const VERSION : String = "0.1.0"

///|
fn usage() -> String {
  let s = StringBuilder()
  s.write_string("mbt-diffview \{VERSION} — 文本 Diff 查看器\n\n")
  s.write_string("用法:\n  mbt-diffview [选项] <旧文件> <新文件>\n\n")
  s.write_string("选项:\n")
  s.write_string("  --format   输出格式(默认 text)\n")
  s.write_string(
    "  --algorithm   行级 diff 算法(默认 myers)\n",
  )
  s.write_string(
    "  --context               unified patch 上下文行数(默认 3)\n",
  )
  s.write_string("  --no-word-level           关闭词级高亮\n")
  s.write_string(
    "  --output <路径>            写入文件而不是标准输出\n",
  )
  s.write_string("  -v, --version             显示版本\n")
  s.write_string("  -h, --help                显示本帮助\n\n")
  s.write_string(
    "退出码:\n  0  两份文本相同\n  1  存在差异\n  2  用法错误或读写失败\n",
  )
  s.to_string()
}

///|
struct Options {
  mut old_path : String?
  mut new_path : String?
  mut format : String
  mut algorithm : @lib.Algorithm
  mut context : Int
  mut word_level : Bool
  mut output : String?
  mut help : Bool
  mut version : Bool
}

///|
fn Options::new() -> Options {
  {
    old_path: None,
    new_path: None,
    format: "text",
    algorithm: @lib.Myers,
    context: 3,
    word_level: true,
    output: None,
    help: false,
    version: false,
  }
}

///|
fn output(text : String) -> Unit {
  let chars = text.iter().to_array()
  let mut n = chars.length()
  while n > 0 && (chars[n - 1] == '\n' || chars[n - 1] == '\r') {
    n = n - 1
  }
  if n == 0 {
    return
  }
  let buffer = StringBuilder()
  for i = 0; i < n; i = i + 1 {
    buffer.write_char(chars[i])
  }
  println(buffer.to_string())
}

///|
fn usage_error(message : String) -> Unit {
  println("错误: \{message}")
  println("")
  output(usage())
  @sys.exit(2)
}

///|
fn next_arg(args : Array[String], i : Int, name : String) -> String {
  if i + 1 >= args.length() {
    usage_error("\{name} 需要一个参数")
  }
  args[i + 1]
}

///|
fn parse_uint(raw : String, name : String) -> Int {
  if raw.is_empty() {
    usage_error("\{name} 需要非负整数")
  }
  let mut value = 0
  for c in raw {
    if !c.is_ascii_digit() {
      usage_error("\{name} 需要非负整数,收到「\{raw}」")
    }
    value = value * 10 + (c.to_int() - 48)
  }
  value
}

///|
fn parse_algorithm(value : String) -> @lib.Algorithm {
  match value {
    "myers" => @lib.Myers
    "patience" => @lib.Patience
    _ => {
      usage_error("--algorithm 只支持 myers / patience")
      @lib.Myers
    }
  }
}

///|
fn parse_args(args : Array[String]) -> Options {
  let options = Options::new()
  let positional : Array[String] = []
  let mut i = 1
  while i < args.length() {
    let arg = args[i]
    match arg {
      "--format" => {
        options.format = next_arg(args, i, "--format")
        i = i + 1
      }
      "--algorithm" => {
        options.algorithm = parse_algorithm(next_arg(args, i, "--algorithm"))
        i = i + 1
      }
      "--context" => {
        options.context = parse_uint(
          next_arg(args, i, "--context"),
          "--context",
        )
        i = i + 1
      }
      "--no-word-level" => options.word_level = false
      "--output" => {
        options.output = Some(next_arg(args, i, "--output"))
        i = i + 1
      }
      "--help" | "-h" => options.help = true
      "--version" | "-v" => options.version = true
      _ =>
        if arg.has_prefix("-") {
          usage_error("未知选项「\{arg}」")
        } else {
          positional.push(arg)
        }
    }
    i = i + 1
  }
  if options.format != "text" &&
    options.format != "json" &&
    options.format != "html" {
    usage_error("--format 只支持 text / json / html")
  }
  if positional.length() > 0 {
    options.old_path = Some(positional[0])
  }
  if positional.length() > 1 {
    options.new_path = Some(positional[1])
  }
  if positional.length() > 2 {
    usage_error("只接受两个文件参数")
  }
  options
}

///|
fn read_text(path : String) -> Result[String, String] {
  Ok(@fs.read_file_to_string(path)) catch {
    _ => Err("读取文件失败")
  }
}

///|
fn main {
  let args = @env.args()
  let options = parse_args(args)
  if options.help {
    output(usage())
    @sys.exit(0)
    return
  }
  if options.version {
    println("mbt-diffview \{VERSION}")
    @sys.exit(0)
    return
  }
  let old_path = match options.old_path {
    Some(path) => path
    None => {
      usage_error("缺少旧文件参数")
      ""
    }
  }
  let new_path = match options.new_path {
    Some(path) => path
    None => {
      usage_error("缺少新文件参数")
      ""
    }
  }
  let old_text = match read_text(old_path) {
    Ok(text) => text
    Err(message) => {
      println("无法读取「\{old_path}」: \{message}")
      @sys.exit(2)
      ""
    }
  }
  let new_text = match read_text(new_path) {
    Ok(text) => text
    Err(message) => {
      println("无法读取「\{new_path}」: \{message}")
      @sys.exit(2)
      ""
    }
  }
  let diff_options = @lib.DiffOptions::create(
    options.algorithm,
    options.context,
    options.word_level,
  )
  let result = @lib.diff_text(old_text, new_text, diff_options)
  let rendered = match options.format {
    "json" => @lib.render_json(result)
    "html" => @lib.render_html(result, old_path, new_path)
    _ => @lib.render_text(result)
  }
  match options.output {
    Some(path) =>
      @fs.write_string_to_file(path, rendered) catch {
        _ => {
          println("无法写入「\{path}」")
          @sys.exit(2)
        }
      }
    None => output(rendered)
  }
  if @lib.is_different(result) {
    @sys.exit(1)
  } else {
    @sys.exit(0)
  }
}