//! The `moonbind` command.
//!
//! `main` is one line of policy: run, then exit with the code the run chose.
//! Everything else is in `drive`, which returns a status instead of calling
//! `exit` from deep inside a `try`, so each exit point is visible in one
//! place.

///|
/// | Exit codes: 0 success, 1 the input could not be processed, 2 the
/// | invocation was wrong. A script can tell "your header has nothing
/// | bindable" from "you typed the flag wrong".
fn main {
  // `@env.args()` is the raw C `argv`, so its first element is the program
  // itself. `parse_args` sees only what the user typed.
  let argv = @env.args()
  let args : Array[String] = if argv.length() > 0 {
    argv[1:].to_owned()
  } else {
    []
  }
  let code = drive(args)
  if code != 0 {
    @sys.exit(code)
  }
}

///|
/// | Parse, run, report. Returns the status.
fn drive(argv : Array[String]) -> Int32 {
  let parsed_args = @cli.prepare_args(argv) catch {
    @cli.Failed(message) => {
      report_error(argv, message)
      return 2
    }
  }
  let options = match parsed_args {
    @cli.Help => {
      println(@cli.usage())
      return 0
    }
    @cli.Version => {
      println("moonbind " + @cli.version)
      return 0
    }
    @cli.Bad(message) => {
      report_error(argv, message)
      if !argv.contains("--json") {
        eprintln(@cli.usage())
      }
      return 2
    }
    @cli.Run(options) => options
  }
  let execution = if options.compare is Some(_) {
    { ..options, json: true, html: false, }
  } else {
    options
  }
  let raw = @cli.run(execution) catch {
    @cli.Failed(message) => {
      report_error(argv, message)
      return 1
    }
  }
  let mut blocked = false
  let text = if options.compare is Some(_) {
    let report = @json.parse(raw) catch {
      _ => {
        eprintln("invalid comparison report")
        return 1
      }
    }
    blocked = @cli.review_blocked(report, options.fail_on)
    if options.html {
      @cli.review_html(report)
    } else if options.json {
      raw
    } else {
      @cli.review_text(report)
    }
  } else {
    raw
  }
  // `--verify` produces a table to read, not a package to write, so it prints
  // whether or not `--out` was given.
  match options.out {
    None => println(text)
    Some(_) =>
      if options.verify || options.json || options.check {
        println(text)
      }
  }
  if blocked {
    return 3
  }
  0
}

///|
fn report_error(argv : Array[String], message : String) -> Unit {
  if argv.contains("--json") {
    println(@cli.failure_report(message))
  } else {
    eprintln("moonbind: " + message)
  }
}