use crate::models::*;
use itertools::Itertools;
use serde::{Serialize, Serializer};
use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
use std::net::IpAddr;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElemType {
    ANNOUNCE,
    WITHDRAW,
}
impl Serialize for ElemType {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(match self {
            ElemType::ANNOUNCE => "announce",
            ElemType::WITHDRAW => "withdraw",
        })
    }
}
impl ElemType {
    pub fn is_announce(&self) -> bool {
        match self {
            ElemType::ANNOUNCE => true,
            ElemType::WITHDRAW => false,
        }
    }
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct BgpElem {
    pub timestamp: f64,
    #[serde(rename = "type")]
    pub elem_type: ElemType,
    pub peer_ip: IpAddr,
    pub peer_asn: Asn,
    pub prefix: NetworkPrefix,
    pub next_hop: Option<IpAddr>,
    pub as_path: Option<AsPath>,
    pub origin_asns: Option<Vec<Asn>>,
    pub origin: Option<Origin>,
    pub local_pref: Option<u32>,
    pub med: Option<u32>,
    pub communities: Option<Vec<MetaCommunity>>,
    pub atomic: Option<AtomicAggregate>,
    pub aggr_asn: Option<Asn>,
    pub aggr_ip: Option<IpAddr>,
    pub only_to_customer: Option<u32>,
}
impl Eq for BgpElem {}
impl PartialOrd<Self> for BgpElem {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}
impl Ord for BgpElem {
    fn cmp(&self, other: &Self) -> Ordering {
        self.timestamp
            .partial_cmp(&other.timestamp)
            .unwrap()
            .then_with(|| self.peer_ip.cmp(&other.peer_ip))
    }
}
#[derive(Debug, Clone, Serialize)]
pub struct BgpElemRef<'a> {
    pub timestamp: &'a f64,
    pub elem_type: &'a ElemType,
    pub peer_ip: &'a IpAddr,
    pub peer_asn: &'a Asn,
    pub prefix: &'a NetworkPrefix,
    pub next_hop: &'a Option<IpAddr>,
    pub as_path: &'a Option<AsPath>,
    pub origin_asns: &'a Option<Vec<Asn>>,
    pub origin: &'a Option<Origin>,
    pub local_pref: &'a Option<u32>,
    pub med: &'a Option<u32>,
    pub communities: &'a Option<Vec<MetaCommunity>>,
    pub atomic: &'a Option<AtomicAggregate>,
    pub aggr_asn: &'a Option<Asn>,
    pub aggr_ip: &'a Option<IpAddr>,
}
impl Default for BgpElem {
    fn default() -> Self {
        BgpElem {
            timestamp: 0.0,
            elem_type: ElemType::ANNOUNCE,
            peer_ip: IpAddr::from_str("0.0.0.0").unwrap(),
            peer_asn: 0.into(),
            prefix: NetworkPrefix::from_str("0.0.0.0/0").unwrap(),
            next_hop: None,
            as_path: None,
            origin_asns: None,
            origin: None,
            local_pref: None,
            med: None,
            communities: None,
            atomic: None,
            aggr_asn: None,
            aggr_ip: None,
            only_to_customer: None,
        }
    }
}
macro_rules! option_to_string {
    ($a:expr) => {
        if let Some(v) = $a {
            v.to_string()
        } else {
            String::new()
        }
    };
}
#[inline(always)]
pub fn option_to_string_communities(o: &Option<Vec<MetaCommunity>>) -> String {
    if let Some(v) = o {
        v.iter().join(" ")
    } else {
        String::new()
    }
}
impl Display for BgpElem {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let t = match self.elem_type {
            ElemType::ANNOUNCE => "A",
            ElemType::WITHDRAW => "W",
        };
        let format = format!(
            "{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}",
            t,
            &self.timestamp,
            &self.peer_ip,
            &self.peer_asn,
            &self.prefix,
            option_to_string!(&self.as_path),
            option_to_string!(&self.origin),
            option_to_string!(&self.next_hop),
            option_to_string!(&self.local_pref),
            option_to_string!(&self.med),
            option_to_string_communities(&self.communities),
            option_to_string!(&self.atomic),
            option_to_string!(&self.aggr_asn),
            option_to_string!(&self.aggr_ip),
        );
        write!(f, "{}", format)
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use std::default::Default;
    use std::str::FromStr;
    #[test]
    fn test_default() {
        let elem = BgpElem {
            timestamp: 0.0,
            elem_type: ElemType::ANNOUNCE,
            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
            peer_asn: 0.into(),
            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
            ..Default::default()
        };
        println!("{}", serde_json::json!(elem).to_string());
    }
    #[test]
    fn test_sorting() {
        let elem1 = BgpElem {
            timestamp: 1.1,
            elem_type: ElemType::ANNOUNCE,
            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
            peer_asn: 0.into(),
            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
            ..Default::default()
        };
        let elem2 = BgpElem {
            timestamp: 1.2,
            elem_type: ElemType::ANNOUNCE,
            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
            peer_asn: 0.into(),
            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
            ..Default::default()
        };
        let elem3 = BgpElem {
            timestamp: 1.2,
            elem_type: ElemType::ANNOUNCE,
            peer_ip: IpAddr::from_str("192.168.1.2").unwrap(),
            peer_asn: 0.into(),
            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
            ..Default::default()
        };
        assert_eq!(elem1 < elem2, true);
        assert_eq!(elem2 < elem3, true);
    }
}