///|
/// Convert a parser error into the common diagnostic format.
fn parse_diagnostic(
  error : @parser.ParseError,
  file_name : String,
) -> @diagnostic.Diagnostic {
  @diagnostic.Diagnostic::new(
    "RTL001",
    @diagnostic.Error,
    error.message,
    error.span,
  ).with_file(file_name)
}

///|
fn usage() -> String {
  options_usage()
}

///|
/// Write a renderer result without adding an extra blank line.
fn emit(text : String) -> Unit {
  for line in text.split("\n") {
    if !line.is_empty() {
      println(line)
    }
  }
}

///|
fn outline(design : @syntax.Design) -> String {
  let builder = StringBuilder()
  for mod in design.modules {
    builder.write_string("module ")
    builder.write_string(mod.name)
    builder.write_string("\n")
    for port in mod.ports {
      builder.write_string("  port ")
      builder.write_string(port.name)
      builder.write_string("\n")
    }
    for signal in mod.signals {
      builder.write_string("  signal ")
      builder.write_string(signal.name)
      builder.write_string("\n")
    }
    for item in mod.items {
      match item {
        @syntax.Instance(instance) => {
          builder.write_string("  instance ")
          builder.write_string(instance.instance_name)
          builder.write_string(" : ")
          builder.write_string(instance.module_name)
          builder.write_string("\n")
        }
        _ => ()
      }
    }
  }
  builder.to_string()
}

///|
fn print_diagnostics(
  diagnostics : Array[@diagnostic.Diagnostic],
  source : @source.SourceFile,
  format : String,
) -> Unit {
  if format == "json" {
    println(@diagnostic.render_json(diagnostics))
  } else if format == "sarif" {
    emit(@diagnostic.render_sarif(diagnostics, source))
  } else {
    emit(@diagnostic.render_text(diagnostics, source))
  }
}

///|
fn read_source(path : String) -> String {
  @fs.read_file_to_string(path) catch {
    _ => {
      println("moonbit-rtl: cannot read " + path)
      @sys.exit(2)
      ""
    }
  }
}

///|
fn check_file(path : String, format : String) -> Int {
  let text = read_source(path)
  let source = @source.SourceFile::new(path, text)
  let parsed = @parser.parse(source)
  let diagnostics : Array[@diagnostic.Diagnostic] = []
  for error in parsed.errors {
    diagnostics.push(parse_diagnostic(error, path))
  }
  if parsed.errors.length() == 0 {
    let index = @semantic.index(parsed.design)
    for diagnostic in @lint.run(index, @lint.default_config()) {
      diagnostics.push(diagnostic.with_file(path))
    }
  }
  print_diagnostics(diagnostics, source, format)
  if diagnostics.any(fn(diagnostic) { diagnostic.severity is @diagnostic.Error }) {
    1
  } else {
    0
  }
}

///|
fn outline_file(path : String) -> Int {
  let text = read_source(path)
  let source = @source.SourceFile::new(path, text)
  let parsed = @parser.parse(source)
  if parsed.errors.length() > 0 {
    for error in parsed.errors {
      println(parse_diagnostic(error, path).message)
    }
    1
  } else {
    emit(outline(parsed.design))
    0
  }
}

///|
fn graph_file(path : String) -> Int {
  let text = read_source(path)
  let source = @source.SourceFile::new(path, text)
  let parsed = @parser.parse(source)
  if parsed.errors.length() > 0 {
    1
  } else {
    emit(@diagnostic.render_graph(@semantic.index(parsed.design)))
    0
  }
}

///|
fn summary_file(path : String) -> Int {
  let text = read_source(path)
  let source = @source.SourceFile::new(path, text)
  let parsed = @parser.parse(source)
  println(parsed.summary())
  if parsed.errors.length() > 0 {
    1
  } else {
    0
  }
}

///|
fn ir_file(path : String) -> Int {
  let text = read_source(path)
  let source = @source.SourceFile::new(path, text)
  let parsed = @parser.parse(source)
  if parsed.errors.length() > 0 {
    for error in parsed.errors {
      println(parse_diagnostic(error, path).message)
    }
    1
  } else {
    println(@semantic.index(parsed.design).to_ir_json())
    0
  }
}

///|
fn tokens_file(path : String) -> Int {
  let text = read_source(path)
  let source = @source.SourceFile::new(path, text)
  let tokens = @lexer.lex(source)
  println(@lexer.token_summary(tokens))
  for token in @lexer.without_eof(tokens) {
    println(token.describe())
  }
  if @lexer.token_metrics(tokens).invalid > 0 {
    1
  } else {
    0
  }
}

///|
fn main {
  let args = @env.args()
  if args.length() < 3 {
    println(usage())
    @sys.exit(2)
  }
  let command = args[1]
  let path = args[2]
  let mut format = "text"
  for argument in args[3:] {
    if argument == "json" || argument == "--format=json" {
      format = "json"
    } else if argument == "sarif" || argument == "--format=sarif" {
      format = "sarif"
    }
  }
  let code = match command {
    "check" => check_file(path, format)
    "outline" => outline_file(path)
    "graph" => graph_file(path)
    "summary" => summary_file(path)
    "ir" => ir_file(path)
    "tokens" => tokens_file(path)
    _ => {
      println(usage())
      2
    }
  }
  @sys.exit(code)
}