use crate::{Flag, FlagType, FlagValue};

/// `Context` type
///
/// This type is used only for `Action` arguments
pub struct Context {
    /// `Vec<String>` with flags and flag values ​​removed from command line arguments
    pub args: Vec<String>,
    /// `Vec` that stores flag name and flag value as tuple
    flags: Option<Vec<(String, Option<FlagValue>)>>,
}

impl Context {
    /// Create new instance of `Context`
    /// Parse processing using `Vec<String>` command line argument and `Vec<Flag>` as arguments
    ///
    /// Example
    ///
    /// ```
    /// use std::env;
    /// use seahorse::{Context, Flag, FlagType};
    ///
    /// let args: Vec<String> = env::args().collect();
    /// let flag = Flag::new("bool", "cli cmd [arg] --bool", FlagType::Bool);
    /// let context = Context::new(args, Some(vec![flag]));
    /// ```
    pub fn new(args: Vec<String>, flags: Option<Vec<Flag>>) -> Self {
        let mut v = Vec::new();
        let mut parsed_args = args.clone();
        let flags_val = match flags {
            Some(flags) => {
                for flag in flags {
                    if let Some(index) = flag.option_index(&parsed_args) {
                        parsed_args.remove(index);
                        if flag.flag_type != FlagType::Bool {
                            parsed_args.remove(index);
                        }
                    }
                    v.push((flag.name.to_string(), flag.value(&args)))
                }
                Some(v)
            }
            None => None,
        };

        Self {
            args: parsed_args,
            flags: flags_val,
        }
    }

    /// Get flag value
    fn option_flag_value(&self, name: &str) -> Option<&FlagValue> {
        self.flags
            .as_ref()
            .and_then(|flags| flags.iter().find(|flag| flag.0 == name))
            .and_then(|flag| flag.1.as_ref())
    }

    /// Get bool flag
    ///
    /// Example
    ///
    /// ```
    /// use std::env;
    /// use seahorse::{Context, Flag, FlagType};
    ///
    /// let args: Vec<String> = env::args().collect();
    /// let flag = Flag::new("bool", "cli cmd [arg] --bool", FlagType::Bool);
    /// let context = Context::new(args, Some(vec![flag]));
    ///
    /// if context.bool_flag("bool") {
    ///     println!("true");
    /// }
    /// ```
    pub fn bool_flag(&self, name: &str) -> bool {
        match self.option_flag_value(name) {
            Some(FlagValue::Bool(val)) => *val,
            _ => false,
        }
    }

    /// Get string flag
    ///
    /// Example
    ///
    /// ```
    /// use std::env;
    /// use seahorse::{Context, Flag, FlagType};
    ///
    /// let args: Vec<String> = env::args().collect();
    /// let flag = Flag::new("string", "cli cmd [arg] --string [string]", FlagType::String);
    /// let context = Context::new(args, Some(vec![flag]));
    ///
    /// match context.string_flag("string") {
    ///     Some(s) => println!("{}", s),
    ///     None => println!("Not found string...")
    /// }
    /// ```
    pub fn string_flag(&self, name: &str) -> Option<String> {
        match self.option_flag_value(name) {
            Some(FlagValue::String(val)) => Some(val.to_string()),
            _ => None,
        }
    }

    /// Get int flag
    ///
    /// Example
    ///
    /// ```
    /// use std::env;
    /// use seahorse::{Context, Flag, FlagType};
    ///
    /// let args: Vec<String> = env::args().collect();
    /// let flag = Flag::new("int", "cli cmd [arg] --int [int]", FlagType::Int);
    /// let context = Context::new(args, Some(vec![flag]));
    ///
    /// match context.int_flag("int") {
    ///     Some(i) => println!("{}", i),
    ///     None => println!("Not found int number...")
    /// }
    /// ```
    pub fn int_flag(&self, name: &str) -> Option<isize> {
        match self.option_flag_value(name) {
            Some(FlagValue::Int(val)) => Some(*val),
            _ => None,
        }
    }

    /// Get float flag
    ///
    /// Example
    ///
    /// ```
    /// use std::env;
    /// use seahorse::{Context, Flag, FlagType};
    ///
    /// let args: Vec<String> = env::args().collect();
    /// let flag = Flag::new("float", "cli cmd [arg] --float [float]", FlagType::Float);
    /// let context = Context::new(args, Some(vec![flag]));
    ///
    /// match context.float_flag("float") {
    ///     Some(f) => println!("{}", f),
    ///     None => println!("Not found float number...")
    /// }
    /// ```
    pub fn float_flag(&self, name: &str) -> Option<f64> {
        match self.option_flag_value(name) {
            Some(FlagValue::Float(val)) => Some(*val),
            _ => None,
        }
    }
}
#[cfg(test)]
mod tests {
    use crate::{Context, Flag, FlagType};

    #[test]
    fn context_test() {
        let args = vec![
            "cli".to_string(),
            "command".to_string(),
            "args".to_string(),
            "--bool".to_string(),
            "--string".to_string(),
            "test".to_string(),
            "--int".to_string(),
            "100".to_string(),
            "--float".to_string(),
            "1.23".to_string(),
        ];
        let flags = vec![
            Flag::new("bool", "", FlagType::Bool),
            Flag::new("string", "", FlagType::String),
            Flag::new("int", "", FlagType::Int),
            Flag::new("float", "", FlagType::Float),
        ];
        let context = Context::new(args, Some(flags));

        assert_eq!(true, context.bool_flag("bool"));

        match context.string_flag("string") {
            Some(val) => assert_eq!("test".to_string(), val),
            _ => assert!(false),
        }

        match context.int_flag("int") {
            Some(val) => assert_eq!(100, val),
            _ => assert!(false),
        }

        match context.float_flag("float") {
            Some(val) => assert_eq!(1.23, val),
            _ => assert!(false),
        }
    }
}
