#![allow(unsafe_code)]

use std::convert::{TryFrom, TryInto};
use std::ffi::OsString;
use std::fmt::Debug;
use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
use std::num::NonZeroU16;
use std::ptr::null_mut as NULL;

use eyre::WrapErr;
use winapi::shared::ifdef::IfOperStatusUp;
use winapi::shared::minwindef::DWORD;
use winapi::shared::ntdef::{LANG_NEUTRAL, MAKELANGID, SUBLANG_DEFAULT, ULONG};
use winapi::shared::winerror::{
    ERROR_ADDRESS_NOT_ASSOCIATED, ERROR_BUFFER_OVERFLOW, ERROR_INVALID_PARAMETER,
    ERROR_NOT_ENOUGH_MEMORY, ERROR_NO_DATA, ERROR_SUCCESS,
};
use winapi::shared::ws2def::{ADDRESS_FAMILY, AF_INET, AF_INET6, AF_UNSPEC, SOCKADDR, SOCKADDR_IN};
use winapi::shared::ws2ipdef::SOCKADDR_IN6;
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::iphlpapi::GetAdaptersAddresses;
use winapi::um::iptypes::{
    GAA_FLAG_SKIP_ANYCAST, GAA_FLAG_SKIP_MULTICAST, GAA_FLAG_SKIP_UNICAST, IP_ADAPTER_ADDRESSES,
    IP_ADAPTER_DNS_SERVER_ADDRESS,
};
use winapi::um::winbase::{
    FormatMessageW, LocalFree, FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM,
    FORMAT_MESSAGE_IGNORE_INSERTS,
};
use wio::wide::FromWide;

use self::ResolversError::*;
use super::{ChoosePort, PortChooser, Resolvers, SockaddrDisplay};

