use std::{
    io,
    os::fd::{AsRawFd, OwnedFd, RawFd},
    path::Path,
};

use crate::sys;

use self::sys::{LocalPacketSocket as SysLocalPacketSocket, OwnedAddress as SysOwnedAddress};

#[repr(transparent)]
pub struct LocalPacketSocket(sys::LocalPacketSocketImpl);

#[derive(Debug)]
#[repr(transparent)]
pub struct BorrowedLocalAddress<'a>(sys::BorrowedAddressImpl<'a>);

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
#[repr(transparent)]
pub struct OwnedLocalAddress(sys::OwnedAddressImpl);

impl LocalPacketSocket {
    pub fn new() -> io::Result<Self> {
        sys::LocalPacketSocketImpl::new().map(Self)
    }

    pub fn connect<P: AsRef<Path>>(path: P) -> io::Result<Self> {
        sys::LocalPacketSocketImpl::connect(path).map(Self)
    }

    pub fn bind<P: AsRef<Path>>(path: P) -> io::Result<Self> {
        sys::LocalPacketSocketImpl::bind(path).map(Self)
    }

    pub fn set_nonblocking(&self, nb: bool) -> io::Result<()> {
        self.0.set_nonblocking(nb)
    }

    pub fn receive(&self, buffer: &mut [u8]) -> io::Result<usize> {
        self.0.receive(buffer)
    }

    pub fn receive_from(&self, buffer: &mut [u8]) -> io::Result<(usize, OwnedLocalAddress)> {
        let (len, address) = self.0.receive_from(buffer)?;
        Ok((len, OwnedLocalAddress(address)))
    }

    pub fn receive_with_ancillary(
        &self,
        buffer: &mut [u8],
    ) -> io::Result<(usize, Option<OwnedFd>)> {
        self.0.receive_with_ancillary(buffer)
    }

    pub fn receive_with_ancillary_from(
        &self,
        buffer: &mut [u8],
    ) -> io::Result<(usize, Option<OwnedFd>, OwnedLocalAddress)> {
        let (len, fd, address) = self.0.receive_with_ancillary_from(buffer)?;
        Ok((len, fd, OwnedLocalAddress(address)))
    }

    pub fn send(&self, data: &[u8]) -> io::Result<usize> {
        self.0.send(data)
    }

    pub fn send_to<'a, A: Into<BorrowedLocalAddress<'a>>>(
        &self,
        data: &[u8],
        remote: A,
    ) -> io::Result<usize> {
        let remote = remote.into();
        self.0.send_to(data, &remote.0)
    }

    pub fn send_with_ancillary(&self, data: &[u8], ancillary: RawFd) -> io::Result<usize> {
        self.0.send_with_ancillary(data, ancillary)
    }

    pub fn send_with_ancillary_to<'a, A: Into<BorrowedLocalAddress<'a>>>(
        &self,
        data: &[u8],
        ancillary: RawFd,
        remote: A,
    ) -> io::Result<usize> {
        let remote = remote.into();
        self.0.send_with_ancillary_to(data, ancillary, &remote.0)
    }
}

impl AsRawFd for LocalPacketSocket {
    fn as_raw_fd(&self) -> RawFd {
        self.0.as_raw_fd()
    }
}

#[cfg(any(rust_analyzer, target_os = "yggdrasil"))]
impl<'a> From<&'a Path> for BorrowedLocalAddress<'a> {
    fn from(value: &'a Path) -> Self {
        Self(sys::BorrowedAddressImpl::from(value))
    }
}

impl<'a> From<&'a OwnedLocalAddress> for BorrowedLocalAddress<'a> {
    fn from(value: &'a OwnedLocalAddress) -> Self {
        Self(value.0.as_borrowed())
    }
}
