// Std
use std::io::Write;

// Internal
use app::parser::Parser;
use args::OptBuilder;
use completions;

pub struct BashGen<'a, 'b>
where
    'a: 'b,
{
    p: &'b Parser<'a, 'b>,
}

impl<'a, 'b> BashGen<'a, 'b> {
    pub fn new(p: &'b Parser<'a, 'b>) -> Self { BashGen { p: p } }

    pub fn generate_to<W: Write>(&self, buf: &mut W) {
        w!(
            buf,
            format!(
                r#"_{name}() {{
    local i cur prev opts cmds
    COMPREPLY=()
    cur="${{COMP_WORDS[COMP_CWORD]}}"
    prev="${{COMP_WORDS[COMP_CWORD-1]}}"
    cmd=""
    opts=""

    for i in ${{COMP_WORDS[@]}}
    do
        case "${{i}}" in
            {name})
                cmd="{name}"
                ;;
            {subcmds}
            *)
                ;;
        esac
    done

    case "${{cmd}}" in
        {name})
            opts="{name_opts}"
            if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq 1 ]] ; then
                COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
                return 0
            fi
            case "${{prev}}" in
                {name_opts_details}
                *)
                    COMPREPLY=()
                    ;;
            esac
            COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
            return 0
            ;;
        {subcmd_details}
    esac
}}

complete -F _{name} -o bashdefault -o default {name}
"#,
                name = self.p.meta.bin_name.as_ref().unwrap(),
                name_opts = self.all_options_for_path(self.p.meta.bin_name.as_ref().unwrap()),
                name_opts_details =
                    self.option_details_for_path(self.p.meta.bin_name.as_ref().unwrap()),
                subcmds = self.all_subcommands(),
                subcmd_details = self.subcommand_details()
            ).as_bytes()
        );
    }

    fn all_subcommands(&self) -> String {
        debugln!("BashGen::all_subcommands;");
        let mut subcmds = String::new();
        let scs = completions::all_subcommand_names(self.p);

        for sc in &scs {
            subcmds = format!(
                r#"{}
            {name})
                cmd+="__{fn_name}"
                ;;"#,
                subcmds,
                name = sc,
                fn_name = sc.replace("-", "__")
            );
        }

        subcmds
    }

    fn subcommand_details(&self) -> String {
        debugln!("BashGen::subcommand_details;");
        let mut subcmd_dets = String::new();
        let mut scs = completions::get_all_subcommand_paths(self.p, true);
        scs.sort();
        scs.dedup();

        for sc in &scs {
            subcmd_dets = format!(
                r#"{}
        {subcmd})
            opts="{sc_opts}"
            if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq {level} ]] ; then
                COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
                return 0
            fi
            case "${{prev}}" in
                {opts_details}
                *)
                    COMPREPLY=()
                    ;;
            esac
            COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
            return 0
            ;;"#,
                subcmd_dets,
                subcmd = sc.replace("-", "__"),
                sc_opts = self.all_options_for_path(&*sc),
                level = sc.split("__").map(|_| 1).fold(0, |acc, n| acc + n),
                opts_details = self.option_details_for_path(&*sc)
            );
        }

        subcmd_dets
    }

    fn option_details_for_path(&self, path: &str) -> String {
        debugln!("BashGen::option_details_for_path: path={}", path);
        let mut p = self.p;
        for sc in path.split("__").skip(1) {
            debugln!("BashGen::option_details_for_path:iter: sc={}", sc);
            p = &find_subcmd!(p, sc).unwrap().p;
        }
        let mut opts = String::new();
        for o in p.opts() {
            if let Some(l) = o.s.long {
                opts = format!(
                    "{}
                --{})
                    COMPREPLY=({})
                    return 0
                    ;;",
                    opts,
                    l,
                    self.vals_for(o)
                );
            }
            if let Some(s) = o.s.short {
                opts = format!(
                    "{}
                    -{})
                    COMPREPLY=({})
                    return 0
                    ;;",
                    opts,
                    s,
                    self.vals_for(o)
                );
            }
        }
        opts
    }

    fn vals_for(&self, o: &OptBuilder) -> String {
        debugln!("BashGen::vals_for: o={}", o.b.name);
        use args::AnyArg;
        if let Some(vals) = o.possible_vals() {
            format!(r#"$(compgen -W "{}" -- "${{cur}}")"#, vals.join(" "))
        } else {
            String::from(r#"$(compgen -f "${cur}")"#)
        }
    }

    fn all_options_for_path(&self, path: &str) -> String {
        debugln!("BashGen::all_options_for_path: path={}", path);
        let mut p = self.p;
        for sc in path.split("__").skip(1) {
            debugln!("BashGen::all_options_for_path:iter: sc={}", sc);
            p = &find_subcmd!(p, sc).unwrap().p;
        }
        let mut opts = shorts!(p).fold(String::new(), |acc, s| format!("{} -{}", acc, s));
        opts = format!(
            "{} {}",
            opts,
            longs!(p).fold(String::new(), |acc, l| format!("{} --{}", acc, l))
        );
        opts = format!(
            "{} {}",
            opts,
            p.positionals
                .values()
                .fold(String::new(), |acc, p| format!("{} {}", acc, p))
        );
        opts = format!(
            "{} {}",
            opts,
            p.subcommands
                .iter()
                .fold(String::new(), |acc, s| format!("{} {}", acc, s.p.meta.name))
        );
        for sc in &p.subcommands {
            if let Some(ref aliases) = sc.p.meta.aliases {
                opts = format!(
                    "{} {}",
                    opts,
                    aliases
                        .iter()
                        .map(|&(n, _)| n)
                        .fold(String::new(), |acc, a| format!("{} {}", acc, a))
                );
            }
        }
        opts
    }
}
