// std
use std::fmt::Display;
#[allow(deprecated, unused_imports)]
use std::ascii::AsciiExt;

// Internal
use INTERNAL_ERROR_MSG;
use INVALID_UTF8;
use args::{AnyArg, ArgMatcher, MatchedArg};
use args::settings::ArgSettings;
use errors::{Error, ErrorKind};
use errors::Result as ClapResult;
use app::settings::AppSettings as AS;
use app::parser::{ParseResult, Parser};
use fmt::{Colorizer, ColorizerOption};
use app::usage;

pub struct Validator<'a, 'b, 'z>(&'z mut Parser<'a, 'b>)
where
    'a: 'b,
    'b: 'z;

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

    pub fn validate(
        &mut self,
        needs_val_of: ParseResult<'a>,
        subcmd_name: Option<String>,
        matcher: &mut ArgMatcher<'a>,
    ) -> ClapResult<()> {
        debugln!("Validator::validate;");
        let mut reqs_validated = false;
        self.0.add_env(matcher)?;
        self.0.add_defaults(matcher)?;
        if let ParseResult::Opt(a) = needs_val_of {
            debugln!("Validator::validate: needs_val_of={:?}", a);
            let o = {
                self.0
                .opts
                .iter()
                .find(|o| o.b.name == a)
                .expect(INTERNAL_ERROR_MSG)
                .clone()
            };
            self.validate_required(matcher)?;
            reqs_validated = true;
            let should_err = if let Some(v) = matcher.0.args.get(&*o.b.name) {
                v.vals.is_empty() && !(o.v.min_vals.is_some() && o.v.min_vals.unwrap() == 0)
            } else {
                true
            };
            if should_err {
                return Err(Error::empty_value(
                    &o,
                    &*usage::create_error_usage(self.0, matcher, None),
                    self.0.color(),
                ));
            }
        }

        if matcher.is_empty() && matcher.subcommand_name().is_none()
            && self.0.is_set(AS::ArgRequiredElseHelp)
        {
            let mut out = vec![];
            self.0.write_help_err(&mut out)?;
            return Err(Error {
                message: String::from_utf8_lossy(&*out).into_owned(),
                kind: ErrorKind::MissingArgumentOrSubcommand,
                info: None,
            });
        }
        self.validate_blacklist(matcher)?;
        if !(self.0.is_set(AS::SubcommandsNegateReqs) && subcmd_name.is_some()) && !reqs_validated {
            self.validate_required(matcher)?;
        }
        self.validate_matched_args(matcher)?;
        matcher.usage(usage::create_usage_with_title(self.0, &[]));

        Ok(())
    }

    fn validate_arg_values<A>(
        &self,
        arg: &A,
        ma: &MatchedArg,
        matcher: &ArgMatcher<'a>,
    ) -> ClapResult<()>
    where
        A: AnyArg<'a, 'b> + Display,
    {
        debugln!("Validator::validate_arg_values: arg={:?}", arg.name());
        for val in &ma.vals {
            if self.0.is_set(AS::StrictUtf8) && val.to_str().is_none() {
                debugln!(
                    "Validator::validate_arg_values: invalid UTF-8 found in val {:?}",
                    val
                );
                return Err(Error::invalid_utf8(
                    &*usage::create_error_usage(self.0, matcher, None),
                    self.0.color(),
                ));
            }
            if let Some(p_vals) = arg.possible_vals() {
                debugln!("Validator::validate_arg_values: possible_vals={:?}", p_vals);
                let val_str = val.to_string_lossy();
                let ok = if arg.is_set(ArgSettings::CaseInsensitive) {
                    p_vals.iter().any(|pv| pv.eq_ignore_ascii_case(&*val_str))
                } else {
                    p_vals.contains(&&*val_str)
                };
                if !ok {
                    return Err(Error::invalid_value(
                        val_str,
                        p_vals,
                        arg,
                        &*usage::create_error_usage(self.0, matcher, None),
                        self.0.color(),
                    ));
                }
            }
            if !arg.is_set(ArgSettings::EmptyValues) && val.is_empty()
                && matcher.contains(&*arg.name())
            {
                debugln!("Validator::validate_arg_values: illegal empty val found");
                return Err(Error::empty_value(
                    arg,
                    &*usage::create_error_usage(self.0, matcher, None),
                    self.0.color(),
                ));
            }
            if let Some(vtor) = arg.validator() {
                debug!("Validator::validate_arg_values: checking validator...");
                if let Err(e) = vtor(val.to_string_lossy().into_owned()) {
                    sdebugln!("error");
                    return Err(Error::value_validation(Some(arg), e, self.0.color()));
                } else {
                    sdebugln!("good");
                }
            }
            if let Some(vtor) = arg.validator_os() {
                debug!("Validator::validate_arg_values: checking validator_os...");
                if let Err(e) = vtor(val) {
                    sdebugln!("error");
                    return Err(Error::value_validation(
                        Some(arg),
                        (*e).to_string_lossy().to_string(),
                        self.0.color(),
                    ));
                } else {
                    sdebugln!("good");
                }
            }
        }
        Ok(())
    }

    fn build_err(&self, name: &str, matcher: &ArgMatcher) -> ClapResult<()> {
        debugln!("build_err!: name={}", name);
        let mut c_with = find_from!(self.0, &name, blacklist, matcher);
        c_with = c_with.or(
        self.0.find_any_arg(name).map_or(None, |aa| aa.blacklist())
            .map_or(None,
                    |bl| bl.iter().find(|arg| matcher.contains(arg)))
            .map_or(None, |an| self.0.find_any_arg(an))
            .map_or(None, |aa| Some(format!("{}", aa)))
        );
        debugln!("build_err!: '{:?}' conflicts with '{}'", c_with, &name);
//        matcher.remove(&name);
        let usg = usage::create_error_usage(self.0, matcher, None);
        if let Some(f) = find_by_name!(self.0, name, flags, iter) {
            debugln!("build_err!: It was a flag...");
            Err(Error::argument_conflict(f, c_with, &*usg, self.0.color()))
        } else if let Some(o) = find_by_name!(self.0, name, opts, iter) {
            debugln!("build_err!: It was an option...");
            Err(Error::argument_conflict(o, c_with, &*usg, self.0.color()))
        } else {
            match find_by_name!(self.0, name, positionals, values) {
                Some(p) => {
                    debugln!("build_err!: It was a positional...");
                    Err(Error::argument_conflict(p, c_with, &*usg, self.0.color()))
                },
                None    => panic!(INTERNAL_ERROR_MSG)
            }
        }
    }

    fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> {
        debugln!("Validator::validate_blacklist;");
        let mut conflicts: Vec<&str> = vec![];
        for (&name, _) in matcher.iter() {
            debugln!("Validator::validate_blacklist:iter:{};", name);
            if let Some(grps) = self.0.groups_for_arg(name) {
                for grp in &grps {
                    if let Some(g) = self.0.groups.iter().find(|g| &g.name == grp) {
                        if !g.multiple {
                            for arg in &g.args {
                                if arg == &name {
                                    continue;
                                }
                                conflicts.push(arg);
                            }
                        }
                        if let Some(ref gc) = g.conflicts {
                            conflicts.extend(&*gc);
                        }
                    }
                }
            }
            if let Some(arg) = find_any_by_name!(self.0, name) {
                if let Some(bl) = arg.blacklist() {
                    for conf in bl {
                        if matcher.get(conf).is_some() {
                            conflicts.push(conf);
                        }
                    }
                }
            } else {
                debugln!("Validator::validate_blacklist:iter:{}:group;", name);
                let args = self.0.arg_names_in_group(name);
                for arg in &args {
                    debugln!("Validator::validate_blacklist:iter:{}:group:iter:{};", name, arg);
                    if let Some(bl) = find_any_by_name!(self.0, *arg).unwrap().blacklist() {
                        for conf in bl {
                            if matcher.get(conf).is_some() {
                                conflicts.push(conf);
                            }
                        }
                    }
                }
            }
        }

        for name in &conflicts {
            debugln!(
                "Validator::validate_blacklist:iter:{}: Checking blacklisted arg",
                name
            );
            let mut should_err = false;
            if self.0.groups.iter().any(|g| &g.name == name) {
                debugln!(
                    "Validator::validate_blacklist:iter:{}: groups contains it...",
                    name
                );
                for n in self.0.arg_names_in_group(name) {
                    debugln!(
                        "Validator::validate_blacklist:iter:{}:iter:{}: looking in group...",
                        name,
                        n
                    );
                    if matcher.contains(n) {
                        debugln!(
                            "Validator::validate_blacklist:iter:{}:iter:{}: matcher contains it...",
                            name,
                            n
                        );
                        return self.build_err(n, matcher);
                    }
                }
            } else if let Some(ma) = matcher.get(name) {
                debugln!(
                    "Validator::validate_blacklist:iter:{}: matcher contains it...",
                    name
                );
                should_err = ma.occurs > 0;
            }
            if should_err {
                return self.build_err(*name, matcher);
            }
        }
        Ok(())
    }

    fn validate_matched_args(&self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> {
        debugln!("Validator::validate_matched_args;");
        for (name, ma) in matcher.iter() {
            debugln!(
                "Validator::validate_matched_args:iter:{}: vals={:#?}",
                name,
                ma.vals
            );
            if let Some(opt) = find_by_name!(self.0, *name, opts, iter) {
                self.validate_arg_num_vals(opt, ma, matcher)?;
                self.validate_arg_values(opt, ma, matcher)?;
                self.validate_arg_requires(opt, ma, matcher)?;
                self.validate_arg_num_occurs(opt, ma, matcher)?;
            } else if let Some(flag) = find_by_name!(self.0, *name, flags, iter) {
                self.validate_arg_requires(flag, ma, matcher)?;
                self.validate_arg_num_occurs(flag, ma, matcher)?;
            } else if let Some(pos) = find_by_name!(self.0, *name, positionals, values) {
                self.validate_arg_num_vals(pos, ma, matcher)?;
                self.validate_arg_num_occurs(pos, ma, matcher)?;
                self.validate_arg_values(pos, ma, matcher)?;
                self.validate_arg_requires(pos, ma, matcher)?;
            } else {
                let grp = self.0
                    .groups
                    .iter()
                    .find(|g| &g.name == name)
                    .expect(INTERNAL_ERROR_MSG);
                if let Some(ref g_reqs) = grp.requires {
                    if g_reqs.iter().any(|&n| !matcher.contains(n)) {
                        return self.missing_required_error(matcher, None);
                    }
                }
            }
        }
        Ok(())
    }

    fn validate_arg_num_occurs<A>(
        &self,
        a: &A,
        ma: &MatchedArg,
        matcher: &ArgMatcher,
    ) -> ClapResult<()>
    where
        A: AnyArg<'a, 'b> + Display,
    {
        debugln!("Validator::validate_arg_num_occurs: a={};", a.name());
        if ma.occurs > 1 && !a.is_set(ArgSettings::Multiple) {
            // Not the first time, and we don't allow multiples
            return Err(Error::unexpected_multiple_usage(
                a,
                &*usage::create_error_usage(self.0, matcher, None),
                self.0.color(),
            ));
        }
        Ok(())
    }

    fn validate_arg_num_vals<A>(
        &self,
        a: &A,
        ma: &MatchedArg,
        matcher: &ArgMatcher,
    ) -> ClapResult<()>
    where
        A: AnyArg<'a, 'b> + Display,
    {
        debugln!("Validator::validate_arg_num_vals:{}", a.name());
        if let Some(num) = a.num_vals() {
            debugln!("Validator::validate_arg_num_vals: num_vals set...{}", num);
            let should_err = if a.is_set(ArgSettings::Multiple) {
                ((ma.vals.len() as u64) % num) != 0
            } else {
                num != (ma.vals.len() as u64)
            };
            if should_err {
                debugln!("Validator::validate_arg_num_vals: Sending error WrongNumberOfValues");
                return Err(Error::wrong_number_of_values(
                    a,
                    num,
                    if a.is_set(ArgSettings::Multiple) {
                        (ma.vals.len() % num as usize)
                    } else {
                        ma.vals.len()
                    },
                    if ma.vals.len() == 1
                        || (a.is_set(ArgSettings::Multiple) && (ma.vals.len() % num as usize) == 1)
                    {
                        "as"
                    } else {
                        "ere"
                    },
                    &*usage::create_error_usage(self.0, matcher, None),
                    self.0.color(),
                ));
            }
        }
        if let Some(num) = a.max_vals() {
            debugln!("Validator::validate_arg_num_vals: max_vals set...{}", num);
            if (ma.vals.len() as u64) > num {
                debugln!("Validator::validate_arg_num_vals: Sending error TooManyValues");
                return Err(Error::too_many_values(
                    ma.vals
                        .iter()
                        .last()
                        .expect(INTERNAL_ERROR_MSG)
                        .to_str()
                        .expect(INVALID_UTF8),
                    a,
                    &*usage::create_error_usage(self.0, matcher, None),
                    self.0.color(),
                ));
            }
        }
        let min_vals_zero = if let Some(num) = a.min_vals() {
            debugln!("Validator::validate_arg_num_vals: min_vals set: {}", num);
            if (ma.vals.len() as u64) < num && num != 0 {
                debugln!("Validator::validate_arg_num_vals: Sending error TooFewValues");
                return Err(Error::too_few_values(
                    a,
                    num,
                    ma.vals.len(),
                    &*usage::create_error_usage(self.0, matcher, None),
                    self.0.color(),
                ));
            }
            num == 0
        } else {
            false
        };
        // Issue 665 (https://github.com/kbknapp/clap-rs/issues/665)
        // Issue 1105 (https://github.com/kbknapp/clap-rs/issues/1105)
        if a.takes_value() && !min_vals_zero && ma.vals.is_empty() {
            return Err(Error::empty_value(
                a,
                &*usage::create_error_usage(self.0, matcher, None),
                self.0.color(),
            ));
        }
        Ok(())
    }

    fn validate_arg_requires<A>(
        &self,
        a: &A,
        ma: &MatchedArg,
        matcher: &ArgMatcher,
    ) -> ClapResult<()>
    where
        A: AnyArg<'a, 'b> + Display,
    {
        debugln!("Validator::validate_arg_requires:{};", a.name());
        if let Some(a_reqs) = a.requires() {
            for &(val, name) in a_reqs.iter().filter(|&&(val, _)| val.is_some()) {
                let missing_req =
                    |v| v == val.expect(INTERNAL_ERROR_MSG) && !matcher.contains(name);
                if ma.vals.iter().any(missing_req) {
                    return self.missing_required_error(matcher, None);
                }
            }
            for &(_, name) in a_reqs.iter().filter(|&&(val, _)| val.is_none()) {
                if !matcher.contains(name) {
                    return self.missing_required_error(matcher, Some(name));
                }
            }
        }
        Ok(())
    }

    fn validate_required(&mut self, matcher: &ArgMatcher) -> ClapResult<()> {
        debugln!(
            "Validator::validate_required: required={:?};",
            self.0.required
        );

        let mut should_err = false;
        let mut to_rem = Vec::new();
        for name in &self.0.required {
            debugln!("Validator::validate_required:iter:{}:", name);
            if matcher.contains(name) {
                continue;
            }
            if to_rem.contains(name) {
                continue;
            } else if let Some(a) = find_any_by_name!(self.0, *name) {
                if self.is_missing_required_ok(a, matcher) {
                    to_rem.push(a.name());
                    if let Some(reqs) = a.requires() {
                        for r in reqs
                            .iter()
                            .filter(|&&(val, _)| val.is_none())
                            .map(|&(_, name)| name)
                        {
                            to_rem.push(r);
                        }
                    }
                    continue;
                }
            }
            should_err = true;
            break;
        }
        if should_err {
            for r in &to_rem {
                'inner: for i in (0 .. self.0.required.len()).rev() {
                    if &self.0.required[i] == r {
                        self.0.required.swap_remove(i);
                        break 'inner;
                    }
                }
            }
            return self.missing_required_error(matcher, None);
        }

        // Validate the conditionally required args
        for &(a, v, r) in &self.0.r_ifs {
            if let Some(ma) = matcher.get(a) {
                if matcher.get(r).is_none() && ma.vals.iter().any(|val| val == v) {
                    return self.missing_required_error(matcher, Some(r));
                }
            }
        }
        Ok(())
    }

    fn validate_arg_conflicts(&self, a: &AnyArg, matcher: &ArgMatcher) -> Option<bool> {
        debugln!("Validator::validate_arg_conflicts: a={:?};", a.name());
        a.blacklist().map(|bl| {
            bl.iter().any(|conf| {
                matcher.contains(conf)
                    || self.0
                        .groups
                        .iter()
                        .find(|g| &g.name == conf)
                        .map_or(false, |g| g.args.iter().any(|arg| matcher.contains(arg)))
            })
        })
    }

    fn validate_required_unless(&self, a: &AnyArg, matcher: &ArgMatcher) -> Option<bool> {
        debugln!("Validator::validate_required_unless: a={:?};", a.name());
        macro_rules! check {
            ($how:ident, $_self:expr, $a:ident, $m:ident) => {{
                $a.required_unless().map(|ru| {
                    ru.iter().$how(|n| {
                        $m.contains(n) || {
                            if let Some(grp) = $_self.groups.iter().find(|g| &g.name == n) {
                                     grp.args.iter().any(|arg| $m.contains(arg))
                            } else {
                                false
                            }
                        }
                    })
                })
            }};
        }
        if a.is_set(ArgSettings::RequiredUnlessAll) {
            check!(all, self.0, a, matcher)
        } else {
            check!(any, self.0, a, matcher)
        }
    }

    fn missing_required_error(&self, matcher: &ArgMatcher, extra: Option<&str>) -> ClapResult<()> {
        debugln!("Validator::missing_required_error: extra={:?}", extra);
        let c = Colorizer::new(ColorizerOption {
            use_stderr: true,
            when: self.0.color(),
        });
        let mut reqs = self.0.required.iter().map(|&r| &*r).collect::<Vec<_>>();
        if let Some(r) = extra {
            reqs.push(r);
        }
        reqs.retain(|n| !matcher.contains(n));
        reqs.dedup();
        debugln!("Validator::missing_required_error: reqs={:#?}", reqs);
        let req_args =
            usage::get_required_usage_from(self.0, &reqs[..], Some(matcher), extra, true)
                .iter()
                .fold(String::new(), |acc, s| {
                    acc + &format!("\n    {}", c.error(s))[..]
                });
        debugln!(
            "Validator::missing_required_error: req_args={:#?}",
            req_args
        );
        Err(Error::missing_required_argument(
            &*req_args,
            &*usage::create_error_usage(self.0, matcher, extra),
            self.0.color(),
        ))
    }

    #[inline]
    fn is_missing_required_ok(&self, a: &AnyArg, matcher: &ArgMatcher) -> bool {
        debugln!("Validator::is_missing_required_ok: a={}", a.name());
        self.validate_arg_conflicts(a, matcher).unwrap_or(false)
            || self.validate_required_unless(a, matcher).unwrap_or(false)
    }
}
