use std::net::IpAddr;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::Instant;
use futures::{future, future::Either, Future, FutureExt};
use proto::op::Query;
use proto::rr::{Name, RData, Record, RecordType};
use proto::xfer::{DnsHandle, DnsRequestOptions};
use crate::config::LookupIpStrategy;
use crate::dns_lru::MAX_TTL;
use crate::error::*;
use crate::hosts::Hosts;
use crate::lookup::{Lookup, LookupIntoIter, LookupIter};
use crate::lookup_state::CachingClient;
#[derive(Debug, Clone)]
pub struct LookupIp(Lookup);
impl LookupIp {
    
    pub fn iter(&self) -> LookupIpIter {
        LookupIpIter(self.0.iter())
    }
    
    pub fn query(&self) -> &Query {
        self.0.query()
    }
    
    pub fn valid_until(&self) -> Instant {
        self.0.valid_until()
    }
    
    
    
    pub fn as_lookup(&self) -> &Lookup {
        &self.0
    }
}
impl From<Lookup> for LookupIp {
    fn from(lookup: Lookup) -> Self {
        LookupIp(lookup)
    }
}
pub struct LookupIpIter<'i>(pub(crate) LookupIter<'i>);
impl<'i> Iterator for LookupIpIter<'i> {
    type Item = IpAddr;
    fn next(&mut self) -> Option<Self::Item> {
        let iter: &mut _ = &mut self.0;
        iter.filter_map(|rdata| match *rdata {
            RData::A(ip) => Some(IpAddr::from(ip)),
            RData::AAAA(ip) => Some(IpAddr::from(ip)),
            _ => None,
        })
        .next()
    }
}
impl IntoIterator for LookupIp {
    type Item = IpAddr;
    type IntoIter = LookupIpIntoIter;
    
    
    fn into_iter(self) -> Self::IntoIter {
        LookupIpIntoIter(self.0.into_iter())
    }
}
pub struct LookupIpIntoIter(LookupIntoIter);
impl Iterator for LookupIpIntoIter {
    type Item = IpAddr;
    fn next(&mut self) -> Option<Self::Item> {
        let iter: &mut _ = &mut self.0;
        iter.filter_map(|rdata| match rdata {
            RData::A(ip) => Some(IpAddr::from(ip)),
            RData::AAAA(ip) => Some(IpAddr::from(ip)),
            _ => None,
        })
        .next()
    }
}
pub struct LookupIpFuture<C>
where
    C: DnsHandle + 'static,
{
    client_cache: CachingClient<C>,
    names: Vec<Name>,
    strategy: LookupIpStrategy,
    options: DnsRequestOptions,
    query: Pin<Box<dyn Future<Output = Result<Lookup, ResolveError>> + Send>>,
    hosts: Option<Arc<Hosts>>,
    finally_ip_addr: Option<RData>,
}
impl<C: DnsHandle + Sync + 'static> Future for LookupIpFuture<C> {
    type Output = Result<LookupIp, ResolveError>;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
        loop {
            
            let query = self.query.as_mut().poll(cx);
            
            let should_retry = match query {
                
                Poll::Pending => return Poll::Pending,
                
                
                
                Poll::Ready(Ok(ref lookup)) => lookup.is_empty(),
                
                Poll::Ready(Err(_)) => true,
            };
            if should_retry {
                if let Some(name) = self.names.pop() {
                    
                    
                    self.query = strategic_lookup(
                        name,
                        self.strategy,
                        self.client_cache.clone(),
                        self.options.clone(),
                        self.hosts.clone(),
                    )
                    .boxed();
                    
                    
                    continue;
                } else if let Some(ip_addr) = self.finally_ip_addr.take() {
                    
                    
                    let record = Record::from_rdata(Name::new(), MAX_TTL, ip_addr);
                    let lookup = Lookup::new_with_max_ttl(Query::new(), Arc::new(vec![record]));
                    return Poll::Ready(Ok(lookup.into()));
                }
            };
            
            
            
            return query.map(|f| f.map(LookupIp::from));
            
            
            
            
        }
    }
}
impl<C> LookupIpFuture<C>
where
    C: DnsHandle + 'static,
{
    
    
    
    
    
    
    
    pub fn lookup(
        names: Vec<Name>,
        strategy: LookupIpStrategy,
        client_cache: CachingClient<C>,
        options: DnsRequestOptions,
        hosts: Option<Arc<Hosts>>,
        finally_ip_addr: Option<RData>,
    ) -> Self {
        let empty =
            ResolveError::from(ResolveErrorKind::Message("can not lookup IPs for no names"));
        LookupIpFuture {
            names,
            strategy,
            client_cache,
            
            
            query: future::err(empty).boxed(),
            options,
            hosts,
            finally_ip_addr,
        }
    }
}
async fn strategic_lookup<C: DnsHandle + 'static>(
    name: Name,
    strategy: LookupIpStrategy,
    client: CachingClient<C>,
    options: DnsRequestOptions,
    hosts: Option<Arc<Hosts>>,
) -> Result<Lookup, ResolveError> {
    match strategy {
        LookupIpStrategy::Ipv4Only => ipv4_only(name, client, options, hosts).await,
        LookupIpStrategy::Ipv6Only => ipv6_only(name, client, options, hosts).await,
        LookupIpStrategy::Ipv4AndIpv6 => ipv4_and_ipv6(name, client, options, hosts).await,
        LookupIpStrategy::Ipv6thenIpv4 => ipv6_then_ipv4(name, client, options, hosts).await,
        LookupIpStrategy::Ipv4thenIpv6 => ipv4_then_ipv6(name, client, options, hosts).await,
    }
}
async fn hosts_lookup<C: DnsHandle + 'static>(
    query: Query,
    mut client: CachingClient<C>,
    options: DnsRequestOptions,
    hosts: Option<Arc<Hosts>>,
) -> Result<Lookup, ResolveError> {
    if let Some(hosts) = hosts {
        if let Some(lookup) = hosts.lookup_static_host(&query) {
            return Ok(lookup);
        };
    }
    client.lookup(query, options).await
}
async fn ipv4_only<C: DnsHandle + 'static>(
    name: Name,
    client: CachingClient<C>,
    options: DnsRequestOptions,
    hosts: Option<Arc<Hosts>>,
) -> Result<Lookup, ResolveError> {
    hosts_lookup(Query::query(name, RecordType::A), client, options, hosts).await
}
async fn ipv6_only<C: DnsHandle + 'static>(
    name: Name,
    client: CachingClient<C>,
    options: DnsRequestOptions,
    hosts: Option<Arc<Hosts>>,
) -> Result<Lookup, ResolveError> {
    hosts_lookup(Query::query(name, RecordType::AAAA), client, options, hosts).await
}
async fn ipv4_and_ipv6<C: DnsHandle + 'static>(
    name: Name,
    client: CachingClient<C>,
    options: DnsRequestOptions,
    hosts: Option<Arc<Hosts>>,
) -> Result<Lookup, ResolveError> {
    let sel_res = future::select(
        hosts_lookup(
            Query::query(name.clone(), RecordType::A),
            client.clone(),
            options.clone(),
            hosts.clone(),
        )
        .boxed(),
        hosts_lookup(Query::query(name, RecordType::AAAA), client, options, hosts).boxed(),
    )
    .await;
    let (ips, remaining_query) = match sel_res {
        Either::Left(ips_and_remaining) => ips_and_remaining,
        Either::Right(ips_and_remaining) => ips_and_remaining,
    };
    let next_ips = remaining_query.await;
    match (ips, next_ips) {
        (Ok(ips), Ok(next_ips)) => {
            
            let ips = ips.append(next_ips);
            Ok(ips)
        }
        (Ok(ips), Err(e)) | (Err(e), Ok(ips)) => {
            debug!(
                "one of ipv4 or ipv6 lookup failed in ipv4_and_ipv6 strategy: {}",
                e
            );
            Ok(ips)
        }
        (Err(e1), Err(e2)) => {
            debug!(
                "both of ipv4 or ipv6 lookup failed in ipv4_and_ipv6 strategy e1: {}, e2: {}",
                e1, e2
            );
            Err(e1)
        }
    }
}
async fn ipv6_then_ipv4<C: DnsHandle + 'static>(
    name: Name,
    client: CachingClient<C>,
    options: DnsRequestOptions,
    hosts: Option<Arc<Hosts>>,
) -> Result<Lookup, ResolveError> {
    rt_then_swap(
        name,
        client,
        RecordType::AAAA,
        RecordType::A,
        options,
        hosts,
    )
    .await
}
async fn ipv4_then_ipv6<C: DnsHandle + 'static>(
    name: Name,
    client: CachingClient<C>,
    options: DnsRequestOptions,
    hosts: Option<Arc<Hosts>>,
) -> Result<Lookup, ResolveError> {
    rt_then_swap(
        name,
        client,
        RecordType::A,
        RecordType::AAAA,
        options,
        hosts,
    )
    .await
}
async fn rt_then_swap<C: DnsHandle + 'static>(
    name: Name,
    client: CachingClient<C>,
    first_type: RecordType,
    second_type: RecordType,
    options: DnsRequestOptions,
    hosts: Option<Arc<Hosts>>,
) -> Result<Lookup, ResolveError> {
    let or_client = client.clone();
    let res = hosts_lookup(
        Query::query(name.clone(), first_type),
        client,
        options.clone(),
        hosts.clone(),
    )
    .await;
    match res {
        Ok(ips) => {
            if ips.is_empty() {
                
                hosts_lookup(
                    Query::query(name.clone(), second_type),
                    or_client,
                    options,
                    hosts,
                )
                .await
            } else {
                Ok(ips)
            }
        }
        Err(_) => {
            hosts_lookup(
                Query::query(name.clone(), second_type),
                or_client,
                options,
                hosts,
            )
            .await
        }
    }
}
#[cfg(test)]
pub mod tests {
    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
    use std::sync::{Arc, Mutex};
    use futures::executor::block_on;
    use futures::{future, Future};
    use proto::error::{ProtoError, ProtoResult};
    use proto::op::Message;
    use proto::rr::{Name, RData, Record};
    use proto::xfer::{DnsHandle, DnsRequest, DnsResponse};
    use super::*;
    #[derive(Clone)]
    pub struct MockDnsHandle {
        messages: Arc<Mutex<Vec<ProtoResult<DnsResponse>>>>,
    }
    impl DnsHandle for MockDnsHandle {
        type Response =
            Pin<Box<dyn Future<Output = Result<DnsResponse, ProtoError>> + Send + Unpin>>;
        fn send<R: Into<DnsRequest>>(&mut self, _: R) -> Self::Response {
            Box::pin(future::ready(
                self.messages.lock().unwrap().pop().unwrap_or_else(empty),
            ))
        }
    }
    pub fn v4_message() -> ProtoResult<DnsResponse> {
        let mut message = Message::new();
        message.insert_answers(vec![Record::from_rdata(
            Name::root(),
            86400,
            RData::A(Ipv4Addr::new(127, 0, 0, 1)),
        )]);
        Ok(message.into())
    }
    pub fn v6_message() -> ProtoResult<DnsResponse> {
        let mut message = Message::new();
        message.insert_answers(vec![Record::from_rdata(
            Name::root(),
            86400,
            RData::AAAA(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
        )]);
        Ok(message.into())
    }
    pub fn empty() -> ProtoResult<DnsResponse> {
        Ok(Message::new().into())
    }
    pub fn error() -> ProtoResult<DnsResponse> {
        Err(ProtoError::from("forced test failure"))
    }
    pub fn mock(messages: Vec<ProtoResult<DnsResponse>>) -> MockDnsHandle {
        MockDnsHandle {
            messages: Arc::new(Mutex::new(messages)),
        }
    }
    #[test]
    fn test_ipv4_only_strategy() {
        assert_eq!(
            block_on(ipv4_only(
                Name::root(),
                CachingClient::new(0, mock(vec![v4_message()]), false),
                Default::default(),
                None,
            ))
            .unwrap()
            .iter()
            .map(|r| r.to_ip_addr().unwrap())
            .collect::<Vec<IpAddr>>(),
            vec![Ipv4Addr::new(127, 0, 0, 1)]
        );
    }
    #[test]
    fn test_ipv6_only_strategy() {
        assert_eq!(
            block_on(ipv6_only(
                Name::root(),
                CachingClient::new(0, mock(vec![v6_message()]), false),
                Default::default(),
                None,
            ))
            .unwrap()
            .iter()
            .map(|r| r.to_ip_addr().unwrap())
            .collect::<Vec<IpAddr>>(),
            vec![Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)]
        );
    }
    #[test]
    fn test_ipv4_and_ipv6_strategy() {
        
        
        assert_eq!(
            block_on(ipv4_and_ipv6(
                Name::root(),
                CachingClient::new(0, mock(vec![v6_message(), v4_message()]), false),
                Default::default(),
                None,
            ))
            .unwrap()
            .iter()
            .map(|r| r.to_ip_addr().unwrap())
            .collect::<Vec<IpAddr>>(),
            vec![
                IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
                IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
            ]
        );
        
        assert_eq!(
            block_on(ipv4_and_ipv6(
                Name::root(),
                CachingClient::new(0, mock(vec![empty(), v4_message()]), false),
                Default::default(),
                None,
            ))
            .unwrap()
            .iter()
            .map(|r| r.to_ip_addr().unwrap())
            .collect::<Vec<IpAddr>>(),
            vec![IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))]
        );
        
        assert_eq!(
            block_on(ipv4_and_ipv6(
                Name::root(),
                CachingClient::new(0, mock(vec![error(), v4_message()]), false),
                Default::default(),
                None,
            ))
            .unwrap()
            .iter()
            .map(|r| r.to_ip_addr().unwrap())
            .collect::<Vec<IpAddr>>(),
            vec![IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))]
        );
        
        assert_eq!(
            block_on(ipv4_and_ipv6(
                Name::root(),
                CachingClient::new(0, mock(vec![v6_message(), empty()]), false),
                Default::default(),
                None,
            ))
            .unwrap()
            .iter()
            .map(|r| r.to_ip_addr().unwrap())
            .collect::<Vec<IpAddr>>(),
            vec![IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))]
        );
        
        assert_eq!(
            block_on(ipv4_and_ipv6(
                Name::root(),
                CachingClient::new(0, mock(vec![v6_message(), error()]), false),
                Default::default(),
                None,
            ))
            .unwrap()
            .iter()
            .map(|r| r.to_ip_addr().unwrap())
            .collect::<Vec<IpAddr>>(),
            vec![IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))]
        );
    }
    #[test]
    fn test_ipv6_then_ipv4_strategy() {
        
        assert_eq!(
            block_on(ipv6_then_ipv4(
                Name::root(),
                CachingClient::new(0, mock(vec![v6_message()]), false),
                Default::default(),
                None,
            ))
            .unwrap()
            .iter()
            .map(|r| r.to_ip_addr().unwrap())
            .collect::<Vec<IpAddr>>(),
            vec![Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)]
        );
        
        assert_eq!(
            block_on(ipv6_then_ipv4(
                Name::root(),
                CachingClient::new(0, mock(vec![v4_message(), empty()]), false),
                Default::default(),
                None,
            ))
            .unwrap()
            .iter()
            .map(|r| r.to_ip_addr().unwrap())
            .collect::<Vec<IpAddr>>(),
            vec![Ipv4Addr::new(127, 0, 0, 1)]
        );
        
        assert_eq!(
            block_on(ipv6_then_ipv4(
                Name::root(),
                CachingClient::new(0, mock(vec![v4_message(), error()]), false),
                Default::default(),
                None,
            ))
            .unwrap()
            .iter()
            .map(|r| r.to_ip_addr().unwrap())
            .collect::<Vec<IpAddr>>(),
            vec![Ipv4Addr::new(127, 0, 0, 1)]
        );
    }
    #[test]
    fn test_ipv4_then_ipv6_strategy() {
        
        assert_eq!(
            block_on(ipv4_then_ipv6(
                Name::root(),
                CachingClient::new(0, mock(vec![v4_message()]), false),
                Default::default(),
                None,
            ))
            .unwrap()
            .iter()
            .map(|r| r.to_ip_addr().unwrap())
            .collect::<Vec<IpAddr>>(),
            vec![Ipv4Addr::new(127, 0, 0, 1)]
        );
        
        assert_eq!(
            block_on(ipv4_then_ipv6(
                Name::root(),
                CachingClient::new(0, mock(vec![v6_message(), empty()]), false),
                Default::default(),
                None,
            ))
            .unwrap()
            .iter()
            .map(|r| r.to_ip_addr().unwrap())
            .collect::<Vec<IpAddr>>(),
            vec![Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)]
        );
        
        assert_eq!(
            block_on(ipv4_then_ipv6(
                Name::root(),
                CachingClient::new(0, mock(vec![v6_message(), error()]), false),
                Default::default(),
                None,
            ))
            .unwrap()
            .iter()
            .map(|r| r.to_ip_addr().unwrap())
            .collect::<Vec<IpAddr>>(),
            vec![Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)]
        );
    }
}