use crate::*;

use log::fatal;

use std::path::PathBuf;


pub struct NamedSwitchQuery<'a> {
    /// The borrowed switchboard instance.
    pub switchboard: &'a mut Switchboard,
    /// Long switch name.
    pub name: String,
    /// Short switch name.
    pub short: Option<char>,
    /// The default value if the switch hasn't been provided.
    pub default: Option<String>,
    /// The default value if the switch has been provided with no value.
    pub quick: Option<String>,
}

impl NamedSwitchQuery<'_> {
    pub fn short(mut self, short: char) -> Self {
        if !short.is_ascii_alphanumeric() {
            panic!("Invalid short name for argument: {short:?}");
        }
        self.short = Some(short);
        self
    }

    pub fn default(mut self, value: &str) -> Self {
        self.default = Some(value.to_string());
        self
    }

    pub fn quick(mut self, value: &str) -> Self {
        self.quick = Some(value.to_string());
        self
    }

    fn debug_name(&self) -> String {
        let mut debug_name = format!("--{}", self.name);
        if let Some(short) = self.short {
            debug_name.push_str(&format!(" (-{})", short));
        }
        debug_name
    }
}


pub struct PositionalSwitchQuery<'a> {
    /// The borrowed switchboard instance.
    pub switchboard: &'a mut Switchboard,
    /// The display name of this argument.
    pub name: String,
    /// The default value if the switch hasn't been provided.
    pub default: Option<String>,
}

impl PositionalSwitchQuery<'_> {
    pub fn default(mut self, value: &str) -> Self {
        self.default = Some(value.to_string());
        self
    }
}


macro_rules! as_number {
    ($type:ty, $name:expr, $error:ty) => {
        paste::paste! {
            fn [< as_ $type >](&mut self) -> $type {
                self.[< as_ $type _opt >]().unwrap_or_else(|| self.missing("number"))
            }
            fn [< as_ $type _opt>](&mut self) -> Option<$type> {
                self.get_value().map(|v| v.trim().parse().unwrap_or_else(
                    |e: $error| self.error(&v, $name, e.to_string())))
            }
        }
    };
}


pub trait SwitchQuery {
    fn get_name(&self) -> String;

    fn get_value(&mut self) -> Option<String>;

    fn error(&self, value: &str, ty: &str, err: String) -> ! {
        let name = self.get_name();
        fatal!("The {name} with value {value:?} could not be parsed as {ty}: {err}");
    }

    fn missing(&self, ty: &str) -> ! {
        let name = self.get_name();
        fatal!("The required {name} that takes a {ty} value was not provided");
    }

    fn as_bool(&mut self) -> bool {
        if let Some(value) = self.get_value() {
            match value.to_lowercase().as_str() {
                "y"|"yes"|"t"|"true" => true,
                "n"|"no" |"f"|"false" => false,
                _ => true,
            }
        } else {
            false
        }
    }

    fn as_path(&mut self) -> PathBuf {
        self.as_path_opt().unwrap_or_else(|| self.missing("path"))
    }
    fn as_path_opt(&mut self) -> Option<PathBuf> {
        if let Some(value) = self.get_value() {
            if !value.is_empty() {
                return Some(PathBuf::from(value));
            }
        }
        return None;
    }

    fn as_string(&mut self) -> String {
        self.as_string_opt().unwrap_or_else(|| self.missing("string"))
    }
    fn as_string_opt(&mut self) -> Option<String> {
        self.get_value()
    }

    as_number!{ f32,   "f32"  , std::num::ParseFloatError }
    as_number!{ f64,   "f64"  , std::num::ParseFloatError }
    as_number!{ u8,    "u8"   , std::num::ParseIntError   }
    as_number!{ u16,   "u16"  , std::num::ParseIntError   }
    as_number!{ u32,   "u32"  , std::num::ParseIntError   }
    as_number!{ u64,   "u64"  , std::num::ParseIntError   }
    as_number!{ usize, "usize", std::num::ParseIntError   }
    as_number!{ i8,    "i8"   , std::num::ParseIntError   }
    as_number!{ i16,   "i16"  , std::num::ParseIntError   }
    as_number!{ i32,   "i32"  , std::num::ParseIntError   }
    as_number!{ i64,   "i64"  , std::num::ParseIntError   }
    as_number!{ isize, "isize", std::num::ParseIntError   }
}


impl SwitchQuery for NamedSwitchQuery<'_> {
    fn get_name(&self) -> String {
        format!("argument {}", self.debug_name())
    }

    fn get_value(&mut self) -> Option<String> {
        // Find all matches first.
        let mut matches = Vec::new();
        for (i, switch) in self.switchboard.switches.iter().enumerate() {
            match &switch.0 {
                SwitchName::Short(other) => if let Some(short) = self.short {
                    if short == *other { matches.push(i) } }
                SwitchName::Long(other) => if self.name == *other {
                    matches.push(i); },
            };
        }
        match matches.len() {
            0 => self.default.clone(),
            1 => Some(
                self.switchboard.switches.remove(matches[0]).1
                    .or_else(|| self.quick.clone())
                    .unwrap_or_else(String::new)
            ),
            _ => fatal!("The argument {} was passed more than once", self.debug_name()),
        }
    }
}


impl SwitchQuery for PositionalSwitchQuery<'_> {
    fn get_name(&self) -> String {
        format!("positional argument {:?}", self.name)
    }

    fn get_value(&mut self) -> Option<String> {
        self.switchboard.positional.pop().or_else(|| self.default.clone())
    }
}