#![allow(unsafe_code)]

mod dnsinfo;

use std::convert::TryFrom;
use std::ffi::CStr;
use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
use std::num::NonZeroU16;

use eyre::bail;

use self::dnsinfo::{dns_config_t, dns_configuration_copy, dns_configuration_free, dns_resolver_t};
use self::dnsinfo::{sockaddr, sockaddr_in, sockaddr_in6, AF_INET, AF_INET6};
use self::ResolversError::*;
use super::{ChoosePort, PortChooser, Resolvers, SockaddrDisplay};

error!(ResolversError);
#[derive(Debug, displaydoc::Display)]
#[prefix_enum_doc_attributes]
/// failed to gather default resolvers
enum ResolversError {
    /// dns_configuration_copy returned NULL
    NullFromFramework,
    /// sockaddr has unsupported sa_family ({0})
    SockaddrFamily(u32),
    /// n_resolver value ({0}) overflows usize
    ResolverOverflow(i32),
    /// n_nameserver value ({0}) overflows usize
    NameserverOverflow(i32),
}

pub(crate) struct MacosResolvers {
    config: *mut dns_config_t,
    result: Vec<(SocketAddr, u32)>,
    i: usize,
}

impl Drop for MacosResolvers {
    fn drop(&mut self) {
        unsafe {
            dns_configuration_free(self.config);
        }
    }
}

impl Resolvers for MacosResolvers {
    fn new(port: PortChooser<NonZeroU16>) -> eyre::Result<Self> {
        unsafe {
            let config = dns_configuration_copy();

            if config.is_null() {
                bail!(NullFromFramework);
            }

            let mut result = Vec::default();

            for resolver in ResolverIterator::new(config) {
                let resolver = resolver?;
                let resolver_port = NonZeroU16::new((*resolver).port);
                let resolver_order = (*resolver).search_order;

                if !(*resolver).__bindgen_anon_1.domain.is_null() {
                    continue;
                }

                for nameserver in NameserverIterator::new(resolver) {
                    let address = nameserver?;
                    let address = sa(address, port, resolver_port)?;
                    result.push((address, resolver_order));
                }
            }

            result.sort_by_key(|(_, order)| *order);

            Ok(MacosResolvers {
                config,
                result,
                i: 0,
            })
        }
    }

    fn show(&self, sink: &mut impl std::io::Write) -> eyre::Result<()> {
        unsafe {
            for (i, resolver) in ResolverIterator::new(self.config).enumerate() {
                let resolver = resolver?;
                write!(sink, "resolver #{}:", i + 1)?;

                let domain = (*resolver).__bindgen_anon_1.domain;
                if !domain.is_null() {
                    write!(sink, " domain {:?};", CStr::from_ptr(domain))?;
                } else {
                    write!(sink, " no domain;")?;
                }

                write!(sink, " order {};", (*resolver).search_order)?;

                let port = (*resolver).port;
                if port != 0 {
                    write!(sink, " port {};", port)?;
                } else {
                    write!(sink, " no port;")?;
                }

                writeln!(sink)?;

                for nameserver in NameserverIterator::new(resolver) {
                    let port = PortChooser::new(None, None);
                    let nameserver = sa(nameserver?, port, None)?;
                    writeln!(sink, "\tnameserver: {}", SockaddrDisplay(nameserver))?;
                }
            }
        }

        Ok(())
    }
}

impl Iterator for MacosResolvers {
    type Item = eyre::Result<SocketAddr>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.i >= self.result.len() {
            return None;
        }

        let (address, _) = self.result[self.i];
        self.i += 1;
        return Some(Ok(address));
    }
}

pub(crate) fn resolvers(port: PortChooser<NonZeroU16>) -> eyre::Result<impl Resolvers> {
    MacosResolvers::new(port)
}

unsafe fn sa<D>(
    address: *const sockaddr,
    port: impl ChoosePort<D>,
    resolver_port: Option<NonZeroU16>,
) -> eyre::Result<SocketAddr> {
    let mut result = match (*address).sa_family.into() {
        AF_INET => SocketAddr::from(sin(address)),
        AF_INET6 => SocketAddr::from(sin6(address)),
        family => bail!(SockaddrFamily(family)),
    };

    let nameserver_port = NonZeroU16::new(result.port());
    let resolver_port = nameserver_port.or(resolver_port);
    result.set_port(port.choose_or_zero(resolver_port));

    Ok(result)
}

unsafe fn sin(address: *const sockaddr) -> SocketAddrV4 {
    let address = address as *const sockaddr_in;

    // https://beej.us/guide/bgnet/html/#byte-order
    // https://beej.us/guide/bgnet/html/#cb11
    //  “Assume the numbers in this document are in Host Byte Order
    //   unless I say otherwise.”
    let port = u16::from_be((*address).sin_port);
    let addr = &(*address).sin_addr as *const _ as *const [u8; 4];

    SocketAddrV4::new((*addr).into(), port)
}

unsafe fn sin6(address: *const sockaddr) -> SocketAddrV6 {
    let address = address as *const sockaddr_in6;

    // https://beej.us/guide/bgnet/html/#byte-order
    // https://beej.us/guide/bgnet/html/#cb13
    //  “Assume the numbers in this document are in Host Byte Order
    //   unless I say otherwise.”
    let port = u16::from_be((*address).sin6_port);
    let flowinfo = (*address).sin6_flowinfo;
    let addr = &(*address).sin6_addr as *const _ as *const [u8; 16];
    let scope_id = (*address).sin6_scope_id;

    SocketAddrV6::new((*addr).into(), port, flowinfo, scope_id)
}

struct ResolverIterator {
    inner: *const dns_config_t,
    i: isize,
}

impl ResolverIterator {
    fn new(inner: *const dns_config_t) -> Self {
        Self { inner, i: 0 }
    }
}

impl Iterator for ResolverIterator {
    type Item = eyre::Result<*const dns_resolver_t>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.inner.is_null() {
            return None;
        }

        unsafe {
            let len = (*self.inner).n_resolver;

            if self.i >= itry!(isize::try_from(len), ResolverOverflow(len)) {
                return None;
            }

            let resolver = (*self.inner).__bindgen_anon_1.resolver.offset(self.i);
            self.i += 1;

            return Some(Ok(*resolver));
        }
    }
}

struct NameserverIterator {
    inner: *const dns_resolver_t,
    i: isize,
}

impl NameserverIterator {
    fn new(inner: *const dns_resolver_t) -> Self {
        Self { inner, i: 0 }
    }
}

impl Iterator for NameserverIterator {
    type Item = eyre::Result<*const sockaddr>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.inner.is_null() {
            return None;
        }

        unsafe {
            let len = (*self.inner).n_nameserver;

            if self.i >= itry!(isize::try_from(len), NameserverOverflow(len)) {
                return None;
            }

            let nameserver = (*self.inner).__bindgen_anon_2.nameserver.offset(self.i);
            self.i += 1;

            return Some(Ok(*nameserver));
        }
    }
}
