use std::cmp::Ordering;
use std::process;
#[macro_use]
mod macros;
mod helper;
pub use crate::helper::{safe_run, safe_run_with_state};
pub struct Resource {
    state: Option<State>,
    metrics: Vec<Box<dyn ResourceMetric>>,
    description: Option<String>,
    name: Option<String>,
}
impl Resource {
    
    
    
    
    
    pub fn new(state: Option<State>, description: Option<&str>) -> Resource {
        Resource {
            state,
            metrics: Vec::new(),
            description: description.map(|d| d.to_owned()),
            name: None,
        }
    }
    
    pub fn push<M>(&mut self, metric: M)
    where
        M: 'static + ResourceMetric,
    {
        self.metrics.push(Box::new(metric))
    }
    
    pub fn metrics(&self) -> &[Box<dyn ResourceMetric>] {
        &self.metrics
    }
    
    
    pub fn set_state(&mut self, state: State) {
        self.state = Some(state)
    }
    
    pub fn set_name(&mut self, name: &str) {
        self.name = Some(name.to_owned())
    }
    
    
    
    
    pub fn to_nagios_string(&self) -> String {
        let mut s = String::new();
        if let Some(ref name) = self.name {
            s.push_str(&format!("{} ", name))
        }
        s.push_str(&self.get_state().to_string());
        if let Some(ref description) = self.description {
            s.push_str(&format!(": {}", description));
        }
        if self.metrics.len() > 0 {
            s.push_str(" |");
            for metric in self.metrics.iter() {
                s.push_str(&format!(" {}", metric.perf_string()));
            }
        }
        s
    }
    
    
    
    
    pub fn get_state(&self) -> State {
        let mut state = State::Unknown;
        if let Some(ref st) = self.state {
            state = st.clone()
        } else {
            for metric in self.metrics.iter() {
                if let Some(st) = metric.state() {
                    if state < st {
                        state = st;
                    }
                }
            }
        }
        state
    }
    
    pub fn get_description(&self) -> Option<&String> {
        self.description.as_ref()
    }
    
    pub fn set_description(&mut self, description: &str) {
        self.description = Some(description.to_owned());
    }
    
    pub fn exit_code(&self) -> i32 {
        self.get_state().exit_code()
    }
    
    pub fn print_and_exit(&self) {
        println!("{}", self.to_nagios_string());
        process::exit(self.exit_code());
    }
}
impl Default for Resource {
    fn default() -> Self {
        Resource::new(None, None)
    }
}
pub trait ResourceMetric {
    fn perf_string(&self) -> String;
    fn name(&self) -> &str;
    fn state(&self) -> Option<State>;
}
impl<T, O> ResourceMetric for T
where
    O: ToPerfString,
    T: Metric<Output = O> + ToPerfString,
{
    fn perf_string(&self) -> String {
        self.to_perf_string()
    }
    fn name(&self) -> &str {
        self.name()
    }
    fn state(&self) -> Option<State> {
        self.state()
    }
}
#[derive(Clone, Debug, PartialEq)]
pub enum State {
    Ok,
    Warning,
    Critical,
    Unknown,
}
impl State {
    
