use super::AppError;
use conc::{RunArgs, Runnable, Show, shell_executable};

/// the different top-level commands that conc can execute
#[derive(Debug)]
pub enum Command {
    /// display the help text
    Help,

    /// execute the given commands concurrently
    Run(RunArgs),

    /// display the version
    Version,
}

/// Parses command-line arguments into separate commands by splitting on the separator token.
pub fn parse<SI: Iterator<Item = String>>(args: SI) -> Result<Command, AppError> {
    let mut runnables = vec![];
    let mut show = Show::All;
    let mut error_on_output = false;
    let mut stderr_to_stdout = false;
    let mut parse_flags = true; // indicates whether we are still in the section that contains conc flags
    for arg in args {
        if arg == "--" {
            parse_flags = false;
            continue;
        }
        if !arg.starts_with('-') {
            parse_flags = false;
        }
        if parse_flags && arg.starts_with('-') {
            match arg.as_ref() {
                "--error-on-output" => error_on_output = true,
                "--help" | "-h" => return Ok(Command::Help),
                "--show=all" | "--show" => show = Show::All,
                "--show=names" => show = Show::Names,
                "--show=failed" => show = Show::Failed,
                "--stderr-to-stdout" => stderr_to_stdout = true,
                "--version" | "-V" => return Ok(Command::Version),
                _ => return Err(AppError::UnknownFlag(arg)),
            }
            continue;
        }
        runnables.push(Runnable::Single(shell_executable(arg)));
    }
    Ok(Command::Run(RunArgs {
        runnables,
        error_on_output,
        stderr_to_stdout,
        show,
    }))
}

#[cfg(test)]
mod tests {

    mod parse {
        use super::super::*;
        use big_s::S;

        #[test]
        fn single_command() {
            let give = vec![S("echo hello world")].into_iter();
            let have = parse(give).unwrap();
            let want = Command::Run(RunArgs {
                runnables: vec![Runnable::Single(shell_executable("echo hello world"))],
                error_on_output: false,
                stderr_to_stdout: false,
                show: Show::All,
            });
            assert_eq!(format!("{have:?}"), format!("{want:?}"));
        }

        #[test]
        fn multiple_commands() {
            let give = vec![S("echo hello"), S("ls -la"), S("pwd")].into_iter();
            let have = parse(give).unwrap();
            let want = Command::Run(RunArgs {
                runnables: vec![
                    Runnable::Single(shell_executable("echo hello")),
                    Runnable::Single(shell_executable("ls -la")),
                    Runnable::Single(shell_executable("pwd")),
                ],
                error_on_output: false,
                stderr_to_stdout: false,
                show: Show::All,
            });
            assert_eq!(format!("{have:?}"), format!("{want:?}"));
        }

        #[test]
        fn empty() {
            let give = vec![].into_iter();
            let have = parse(give).unwrap();
            let want = Command::Run(RunArgs {
                runnables: vec![],
                error_on_output: false,
                stderr_to_stdout: false,
                show: Show::All,
            });
            assert_eq!(format!("{have:?}"), format!("{want:?}"));
        }

        #[test]
        fn show_commands() {
            let give = vec![S("--show=names"), S("echo hello")].into_iter();
            let have = parse(give).unwrap();
            let want = Command::Run(RunArgs {
                runnables: vec![Runnable::Single(shell_executable("echo hello"))],
                error_on_output: false,
                stderr_to_stdout: false,
                show: Show::Names,
            });
            assert_eq!(format!("{have:?}"), format!("{want:?}"));
        }

        #[test]
        fn show_all() {
            let give = vec![S("--show=all"), S("echo hello")].into_iter();
            let have = parse(give).unwrap();
            let want = Command::Run(RunArgs {
                runnables: vec![Runnable::Single(shell_executable("echo hello"))],
                error_on_output: false,
                stderr_to_stdout: false,
                show: Show::All,
            });
            assert_eq!(format!("{have:?}"), format!("{want:?}"));
        }

        #[test]
        fn unknown_flag() {
            let give = vec![S("--zonk"), S("echo hello")].into_iter();
            let have = parse(give);
            let want: Result<Command, AppError> = Err(AppError::UnknownFlag(S("--zonk")));
            assert_eq!(format!("{have:?}"), format!("{want:?}"));
        }

        #[test]
        fn manually_end_flags_section() {
            let give = vec![S("--show"), S("--"), S("echo hello")].into_iter();
            let have = parse(give).unwrap();
            let want = Command::Run(RunArgs {
                runnables: vec![Runnable::Single(shell_executable("echo hello"))],
                error_on_output: false,
                stderr_to_stdout: false,
                show: Show::All,
            });
            assert_eq!(format!("{have:?}"), format!("{want:?}"));
        }

        #[test]
        fn help_long() {
            let give = vec![S("--help")].into_iter();
            let have = parse(give).unwrap();
            let want = Command::Help;
            assert_eq!(format!("{have:?}"), format!("{want:?}"));
        }

        #[test]
        fn help_short() {
            let give = vec![S("-h")].into_iter();
            let have = parse(give).unwrap();
            let want = Command::Help;
            assert_eq!(format!("{have:?}"), format!("{want:?}"));
        }

        #[test]
        fn version_short() {
            let give = vec![S("-V")].into_iter();
            let have = parse(give).unwrap();
            let want = Command::Version;
            assert_eq!(format!("{have:?}"), format!("{want:?}"));
        }

        #[test]
        fn version_long() {
            let give = vec![S("--version")].into_iter();
            let have = parse(give).unwrap();
            let want = Command::Version;
            assert_eq!(format!("{have:?}"), format!("{want:?}"));
        }

        #[test]
        fn error_on_output() {
            let give = vec![S("--error-on-output"), S("echo hello")].into_iter();
            let have = parse(give).unwrap();
            let want = Command::Run(RunArgs {
                runnables: vec![Runnable::Single(shell_executable("echo hello"))],
                error_on_output: true,
                stderr_to_stdout: false,
                show: Show::All,
            });
            assert_eq!(format!("{have:?}"), format!("{want:?}"));
        }

        #[test]
        fn stderr_to_stdout() {
            let give = vec![S("--stderr-to-stdout"), S("echo hello")].into_iter();
            let have = parse(give).unwrap();
            let want = Command::Run(RunArgs {
                runnables: vec![Runnable::Single(shell_executable("echo hello"))],
                error_on_output: false,
                stderr_to_stdout: true,
                show: Show::All,
            });
            assert_eq!(format!("{have:?}"), format!("{want:?}"));
        }

        #[test]
        fn error_on_output_with_show_failed() {
            let give = vec![S("--error-on-output"), S("--show=names"), S("echo hello")].into_iter();
            let have = parse(give).unwrap();
            let want = Command::Run(RunArgs {
                runnables: vec![Runnable::Single(shell_executable("echo hello"))],
                error_on_output: true,
                stderr_to_stdout: false,
                show: Show::Names,
            });
            assert_eq!(format!("{have:?}"), format!("{want:?}"));
        }
    }
}
