use crate::command_prelude::*;

use cargo::core::Verbosity;
use cargo::ops::{self, CompileFilter};

pub fn cli() -> App {
    subcommand("run")
        // subcommand aliases are handled in aliased_command()
        // .alias("r")
        .setting(AppSettings::TrailingVarArg)
        .about("Run a binary or example of the local package")
        .arg(opt("quiet", "No output printed to stdout").short("q"))
        .arg(Arg::with_name("args").multiple(true))
        .arg_targets_bin_example(
            "Name of the bin target to run",
            "Name of the example target to run",
        )
        .arg_package("Package with the target to run")
        .arg_jobs()
        .arg_release("Build artifacts in release mode, with optimizations")
        .arg_profile("Build artifacts with the specified profile")
        .arg_features()
        .arg_target_triple("Build for the target triple")
        .arg_target_dir()
        .arg_manifest_path()
        .arg_message_format()
        .arg_unit_graph()
        .after_help(
            "\
If neither `--bin` nor `--example` are given, then if the package only has one
bin target it will be run. Otherwise `--bin` specifies the bin target to run,
and `--example` specifies the example target to run. At most one of `--bin` or
`--example` can be provided.

All the arguments following the two dashes (`--`) are passed to the binary to
run. If you're passing arguments to both Cargo and the binary, the ones after
`--` go to the binary, the ones before go to Cargo.
",
        )
}

pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
    let ws = args.workspace(config)?;

    let mut compile_opts = args.compile_options(
        config,
        CompileMode::Build,
        Some(&ws),
        ProfileChecking::Checked,
    )?;

    if !args.is_present("example") && !args.is_present("bin") {
        let default_runs: Vec<_> = compile_opts
            .spec
            .get_packages(&ws)?
            .iter()
            .filter_map(|pkg| pkg.manifest().default_run())
            .collect();
        if default_runs.len() == 1 {
            compile_opts.filter = CompileFilter::from_raw_arguments(
                false,
                vec![default_runs[0].to_owned()],
                false,
                vec![],
                false,
                vec![],
                false,
                vec![],
                false,
                false,
            );
        } else {
            // ops::run will take care of errors if len pkgs != 1.
            compile_opts.filter = CompileFilter::Default {
                // Force this to false because the code in ops::run is not
                // able to pre-check features before compilation starts to
                // enforce that only 1 binary is built.
                required_features_filterable: false,
            };
        }
    };
    match ops::run(&ws, &compile_opts, &values_os(args, "args"))? {
        None => Ok(()),
        Some(err) => {
            // If we never actually spawned the process then that sounds pretty
            // bad and we always want to forward that up.
            let exit = match err.exit {
                Some(exit) => exit,
                None => return Err(CliError::new(err.into(), 101)),
            };

            // If `-q` was passed then we suppress extra error information about
            // a failed process, we assume the process itself printed out enough
            // information about why it failed so we don't do so as well
            let exit_code = exit.code().unwrap_or(101);
            let is_quiet = config.shell().verbosity() == Verbosity::Quiet;
            Err(if is_quiet {
                CliError::code(exit_code)
            } else {
                CliError::new(err.into(), exit_code)
            })
        }
    }
}