    pub fn exit_code(&self) -> i32 {
        match self {
            &State::Ok => 0,
            &State::Warning => 1,
            &State::Critical => 2,
            &State::Unknown => 3,
        }
    }
}
impl ToString for State {
    fn to_string(&self) -> String {
        match self {
            State::Ok => "OK".to_owned(),
            State::Warning => "WARNING".to_owned(),
            State::Critical => "CRITICAL".to_owned(),
            State::Unknown => "UNKNOWN".to_owned(),
        }
    }
}
impl PartialOrd for State {
    fn partial_cmp(&self, other: &State) -> Option<Ordering> {
        let f = |state| match state {
            &State::Unknown => 0,
            &State::Ok => 1,
            &State::Warning => 2,
            &State::Critical => 3,
        };
        f(self).partial_cmp(&f(other))
    }
}
pub trait ToPerfString {
    fn to_perf_string(&self) -> String;
}
impl_to_perf_string_on_to_string!(bool, usize);
impl_to_perf_string_on_to_string!(u8, u16, u32, u64, u128);
impl_to_perf_string_on_to_string!(i8, i16, i32, i64, i128);
impl_to_perf_string_on_to_string!(f32, f64);
impl_to_perf_string_on_to_string!(String);
impl<'a> ToPerfString for &'a str {
    fn to_perf_string(&self) -> String {
        self.to_string()
    }
}
impl<T, O> ToPerfString for T
where
    O: ToPerfString,
    T: Metric<Output = O>,
{
    fn to_perf_string(&self) -> String {
        
        let name = self.name().replace('=', "_");
        
        let name = name.replace('\'', "''");
        
        let name = if name.contains(' ') {
            format!("'{}'", self.name())
        } else {
            name.to_string()
        };
        metric_string!(
            name,
            format!(
                "{}{}",
                self.value().to_perf_string(),
                self.unit_of_measurement().to_string()
            ),
            
            self.warning(),
            self.critical(),
            self.min(),
            self.max()
        )
    }
}
impl<T> ToPerfString for Option<T>
where
    T: ToPerfString,
{
    fn to_perf_string(&self) -> String {
        match self {
            Some(ref s) => s.to_perf_string(),
            None => String::new(),
        }
    }
}
#[derive(Clone)]
pub enum Unit {
    None,
    Seconds,
    Milliseconds,
    Microseconds,
    Percentage,
    Bytes,
    KiloBytes,
    MegaBytes,
    TeraBytes,
    Counter,
    Other(String),
}
impl ToString for Unit {
    fn to_string(&self) -> String {
        match self {
            &Unit::None => "".to_owned(),
            &Unit::Seconds => "s".to_owned(),
            &Unit::Milliseconds => "ms".to_owned(),
            &Unit::Microseconds => "us".to_owned(),
            &Unit::Percentage => "%".to_owned(),
            &Unit::Bytes => "B".to_owned(),
            &Unit::KiloBytes => "KB".to_owned(),
            &Unit::MegaBytes => "MB".to_owned(),
            &Unit::TeraBytes => "TB".to_owned(),
            &Unit::Counter => "c".to_owned(),
            &Unit::Other(ref str) => str.to_owned(),
        }
    }
}
pub trait Metric {
    type Output: ToPerfString;
    fn name(&self) -> &str;
    fn state(&self) -> Option<State>;
    fn value(&self) -> Self::Output;
    fn warning(&self) -> Option<Self::Output>;
    fn critical(&self) -> Option<Self::Output>;
    fn min(&self) -> Option<Self::Output>;
    fn max(&self) -> Option<Self::Output>;
    fn unit_of_measurement(&self) -> &Unit;
}
pub struct PartialOrdMetric<T>
where
    T: PartialOrd + ToPerfString + Clone,
{
    name: String,
    value: T,
    warning: Option<T>,
    critical: Option<T>,
    min: Option<T>,
    max: Option<T>,
    lower_is_critical: bool,
    unit_of_measurement: Unit,
}
impl<T> PartialOrdMetric<T>
where
    T: PartialOrd + ToPerfString + Clone,
{
    
    
    
    pub fn new(
        name: &str,
        value: T,
        warning: Option<T>,
        critical: Option<T>,
        min: Option<T>,
        max: Option<T>,
        lower_is_critical: bool,
    ) -> Self {
        #[cfg(debug_assertions)]
        {
            if warning.is_some() && critical.is_some() {
                let warning = warning.clone().unwrap();
                let critical = critical.clone().unwrap();
                if lower_is_critical && warning < critical {
                    panic!("lower_is_critical is set to true while warning is lower than critical, this is not correct");
                } else if !lower_is_critical && warning > critical {
                    panic!("lower_is_critical is set to false while warning is lower than critical, this is not correct");
                }
            }
            if min.is_some() && max.is_some() {
                let min = min.clone().unwrap();
                let max = max.clone().unwrap();
                assert!(min < max, "minimum value is not smaller than maximum value")
            }
        }
        PartialOrdMetric {
            name: name.to_owned(),
            value: value.clone(),
            warning: warning.map(|w| w.clone()),
            critical: critical.map(|c| c.clone()),
            min: min.map(|m| m.clone()),
            max: max.map(|m| m.clone()),
            lower_is_critical,
            unit_of_measurement: Unit::None,
        }
    }
    pub fn set_unit_of_measurement(&mut self, unit_of_measurement: Unit) {
        self.unit_of_measurement = unit_of_measurement
    }
}
impl<T> Metric for PartialOrdMetric<T>
where
    T: PartialOrd + ToPerfString + Clone,
{
    type Output = T;
    fn name(&self) -> &str {
        &self.name
    }
    fn state(&self) -> Option<State> {
        if let Some(ref critical) = self.critical {
            if self.lower_is_critical {
                if &self.value <= critical {
                    return Some(State::Critical);
                }
            } else {
                if &self.value >= critical {
                    return Some(State::Critical);
                }
            }
        }
        if let Some(ref warning) = self.warning {
            if self.lower_is_critical {
                if &self.value <= warning {
                    return Some(State::Warning);
                }
            } else {
                if &self.value >= warning {
                    return Some(State::Warning);
                }
            }
        }
        Some(State::Ok)
    }
    fn value(&self) -> <Self as Metric>::Output {
        self.value.clone()
    }
    fn warning(&self) -> Option<<Self as Metric>::Output> {
        self.warning.clone()
    }
    fn critical(&self) -> Option<<Self as Metric>::Output> {
        self.critical.clone()
    }
    fn min(&self) -> Option<<Self as Metric>::Output> {
        self.min.clone()
    }
    fn max(&self) -> Option<<Self as Metric>::Output> {
        self.max.clone()
    }
    fn unit_of_measurement(&self) -> &Unit {
        &self.unit_of_measurement
    }
}
#[derive(Clone)]
pub struct SimpleMetric<T>
where
    T: ToPerfString + Clone,
{
    name: String,
    state: Option<State>,
    value: T,
    warning: Option<T>,
    critical: Option<T>,
    min: Option<T>,
    max: Option<T>,
    unit_of_measurement: Unit,
}
impl<T> SimpleMetric<T>
where
    T: ToPerfString + Clone,
{
    pub fn new(
        name: &str,
        state: Option<State>,
        value: T,
        warning: Option<T>,
        critical: Option<T>,
        min: Option<T>,
        max: Option<T>,
    ) -> Self {
        SimpleMetric {
            name: name.to_owned(),
            state,
            value,
            warning,
            critical,
            min,
            max,
            unit_of_measurement: Unit::None,
        }
    }
    pub fn set_unit_of_measurement(&mut self, unit_of_measurement: Unit) {
        self.unit_of_measurement = unit_of_measurement
    }
}
impl<T> Metric for SimpleMetric<T>
where
    T: ToPerfString + Clone,
{
    type Output = T;
    fn name(&self) -> &str {
        &self.name
    }
    fn state(&self) -> Option<State> {
        self.state.clone()
    }
    fn value(&self) -> <Self as Metric>::Output {
        self.value.clone()
    }
    fn warning(&self) -> Option<<Self as Metric>::Output> {
        self.warning.clone()
    }
    fn critical(&self) -> Option<<Self as Metric>::Output> {
        self.critical.clone()
    }
    fn min(&self) -> Option<<Self as Metric>::Output> {
        self.min.clone()
    }
    fn max(&self) -> Option<<Self as Metric>::Output> {
        self.max.clone()
    }
    fn unit_of_measurement(&self) -> &Unit {
        &self.unit_of_measurement
    }
}
#[cfg(test)]
mod tests {
    use crate::{Metric, PartialOrdMetric, Resource, SimpleMetric, State, ToPerfString, Unit};
    #[test]
    fn test_partial_ord_metric() {
        let metric = PartialOrdMetric::new("test", 12, None, None, None, None, false);
        assert_eq!(metric.name(), "test");
        assert_eq!(metric.state(), Some(State::Ok));
        assert_eq!(metric.value(), 12);
        assert_eq!(metric.warning(), None);
        assert_eq!(metric.critical(), None);
        
        let metric = PartialOrdMetric::new("test", 12, Some(15), Some(30), None, None, false);
        assert_eq!(metric.state(), Some(State::Ok));
        assert_eq!(metric.value(), 12);
        assert_eq!(metric.warning(), Some(15));
        assert_eq!(metric.critical(), Some(30));
        let metric = PartialOrdMetric::new("test", 15, Some(15), Some(30), None, None, false);
        assert_eq!(metric.state(), Some(State::Warning));
        assert_eq!(metric.value(), 15);
        let metric = PartialOrdMetric::new("test", 18, Some(15), Some(30), None, None, false);
        assert_eq!(metric.state(), Some(State::Warning));
        assert_eq!(metric.value(), 18);
        let metric = PartialOrdMetric::new("test", 30, Some(15), Some(30), None, None, false);
        assert_eq!(metric.state(), Some(State::Critical));
        assert_eq!(metric.value(), 30);
        let metric = PartialOrdMetric::new("test", 35, Some(15), Some(30), None, None, false);
        assert_eq!(metric.state(), Some(State::Critical));
        assert_eq!(metric.value(), 35);
        
        let metric = PartialOrdMetric::new("test", 35, Some(30), Some(15), None, None, true);
        assert_eq!(metric.state(), Some(State::Ok));
        assert_eq!(metric.value(), 35);
        assert_eq!(metric.warning(), Some(30));
        assert_eq!(metric.critical(), Some(15));
        let metric = PartialOrdMetric::new("test", 30, Some(30), Some(15), None, None, true);
        assert_eq!(metric.state(), Some(State::Warning));
        assert_eq!(metric.value(), 30);
        let metric = PartialOrdMetric::new("test", 20, Some(30), Some(15), None, None, true);
        assert_eq!(metric.state(), Some(State::Warning));
        assert_eq!(metric.value(), 20);
        let metric = PartialOrdMetric::new("test", 15, Some(30), Some(15), None, None, true);
        assert_eq!(metric.state(), Some(State::Critical));
        assert_eq!(metric.value(), 15);
        let metric = PartialOrdMetric::new("test", 10, Some(30), Some(15), None, None, true);
        assert_eq!(metric.state(), Some(State::Critical));
        assert_eq!(metric.value(), 10);
    }
    #[test]
    fn test_simple_metric() {
        let metric = SimpleMetric::new("test", Some(State::Ok), 12, None, None, None, None);
        assert_eq!(metric.state(), Some(State::Ok));
        assert_eq!(metric.value(), 12);
        assert_eq!(metric.warning(), None);
        assert_eq!(metric.critical(), None);
        let metric = SimpleMetric::new(
            "test",
            Some(State::Unknown),
            22,
            Some(15),
            Some(30),
            None,
            None,
        );
        assert_eq!(metric.state(), Some(State::Unknown));
        assert_eq!(metric.value(), 22);
        assert_eq!(metric.warning(), Some(15));
        assert_eq!(metric.critical(), Some(30));
        let metric = SimpleMetric::new("test", Some(State::Ok), "test", None, None, None, None);
        assert_eq!(metric.state(), Some(State::Ok));
        assert_eq!(metric.value(), "test");
        assert_eq!(metric.warning(), None);
        assert_eq!(metric.critical(), None);
    }
    #[test]
    fn test_simple_metric_unit_of_measurement() {
        let mut metric = SimpleMetric::new("foo", None, 12, None, None, None, None);
        metric.set_unit_of_measurement(Unit::Microseconds);
        assert_eq!(&metric.to_perf_string(), "foo=12us");
        metric.set_unit_of_measurement(Unit::Other("bar".to_owned()));
        assert_eq!(&metric.to_perf_string(), "foo=12bar");
    }
    #[test]
    fn test_resource() {
        let m1 = SimpleMetric::new("test", Some(State::Ok), 12, None, None, None, None);
        let m2 = SimpleMetric::new("other", None, true, None, None, None, None);
        let resource = resource![m1, m2];
        assert_eq!(&resource.to_nagios_string(), "OK | test=12 other=true");
        let m1 = SimpleMetric::new("test", Some(State::Ok), 12, Some(14), None, Some(0), None);
        let m2 = SimpleMetric::new("other", None, true, None, None, None, None);
        let resource = resource![m1, m2];
        assert_eq!(
            &resource.to_nagios_string(),
            "OK | test=12;14;;0 other=true"
        );
        let m1 = SimpleMetric::new("test", Some(State::Ok), 12, Some(14), None, Some(0), None);
        let m2 = SimpleMetric::new("other", None, true, None, None, None, None);
        let mut resource: Resource = resource![m1, m2];
        resource.set_description("A test description");
        assert_eq!(
            &resource.to_nagios_string(),
            "OK: A test description | test=12;14;;0 other=true"
        );
        let test_data = [
            ("test", "OK | test=0"),
            ("test=a", "OK | test_a=0"),
            ("te'st", "OK | te''st=0"),
            ("te st", "OK | 'te st'=0"),
        ];
        for (label, expected_string) in &test_data {
            let metric = SimpleMetric::new(label, Some(State::Ok), 0, None, None, None, None);
            let resource: Resource = resource![metric];
            assert_eq!(&resource.to_nagios_string(), expected_string,);
        }
    }
    #[test]
    fn test_resource_with_name() {
        let mut resource = Resource::new(Some(State::Ok), None);
        resource.set_name("foo");
        assert_eq!(&resource.to_nagios_string(), "foo OK")
    }
    #[test]
    fn test_state() {
        assert_eq!(State::Ok.exit_code(), 0);
        assert_eq!(State::Warning.exit_code(), 1);
        assert_eq!(State::Critical.exit_code(), 2);
        assert_eq!(State::Unknown.exit_code(), 3);
        assert_eq!(&State::Ok.to_string(), "OK");
        assert_eq!(&State::Warning.to_string(), "WARNING");
        assert_eq!(&State::Critical.to_string(), "CRITICAL");
        assert_eq!(&State::Unknown.to_string(), "UNKNOWN");
    }
}