use std::ffi::OsString;
use std::fmt;
use std::num::ParseIntError;

use glob;

use options::{flags, HelpString, VersionString};
use options::parser::{Arg, Flag, ParseError};


/// A **misfire** is a thing that can happen instead of listing files -- a
/// catch-all for anything outside the program’s normal execution.
#[derive(PartialEq, Debug)]
pub enum Misfire {

    /// The getopts crate didn’t like these Arguments.
    InvalidOptions(ParseError),

    /// The user supplied an illegal choice to an Argument.
    BadArgument(&'static Arg, OsString),

    /// The user supplied a set of options
    Unsupported(String),

    /// The user asked for help. This isn’t strictly an error, which is why
    /// this enum isn’t named Error!
    Help(HelpString),

    /// The user wanted the version number.
    Version(VersionString),

    /// An option was given twice or more in strict mode.
    Duplicate(Flag, Flag),

    /// Two options were given that conflict with one another.
    Conflict(&'static Arg, &'static Arg),

    /// An option was given that does nothing when another one either is or
    /// isn't present.
    Useless(&'static Arg, bool, &'static Arg),

    /// An option was given that does nothing when either of two other options
    /// are not present.
    Useless2(&'static Arg, &'static Arg, &'static Arg),

    /// A very specific edge case where --tree can’t be used with --all twice.
    TreeAllAll,

    /// A numeric option was given that failed to be parsed as a number.
    FailedParse(ParseIntError),

    /// A glob ignore was given that failed to be parsed as a pattern.
    FailedGlobPattern(String),
}

impl Misfire {

    /// The OS return code this misfire should signify.
    pub fn is_error(&self) -> bool {
        match *self {
            Misfire::Help(_)    => false,
            Misfire::Version(_) => false,
            _                   => true,
        }
    }
}

impl From<glob::PatternError> for Misfire {
    fn from(error: glob::PatternError) -> Misfire {
        Misfire::FailedGlobPattern(error.to_string())
    }
}

impl fmt::Display for Misfire {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use options::parser::TakesValue;
        use self::Misfire::*;

        match *self {
            BadArgument(ref arg, ref attempt) => {
                if let TakesValue::Necessary(Some(values)) = arg.takes_value {
                    write!(f, "Option {} has no {:?} setting ({})", arg, attempt, Choices(values))
                }
                else {
                    write!(f, "Option {} has no {:?} setting", arg, attempt)
                }
            },
            InvalidOptions(ref e)            => write!(f, "{}", e),
            Unsupported(ref e)               => write!(f, "{}", e),
            Help(ref text)                   => write!(f, "{}", text),
            Version(ref version)             => write!(f, "{}", version),
            Conflict(ref a, ref b)           => write!(f, "Option {} conflicts with option {}", a, b),
            Duplicate(ref a, ref b)          => if a == b { write!(f, "Flag {} was given twice", a) }
                                                     else { write!(f, "Flag {} conflicts with flag {}", a, b) },
            Useless(ref a, false, ref b)     => write!(f, "Option {} is useless without option {}", a, b),
            Useless(ref a, true, ref b)      => write!(f, "Option {} is useless given option {}", a, b),
            Useless2(ref a, ref b1, ref b2)  => write!(f, "Option {} is useless without options {} or {}", a, b1, b2),
            TreeAllAll                       => write!(f, "Option --tree is useless given --all --all"),
            FailedParse(ref e)               => write!(f, "Failed to parse number: {}", e),
            FailedGlobPattern(ref e)         => write!(f, "Failed to parse glob pattern: {}", e),
        }
    }
}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::ParseError::*;

        match *self {
            NeedsValue { ref flag, values: None }     => write!(f, "Flag {} needs a value", flag),
            NeedsValue { ref flag, values: Some(cs) } => write!(f, "Flag {} needs a value ({})", flag, Choices(cs)),
            ForbiddenValue { ref flag }               => write!(f, "Flag {} cannot take a value", flag),
            UnknownShortArgument { ref attempt }      => write!(f, "Unknown argument -{}", *attempt as char),
            UnknownArgument { ref attempt }           => write!(f, "Unknown argument --{}", attempt.to_string_lossy()),
        }
    }
}

impl Misfire {
    /// Try to second-guess what the user was trying to do, depending on what
    /// went wrong.
    pub fn suggestion(&self) -> Option<&'static str> {
        // ‘ls -lt’ and ‘ls -ltr’ are common combinations
        match *self {
            Misfire::BadArgument(ref time, ref r) if *time == &flags::TIME && r == "r" =>
                Some("To sort oldest files last, try \"--sort oldest\", or just \"-sold\""),
            Misfire::InvalidOptions(ParseError::NeedsValue { ref flag, .. }) if *flag == Flag::Short(b't') =>
                Some("To sort newest files last, try \"--sort newest\", or just \"-snew\""),
            _ => None
        }
    }
}


/// A list of legal choices for an argument-taking option.
#[derive(PartialEq, Debug)]
pub struct Choices(&'static [&'static str]);

impl fmt::Display for Choices {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "choices: {}", self.0.join(", "))
    }
}
