use std::collections::{BTreeSet, HashMap};
use std::hash::Hasher;
use fnv::FnvHasher;
use regex::Regex;
use crate::errors::{Error, Result};
use crate::metrics::SEPARATOR_BYTE;
use crate::proto::LabelPair;
fn is_valid_metric_name(name: &str) -> bool {
    lazy_static! {
        static ref VALIDATOR: Regex =
            Regex::new("^[a-zA-Z_:][a-zA-Z0-9_:]*$").expect("Regex to be valid.");
    }
    VALIDATOR.is_match(name)
}
fn is_valid_label_name(name: &str) -> bool {
    lazy_static! {
        static ref VALIDATOR: Regex =
            Regex::new("^[a-zA-Z_][a-zA-Z0-9_]*$").expect("Regex to be valid.");
    }
    VALIDATOR.is_match(name)
}
#[derive(Clone, Debug)]
pub struct Desc {
    
    pub fq_name: String,
    
    pub help: String,
    
    
    pub const_label_pairs: Vec<LabelPair>,
    
    
    pub variable_labels: Vec<String>,
    
    
    
    pub id: u64,
    
    
    
    pub dim_hash: u64,
}
impl Desc {
    
    
    
    pub fn new(
        fq_name: String,
        help: String,
        variable_labels: Vec<String>,
        const_labels: HashMap<String, String>,
    ) -> Result<Desc> {
        let mut desc = Desc {
            fq_name: fq_name.clone(),
            help,
            const_label_pairs: Vec::with_capacity(const_labels.len()),
            variable_labels,
            id: 0,
            dim_hash: 0,
        };
        if desc.help.is_empty() {
            return Err(Error::Msg("empty help string".into()));
        }
        if !is_valid_metric_name(&desc.fq_name) {
            return Err(Error::Msg(format!(
                "'{}' is not a valid metric name",
                desc.fq_name
            )));
        }
        let mut label_values = Vec::with_capacity(const_labels.len() + 1);
        label_values.push(fq_name);
        let mut label_names = BTreeSet::new();
        for label_name in const_labels.keys() {
            if !is_valid_label_name(label_name) {
                return Err(Error::Msg(format!(
                    "'{}' is not a valid label name",
                    &label_name
                )));
            }
            if !label_names.insert(label_name.clone()) {
                return Err(Error::Msg(format!(
                    "duplicate const label name {}",
                    label_name
                )));
            }
        }
        
        for label_name in &label_names {
            label_values.push(const_labels.get(label_name).cloned().unwrap());
        }
        
        
        
        for label_name in &desc.variable_labels {
            if !is_valid_label_name(label_name) {
                return Err(Error::Msg(format!(
                    "'{}' is not a valid label name",
                    &label_name
                )));
            }
            if !label_names.insert(format!("${}", label_name)) {
                return Err(Error::Msg(format!(
                    "duplicate variable label name {}",
                    label_name
                )));
            }
        }
        let mut vh = FnvHasher::default();
        for val in &label_values {
            vh.write(val.as_bytes());
            vh.write_u8(SEPARATOR_BYTE);
        }
        desc.id = vh.finish();
        
        
        let mut lh = FnvHasher::default();
        lh.write(desc.help.as_bytes());
        lh.write_u8(SEPARATOR_BYTE);
        for label_name in &label_names {
            lh.write(label_name.as_bytes());
            lh.write_u8(SEPARATOR_BYTE);
        }
        desc.dim_hash = lh.finish();
        for (key, value) in const_labels {
            let mut label_pair = LabelPair::default();
            label_pair.set_name(key);
            label_pair.set_value(value);
            desc.const_label_pairs.push(label_pair);
        }
        desc.const_label_pairs.sort();
        Ok(desc)
    }
}
pub trait Describer {
    
    fn describe(&self) -> Result<Desc>;
}
#[cfg(test)]
mod tests {
    use std::collections::HashMap;
    use crate::desc::{is_valid_label_name, is_valid_metric_name, Desc};
    use crate::errors::Error;
    #[test]
    fn test_is_valid_metric_name() {
        let tbl = [
            (":", true),
            ("_", true),
            ("a", true),
            (":9", true),
            ("_9", true),
            ("a9", true),
            ("a_b_9_d:x_", true),
            ("9", false),
            ("9:", false),
            ("9_", false),
            ("9a", false),
            ("a-", false),
        ];
        for &(name, expected) in &tbl {
            assert_eq!(is_valid_metric_name(name), expected);
        }
    }
    #[test]
    fn test_is_valid_label_name() {
        let tbl = [
            ("_", true),
            ("a", true),
            ("_9", true),
            ("a9", true),
            ("a_b_9_dx_", true),
            (":", false),
            (":9", false),
            ("9", false),
            ("9:", false),
            ("9_", false),
            ("9a", false),
            ("a-", false),
            ("a_b_9_d:x_", false),
        ];
        for &(name, expected) in &tbl {
            assert_eq!(is_valid_label_name(name), expected);
        }
    }
    #[test]
    fn test_invalid_const_label_name() {
        for &name in &["-dash", "9gag", ":colon", "colon:", "has space"] {
            let res = Desc::new(
                "name".into(),
                "help".into(),
                vec![name.into()],
                HashMap::new(),
            )
            .err()
            .expect(format!("expected error for {}", name).as_ref());
            match res {
                Error::Msg(msg) => assert_eq!(msg, format!("'{}' is not a valid label name", name)),
                other => panic!(other),
            };
        }
    }
    #[test]
    fn test_invalid_variable_label_name() {
        for &name in &["-dash", "9gag", ":colon", "colon:", "has space"] {
            let mut labels = HashMap::new();
            labels.insert(name.into(), "value".into());
            let res = Desc::new("name".into(), "help".into(), vec![], labels)
                .err()
                .expect(format!("expected error for {}", name).as_ref());
            match res {
                Error::Msg(msg) => assert_eq!(msg, format!("'{}' is not a valid label name", name)),
                other => panic!(other),
            };
        }
    }
    #[test]
    fn test_invalid_metric_name() {
        for &name in &["-dash", "9gag", "has space"] {
            let res = Desc::new(name.into(), "help".into(), vec![], HashMap::new())
                .err()
                .expect(format!("expected error for {}", name).as_ref());
            match res {
                Error::Msg(msg) => {
                    assert_eq!(msg, format!("'{}' is not a valid metric name", name))
                }
                other => panic!(other),
            };
        }
    }
}