error!(ResolversError);
#[derive(Debug, displaydoc::Display, PartialEq)]
#[prefix_enum_doc_attributes]
/// failed to gather default resolvers
enum ResolversError {
    /// {0} error: {1:?}
    WinapiError(&'static str, OsString),
    /// {0} returned {1:?}
    WinapiReturn(&'static str, String),
    /// resolver has unexpected non-zero port ({0})
    UnexpectedPort(NonZeroU16),
    /// sockaddr has unsupported sa_family ({0})
    SockaddrFamily(u16),
    /// error converting length from usize
    LenFromUsize,
    /// error converting length to usize
    LenToUsize,
}

pub(crate) struct WindowsResolvers {
    #[allow(dead_code)]
    config: Vec<u8>,
    result: Vec<SocketAddr>,
    i: usize,
}

impl Resolvers for WindowsResolvers {
    fn new(port: PortChooser<NonZeroU16>) -> eyre::Result<Self> {
        let port = port.choose(None);

        unsafe {
            let family = AF_UNSPEC as ULONG;
            let flags = GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST;
            let reserved = NULL();

            let config = buffer(
                "GetAdaptersAddresses",
                Vec::with_capacity(15000),
                |buffer, len| GetAdaptersAddresses(family, flags, reserved, buffer, len),
                |retval| retval == ERROR_SUCCESS,
                |retval| retval == ERROR_BUFFER_OVERFLOW,
                |retval| {
                    ![
                        ERROR_SUCCESS,
                        ERROR_ADDRESS_NOT_ASSOCIATED,
                        ERROR_BUFFER_OVERFLOW,
                        ERROR_INVALID_PARAMETER,
                        ERROR_NOT_ENOUGH_MEMORY,
                        ERROR_NO_DATA,
                    ]
                    .contains(&retval)
                },
            )?;

            let mut result = Vec::default();

            for adapter in AdapterIterator::new(config.as_ptr() as *const _) {
                for address in AddressIterator::new((*adapter).FirstDnsServerAddress) {
                    result.push(sa((*address).Address.lpSockaddr, port.into())?);
                }
            }

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

    fn show(&self, sink: &mut impl std::io::Write) -> eyre::Result<()> {
        unsafe {
            for adapter in AdapterIterator::new(self.config.as_ptr() as *const _) {
                writeln!(
                    sink,
                    "adapter: friendly name {:?};",
                    OsString::from_wide_ptr_null((*adapter).FriendlyName)
                )?;

                for address in AddressIterator::new((*adapter).FirstDnsServerAddress) {
                    let nameserver = sa((*address).Address.lpSockaddr, 0)?;
                    writeln!(sink, "\tnameserver: {}", SockaddrDisplay(nameserver))?;
                }
            }
        }

        Ok(())
    }
}

impl Iterator for WindowsResolvers {
    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> {
    WindowsResolvers::new(port)
}

unsafe fn sa(address: *const SOCKADDR, port: u16) -> eyre::Result<SocketAddr> {
    Ok(match (*address).sa_family {
        x if x == (AF_INET as ADDRESS_FAMILY) => {
            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_zero = u16::from_be((*address).sin_port);
            let a = *(*address).sin_addr.S_un.S_un_b();
            let addr = [a.s_b1, a.s_b2, a.s_b3, a.s_b4];

            match NonZeroU16::new(port_zero) {
                Some(x) => return Err(UnexpectedPort(x).into()),
                None => {}
            }

            SocketAddr::from(SocketAddrV4::new(addr.into(), port))
        }
        x if x == (AF_INET6 as ADDRESS_FAMILY) => {
            let address = address as *const SOCKADDR_IN6;

            // 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_zero = u16::from_be((*address).sin6_port);
            let flowinfo = (*address).sin6_flowinfo;
            let addr = (*address).sin6_addr.u.Byte() as *const _ as *const [u8; 16];
            let scope_id = *(*address).u.sin6_scope_id();

            match NonZeroU16::new(port_zero) {
                Some(x) => return Err(UnexpectedPort(x).into()),
                None => {}
            }

            // I'll just set sin6_flowinfo to zero, because now I'm unsure whether
            // or not I need to ntohs it (and I don't like guessing these things)
            SocketAddr::from(SocketAddrV6::new((*addr).into(), port, flowinfo, scope_id))
        }
        x => return Err(SockaddrFamily(x).into()),
    })
}

struct AdapterIterator {
    next: *const IP_ADAPTER_ADDRESSES,
}

impl AdapterIterator {
    fn new(next: *const IP_ADAPTER_ADDRESSES) -> Self {
        Self { next }
    }
}

impl Iterator for AdapterIterator {
    type Item = *const IP_ADAPTER_ADDRESSES;

    fn next(&mut self) -> Option<Self::Item> {
        unsafe {
            // Ignore resolvers associated with interfaces that aren’t connected (ifOperStatus=up
            // [RFC 2863]). Such resolvers might only be reachable over that interface (e.g. link-
            // local), or not necessarily be reachable at all (e.g. fec0:0:0:ffff::{1,2,3} might be
            // returned for disconnected adapters whose resolvers would be excluded by ipconfig
            // [draft-ietf-ipv6-dns-discovery-07]).
            while !self.next.is_null() && (*self.next).OperStatus != IfOperStatusUp {
                self.next = (*self.next).Next;
            }

            if self.next.is_null() {
                return None;
            }

            let result = Some(self.next);

            self.next = (*self.next).Next;

            result
        }
    }
}

struct AddressIterator {
    next: *const IP_ADAPTER_DNS_SERVER_ADDRESS,
}

impl AddressIterator {
    fn new(next: *const IP_ADAPTER_DNS_SERVER_ADDRESS) -> Self {
        Self { next }
    }
}

impl Iterator for AddressIterator {
    type Item = *const IP_ADAPTER_DNS_SERVER_ADDRESS;

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

        let result = Some(self.next);

        unsafe {
            self.next = (*self.next).Next;
        }

        result
    }
}

#[must_use]
fn format_last_error(id: DWORD) -> OsString {
    unsafe {
        let mut pointer = NULL();

        if FormatMessageW(
            FORMAT_MESSAGE_ALLOCATE_BUFFER
                | FORMAT_MESSAGE_FROM_SYSTEM
                | FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL(),
            id,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT).into(),
            &mut pointer as *mut _ as *mut _,
            0,
            NULL(),
        ) == 0
        {
            panic!("FormatMessageW failed with error {}", GetLastError());
        }

        let result = OsString::from_wide_ptr_null(pointer);
        let _ignored = LocalFree(pointer as *mut _);

        result
    }
}

/// Calls a function that writes to the given `buffer`, retrying while `buffer` is insufficient.
///
/// `fun` is called with two arguments: a pointer to the buffer (as any type), and a pointer to a
/// `len` variable (of any type), which is set to the buffer’s capacity before each attempt.
///
/// `ok` and `insufficient` are predicates over the return value. `fun` must set `len` to the size
/// it thinks it needs in the `insufficient` case, but can do anything with `len` (or nothing at
/// all, in the case of GetAdaptersAddresses) in the `ok` case or in the case of any other error.
///
/// When the return value is neither `ok` nor `insufficient`, we return an error, which includes
/// the message from GetLastError and FormatMessage iff `message` returns true.
///
/// # Examples
///
/// ```no_run
/// # use bore::platform::windows::buffer;
/// # use std::ptr::null_mut;
/// # use winapi::shared::minwindef::{TRUE, ULONG};
/// # use winapi::shared::winerror::{ERROR_BUFFER_OVERFLOW, ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS};
/// # use winapi::shared::ws2def::AF_UNSPEC;
/// # use winapi::um::errhandlingapi::GetLastError;
/// # use winapi::um::iphlpapi::GetAdaptersAddresses;
/// # use winapi::um::sysinfoapi::GetLogicalProcessorInformationEx;
/// # use winapi::um::winnt::RelationGroup;
/// # unsafe {
/// let adapter_addresses = buffer(
///     Vec::with_capacity(15000),
///     |buffer, len| GetAdaptersAddresses(AF_UNSPEC as ULONG, 0, null_mut(), buffer, len),
///     |retval| retval == ERROR_SUCCESS,
///     |retval| retval == ERROR_BUFFER_OVERFLOW,
/// )?;
/// let processor_groups = buffer(
///     Vec::default(),
///     |buffer, len| GetLogicalProcessorInformationEx(RelationGroup, buffer, len),
///     |retval| retval == TRUE,
///     |_| GetLastError() == ERROR_INSUFFICIENT_BUFFER,
/// )?;
/// # }
/// # Ok::<(), eyre::Error>(())
/// ```
pub unsafe fn buffer<I, F, M, B, O, R, T, L, E>(
    name: &'static str,
    mut buffer: Vec<u8>,
    mut fun: T,
    mut ok: O,
    mut insufficient: E,
    mut message: M,
) -> eyre::Result<Vec<u8>>
where
    T: FnMut(*mut B, *mut L) -> R,
    O: FnMut(R) -> bool,
    E: FnMut(R) -> bool,
    M: FnMut(R) -> bool,
    L: Copy + TryInto<usize, Error = I> + TryFrom<usize, Error = F> + std::fmt::Debug,
    R: Copy + ToString,
    I: 'static + Send + Sync + std::error::Error,
    F: 'static + Send + Sync + std::error::Error,
{
    let mut len = L::try_from(buffer.capacity()).wrap_err_with(|| LenFromUsize)?;

    loop {
        let retval = fun(buffer.as_mut_ptr() as *mut B, &mut len);
        let error = GetLastError(); // only valid in message case

        if ok(retval) {
            // Don’t set the Vec’s len to our len on success, because this implies all of the first
            // len bytes were initialised. Some functions set our len to indicate this on success,
            // like GetLogicalProcessorInformationEx, while others like GetAdaptersAddresses don’t.
            return Ok(buffer);
        } else if insufficient(retval) {
            buffer.reserve(len.try_into().wrap_err_with(|| LenToUsize)?);
        } else if message(retval) {
            return Err(WinapiError(name, format_last_error(error)).into());
        } else {
            return Err(WinapiReturn(name, retval.to_string()).into());
        }
    }
}

#[cfg(test)]
mod test {
    use std::ffi::c_void;

    #[test]
    fn buffer() -> eyre::Result<()> {
        fn make_fun(lens: &mut Vec<i16>) -> impl FnMut(*mut (), *mut i16) -> bool + '_ {
            move |_, len| unsafe {
                lens.push(*len);
                if *len < 42 {
                    *len = 69;
                    return false;
                } else {
                    *len = 42;
                    return true;
                }
            }
        }

        let mut lens = Vec::default();
        let result = unsafe {
            super::buffer(
                "fun",
                Vec::with_capacity(13),
                make_fun(&mut lens),
                |retval| retval,
                |retval| !retval,
                |_| false,
            )
        }?;

        assert_eq!(result.capacity(), 69);
        assert_eq!(result.len(), 0);
        assert_eq!(lens, [13, 69]);

        let mut lens = Vec::default();
        let result = unsafe {
            super::buffer(
                "fun",
                Vec::with_capacity(420),
                make_fun(&mut lens),
                |retval| retval,
                |retval| !retval,
                |_| false,
            )
        }?;

        assert_eq!(result.capacity(), 420);
        assert_eq!(result.len(), 0);
        assert_eq!(lens, [420]);

        Ok(())
    }

    #[test]
    fn error() {
        use super::*;

        let result = unsafe {
            buffer(
                "GetAdaptersAddresses",
                Vec::default(),
                |_: *mut c_void, _: *mut usize| GetAdaptersAddresses(0, 0, NULL(), NULL(), NULL()),
                |_| false,
                |_| false,
                |_| false,
            )
        };

        assert_eq!(
            result.unwrap_err().downcast::<ResolversError>().unwrap(),
            WinapiReturn("GetAdaptersAddresses", "87".into()),
        );
    }
}
