use super::types::{BinaryName, ErrBox};

pub struct CliArgs {
    pub sub_command: SubCommand,
}

#[derive(Debug, PartialEq)]
pub enum SubCommand {
    Resolve(ResolveCommand),
    Use(UseCommand),
    List,
    Install,
    InstallUrl(String),
    Uninstall(UninstallCommand),
    Version,
    Help(String),
}

#[derive(Debug, PartialEq)]
pub struct ResolveCommand {
    pub binary_name: String,
}

#[derive(Debug, PartialEq)]
pub struct UseCommand {
    pub binary_name: BinaryName,
    pub version: String,
}

#[derive(Debug, PartialEq)]
pub struct UninstallCommand {
    pub binary_name: BinaryName,
    pub version: String,
}

pub fn parse_args(args: Vec<String>) -> Result<CliArgs, ErrBox> {
    let mut cli_parser = create_cli_parser();
    let matches = match cli_parser.get_matches_from_safe_borrow(args) {
        Ok(result) => result,
        Err(err) => return err!("{}", err.to_string()),
    };

    let sub_command = if matches.is_present("resolve") {
        let resolve_matches = matches.subcommand_matches("resolve").unwrap();
        SubCommand::Resolve(ResolveCommand {
            binary_name: resolve_matches.value_of("binary_name").map(String::from).unwrap(),
        })
    } else if matches.is_present("version") {
        SubCommand::Version
    } else if matches.is_present("install") {
        let install_matches = matches.subcommand_matches("install").unwrap();
        if let Some(url) = install_matches.value_of("url").map(String::from) {
            SubCommand::InstallUrl(url)
        } else {
            SubCommand::Install
        }
    } else if matches.is_present("use") {
        let use_matches = matches.subcommand_matches("use").unwrap();
        let binary_name = parse_binary_name(use_matches.value_of("binary_name").map(String::from).unwrap());
        SubCommand::Use(UseCommand {
            binary_name,
            version: use_matches.value_of("version").map(String::from).unwrap(),
        })
    } else if matches.is_present("uninstall") {
        let uninstall_matches = matches.subcommand_matches("uninstall").unwrap();
        let binary_name = parse_binary_name(uninstall_matches.value_of("binary_name").map(String::from).unwrap());
        SubCommand::Uninstall(UninstallCommand {
            binary_name,
            version: uninstall_matches.value_of("version").map(String::from).unwrap(),
        })
    } else if matches.is_present("list") {
        SubCommand::List
    } else {
        SubCommand::Help({
            let mut text = Vec::new();
            cli_parser.write_help(&mut text).unwrap();
            String::from_utf8(text).unwrap()
        })
    };

    Ok(CliArgs { sub_command })
}

fn parse_binary_name(text: String) -> BinaryName {
    let index = text.find('/');
    if let Some(index) = index {
        let owner_name = text[0..index].to_string();
        let name = text[index + 1..].to_string();
        BinaryName {
            owner: Some(owner_name),
            name,
        }
    } else {
        BinaryName {
            owner: None,
            name: text,
        }
    }
}

fn create_cli_parser<'a, 'b>() -> clap::App<'a, 'b> {
    use clap::{App, AppSettings, Arg, SubCommand};
    App::new("bvm")
        .setting(AppSettings::UnifiedHelpMessage)
        .setting(AppSettings::DisableHelpFlags)
        .setting(AppSettings::DisableHelpSubcommand)
        .setting(AppSettings::DeriveDisplayOrder)
        .bin_name("bvm")
        .version(env!("CARGO_PKG_VERSION"))
        .author("Copyright 2020 by David Sherret")
        .about("Runs versions of specific binaries based on the current working directory.")
        .usage("bvm <SUBCOMMAND> [OPTIONS]")
        .template(
            r#"{bin} {version}
{author}

{about}

USAGE:
    {usage}

SUBCOMMANDS:
{subcommands}

OPTIONS:
{unified}

ARGS:
{positionals}

{after-help}
"#,
        )
        .after_help(r#"TODO: Will fill in this info later..."#)
        .subcommand(
            SubCommand::with_name("resolve")
                .about("Outputs the binary path according to the current working directory.")
                .arg(
                    Arg::with_name("binary_name")
                        .help("The binary name to resolve.")
                        .takes_value(true)
                        .required(true),
                ),
        )
        .subcommand(
            SubCommand::with_name("install")
                .about("Installs the binaries for the current configuration file.")
                .arg(
                    Arg::with_name("url")
                        .help("The url of the binary manifest to install.")
                        .takes_value(true),
                ),
        )
        .subcommand(
            SubCommand::with_name("uninstall")
                .about("Uninstalls the specified binary version.")
                .arg(
                    Arg::with_name("binary_name")
                        .help("The binary name.")
                        .takes_value(true)
                        .required(true),
                )
                .arg(
                    Arg::with_name("version")
                        .help("The version of the binary to uninstall.")
                        .takes_value(true)
                        .required(true),
                ),
        )
        .subcommand(
            SubCommand::with_name("use")
                .about("Select a different version to use globally of a binary.")
                .arg(
                    Arg::with_name("binary_name")
                        .help("The binary name.")
                        .takes_value(true)
                        .required(true),
                )
                .arg(
                    Arg::with_name("version")
                        .help("The version of the binary to use or 'path' to use the binary on the path.")
                        .takes_value(true)
                        .required(true),
                ),
        )
        .subcommand(SubCommand::with_name("list").about("Output a list of installed binary versions."))
        .arg(
            Arg::with_name("help")
                .long("help")
                .short("h")
                .hidden(true)
                .takes_value(false),
        )
        .arg(
            Arg::with_name("version")
                .short("v")
                .long("version")
                .help("Prints the version.")
                .takes_value(false),
        )
}