use super::bundle::*;
use core::convert::From;
use core::fmt;
use serde::de::{SeqAccess, Visitor};
use crate::helpers::Url;
use serde::{de, Deserialize, Deserializer, Serialize};
pub const ENDPOINT_URI_SCHEME_DTN: u8 = 1;
pub const ENDPOINT_URI_SCHEME_IPN: u8 = 2;
pub const DTN_NONE: EndpointID = EndpointID::DtnNone(ENDPOINT_URI_SCHEME_DTN, 0);
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct IpnAddress(pub u64, pub u64);
impl fmt::Display for IpnAddress {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}.{}", self.0, self.1)
    }
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash)]
#[serde(untagged)]
pub enum EndpointID {
    Dtn(u8, String),     DtnNone(u8, u8),
    Ipn(u8, IpnAddress),
}
impl<'de> Deserialize<'de> for EndpointID {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct EndpointIDVisitor;
        impl<'de> Visitor<'de> for EndpointIDVisitor {
            type Value = EndpointID;
            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("EndpointID")
            }
            fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
            where
                V: SeqAccess<'de>,
            {
                let eid_type: u8 = seq
                    .next_element()?
                    .ok_or_else(|| de::Error::invalid_length(0, &self))?;
                if eid_type == ENDPOINT_URI_SCHEME_DTN {
                                        let name: String = seq.next_element().unwrap_or_default().unwrap_or_default();
                    if name == "" {
                        Ok(EndpointID::with_dtn_none())
                    } else {
                        Ok(EndpointID::Dtn(eid_type, name))
                    }
                } else if eid_type == ENDPOINT_URI_SCHEME_IPN {
                    let ipnaddr: IpnAddress = seq
                        .next_element()?
                        .ok_or_else(|| de::Error::invalid_length(1, &self))?;
                    Ok(EndpointID::with_ipn(ipnaddr))
                } else {
                    Err(de::Error::invalid_value(
                        de::Unexpected::Unsigned(eid_type.into()),
                        &self,
                    ))
                }
            }
        }
        deserializer.deserialize_any(EndpointIDVisitor)
    }
}
impl Default for EndpointID {
    fn default() -> Self {
        EndpointID::DtnNone(ENDPOINT_URI_SCHEME_DTN, 0)
    }
}
impl EndpointID {
    pub fn new() -> EndpointID {
        Default::default()
    }
                                        pub fn with_dtn(addr: &str) -> EndpointID {
        EndpointID::Dtn(ENDPOINT_URI_SCHEME_DTN, addr.into())
    }
                                            pub fn with_dtn_none() -> EndpointID {
        EndpointID::DtnNone(ENDPOINT_URI_SCHEME_DTN, 0)
    }
                                                    pub fn with_ipn(addr: IpnAddress) -> EndpointID {
        EndpointID::Ipn(ENDPOINT_URI_SCHEME_IPN, addr)
    }
                                                                                                                pub fn endpoint(&self, ep: &str) -> Result<EndpointID, Bp7Error> {
        match self {
            EndpointID::DtnNone(_, _) => Err(Bp7Error::EIDError(
                "Cannot set endpoint on eid 'none'".to_owned(),
            )),
            EndpointID::Dtn(_, _) => {
                Ok(format!("dtn://{}/{}", self.node_part().unwrap(), ep).into())
            }
            EndpointID::Ipn(_, ipnaddr) => {
                if let Ok(number) = ep.trim().parse::<u64>() {
                    Ok(EndpointID::with_ipn(IpnAddress(ipnaddr.0, number)))
                } else {
                    Err(Bp7Error::EIDError(
                        "Invalid endpoint for IPN address".to_owned(),
                    ))
                }
            }
        }
    }
    pub fn scheme(&self) -> String {
        match self {
            EndpointID::DtnNone(_, _) => "dtn".to_string(),
            EndpointID::Dtn(_, _) => "dtn".to_string(),
            EndpointID::Ipn(_, _) => "ipn".to_string(),
        }
    }
    pub fn scheme_specific_part_dtn(&self) -> Option<String> {
        match self {
            EndpointID::Dtn(_, ssp) => Some(ssp.to_string()),
            _ => None,
        }
    }
    pub fn scheme_specific_part_ipn(&self) -> Option<IpnAddress> {
        match self {
            EndpointID::Ipn(_, ssp) => Some(ssp.to_owned()),
            _ => None,
        }
    }
}
impl fmt::Display for EndpointID {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let addr = match self {
            EndpointID::Ipn(_, ssp) => ssp.to_string(),
            EndpointID::Dtn(_, ssp) => ssp.to_string(),
            _ => "none".to_string(),
        };
        write!(f, "{}://{}", self.scheme(), addr)
    }
}
impl EndpointID {
                                                            pub fn node_part(&self) -> Option<String> {
        match self {
            EndpointID::DtnNone(_, _) => None,
            EndpointID::Dtn(_, eid) => {
                let nodeid: Vec<&str> = eid.split('/').collect();
                Some(nodeid[0].to_string())
            }
            EndpointID::Ipn(_, addr) => Some(addr.0.to_string()),
        }
    }
                                                            pub fn host_id(&self) -> Option<String> {
        match self {
            EndpointID::DtnNone(_, _) => None,
            EndpointID::Dtn(_, eid) => {
                let nodeid: Vec<&str> = eid.split('/').collect();
                Some(format!("{}://{}", self.scheme(), nodeid[0].to_string()))
            }
            EndpointID::Ipn(_, addr) => Some(format!("{}://{}", self.scheme(), addr.0)),
        }
    }
                                                                        pub fn is_node_id(&self) -> bool {
        match self {
            EndpointID::DtnNone(_, _) => false,
            EndpointID::Dtn(_, eid) => self.node_part() == Some(eid.to_string()),
            EndpointID::Ipn(_, addr) => addr.1 == 0,
        }
    }
                                                                                    pub fn validation_error(&self) -> Option<Bp7Error> {
        match self {
            EndpointID::Dtn(_, _) => None,             EndpointID::Ipn(code, addr) => {
                if *code != ENDPOINT_URI_SCHEME_IPN {
                    Some(Bp7Error::EIDError(
                        "Wrong URI scheme code for IPN".to_string(),
                    ))
                } else if addr.0 < 1 || addr.1 < 1 {
                    Some(Bp7Error::EIDError(
                        "IPN's node and service number must be >= 1".to_string(),
                    ))
                } else {
                    None
                }
            }
            EndpointID::DtnNone(code, addr) => {
                if *code != ENDPOINT_URI_SCHEME_DTN {
                    Some(Bp7Error::EIDError(
                        "Wrong URI scheme code for DTN".to_string(),
                    ))
                } else if *addr != 0 {
                    Some(Bp7Error::EIDError(
                        "dtn none must have uint(0) set as address".to_string(),
                    ))
                } else {
                    None
                }
            }
        }
    }
}
impl From<String> for EndpointID {
    fn from(item: String) -> Self {
        let item = if item.contains("://") {
            item
        } else {
            item.replace(":", "://")
        };
        let u = Url::parse(&item).expect("EndpointID url parsing error");
        let host = u.host();
        match u.scheme() {
            "dtn" => {
                if host == "none" {
                    return <EndpointID>::with_dtn_none();
                }
                let mut host = format!("{}{}", host, u.path());
                if host.ends_with('/') {
                    host.truncate(host.len() - 1);
                }
                EndpointID::with_dtn(&host)
            }
            "ipn" => {
                let fields: Vec<&str> = host.split('.').collect();
                if fields.len() != 2 {
                    panic!("wrong number of fields in IPN address");
                }
                let p1: u64 = fields[0].parse().unwrap();
                let p2: u64 = fields[1].parse().unwrap();
                EndpointID::with_ipn(IpnAddress(p1, p2))
            }
            _ => <EndpointID>::with_dtn_none(),
        }
    }
}
impl From<&str> for EndpointID {
    fn from(item: &str) -> Self {
        EndpointID::from(String::from(item))
    }
}