// Take a look at the license at the top of the repository in the LICENSE file.

use std::ffi::OsString;
use std::os::windows::prelude::OsStringExt;
use std::ptr::null_mut;

use winapi::shared::winerror::{ERROR_BUFFER_OVERFLOW, ERROR_SUCCESS};
use winapi::shared::ws2def::AF_UNSPEC;
use winapi::um::iphlpapi::GetAdaptersAddresses;
use winapi::um::iptypes::{
    GAA_FLAG_SKIP_ANYCAST, GAA_FLAG_SKIP_DNS_SERVER, GAA_FLAG_SKIP_MULTICAST, PIP_ADAPTER_ADDRESSES,
};

use crate::common::MacAddr;

// Need a function to convert u16 pointer into String
// https://stackoverflow.com/a/48587463/8706476
unsafe fn u16_ptr_to_string(ptr: *const u16) -> OsString {
    let len = (0..).take_while(|&i| *ptr.offset(i) != 0).count();
    let slice = std::slice::from_raw_parts(ptr, len);

    OsString::from_wide(slice)
}

/// this iterator yields an interface name and address
pub(crate) struct InterfaceAddressIterator {
    /// The first item in the linked list
    buf: PIP_ADAPTER_ADDRESSES,
    /// The current adapter
    adapter: PIP_ADAPTER_ADDRESSES,
}

impl InterfaceAddressIterator {
    fn new() -> Self {
        Self {
            buf: null_mut(),
            adapter: null_mut(),
        }
    }
    unsafe fn realloc(mut self, size: libc::size_t) -> Result<Self, String> {
        let new_buf = libc::realloc(self.buf as _, size) as PIP_ADAPTER_ADDRESSES;
        if new_buf.is_null() {
            // insufficient memory available
            // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/malloc?view=msvc-170#return-value
            // malloc is not documented to set the last-error code
            Err("failed to allocate memory for IP_ADAPTER_ADDRESSES".to_string())
        } else {
            self.buf = new_buf;
            self.adapter = new_buf;
            Ok(self)
        }
    }
}

impl Iterator for InterfaceAddressIterator {
    type Item = (String, MacAddr);

    fn next(&mut self) -> Option<Self::Item> {
        if self.adapter.is_null() {
            return None;
        }
        unsafe {
            let adapter = self.adapter;
            // Move to the next adapter
            self.adapter = (*adapter).Next;
            if let Ok(interface_name) = u16_ptr_to_string((*adapter).FriendlyName).into_string() {
                // take the first 6 bytes and return the MAC address instead
                let [mac @ .., _, _] = (*adapter).PhysicalAddress;
                Some((interface_name, MacAddr(mac)))
            } else {
                // Not sure whether error can occur when parsing adapter name.
                self.next()
            }
        }
    }
}

impl Drop for InterfaceAddressIterator {
    fn drop(&mut self) {
        unsafe {
            libc::free(self.buf as _);
        }
    }
}

pub(crate) fn get_interface_address() -> Result<InterfaceAddressIterator, String> {
    unsafe {
        // https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses#remarks
        // A 15k buffer is recommended
        let mut size: u32 = 15 * 1024;
        let mut ret = ERROR_SUCCESS;
        let mut iterator = InterfaceAddressIterator::new();

        // https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses#examples
        // Try to retrieve adapter information up to 3 times
        for _ in 0..3 {
            iterator = iterator.realloc(size as _)?;
            ret = GetAdaptersAddresses(
                AF_UNSPEC as u32,
                GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_DNS_SERVER,
                null_mut(),
                iterator.buf,
                &mut size,
            );
            if ret == ERROR_SUCCESS {
                return Ok(iterator);
            } else if ret != ERROR_BUFFER_OVERFLOW {
                break;
            }
            // if the given memory size is too small to hold the adapter information,
            // the SizePointer returned will point to the required size of the buffer,
            // and we should continue.
            // Otherwise, break the loop and check the return code again
        }

        Err(format!("GetAdaptersAddresses() failed with code {ret}"))
    }
}
