#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os))]

use std::{
    io,
    marker::PhantomData,
    ops::Deref,
    os::fd::{AsRawFd, OwnedFd, RawFd},
    path::Path,
    sync::Arc,
};

use cross::net::{LocalPacketSocket, OwnedLocalAddress};
use serde::{de::DeserializeOwned, Serialize};

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("{0}")]
    Io(#[from] io::Error),
    #[error("{0}")]
    Serde(#[from] postcard::Error),
}

pub struct Channel(LocalPacketSocket);

pub struct Sender<T> {
    channel: Arc<Channel>,
    _pd: PhantomData<T>,
}

pub struct Receiver<T> {
    channel: Arc<Channel>,
    buffer: [u8; 4096],
    _pd: PhantomData<T>,
}

impl Error {
    pub fn is_would_block(&self) -> bool {
        matches!(self, Self::Io(error) if error.kind() == io::ErrorKind::WouldBlock)
    }
}

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

impl Channel {
    pub fn connect<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
        let socket = LocalPacketSocket::connect(path)?;
        Ok(Self(socket))
    }

    pub fn bind<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
        let socket = LocalPacketSocket::bind(path)?;
        Ok(Self(socket))
    }

    pub fn set_nonblocking(&self, nb: bool) -> Result<(), Error> {
        self.0.set_nonblocking(nb)?;
        Ok(())
    }

    pub fn split<T: Serialize, U: DeserializeOwned>(self) -> (Sender<T>, Receiver<U>) {
        let this = Arc::new(self);
        let sender = Sender {
            channel: this.clone(),
            _pd: PhantomData,
        };
        let receiver = Receiver {
            channel: this,
            buffer: [0; 4096],
            _pd: PhantomData,
        };
        (sender, receiver)
    }
}

impl Deref for Channel {
    type Target = LocalPacketSocket;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T: Serialize> Sender<T> {
    pub fn send(&self, message: &T) -> Result<usize, Error> {
        let mut buffer = [0; 512];
        let payload = postcard::to_slice(message, &mut buffer)?;
        let len = self.channel.send(payload)?;
        Ok(len)
    }

    pub fn send_with_file<F: AsRawFd>(&self, message: &T, file: &F) -> Result<usize, Error> {
        let mut buffer = [0; 512];
        let ancillary = file.as_raw_fd();
        let payload = postcard::to_slice(message, &mut buffer)?;
        let len = self.channel.send_with_ancillary(payload, ancillary)?;
        Ok(len)
    }

    pub fn send_to(&self, message: &T, destination: &PeerAddress) -> Result<usize, Error> {
        let mut buffer = [0; 512];
        let payload = postcard::to_slice(message, &mut buffer)?;
        let len = self.channel.send_to(payload, &destination.0)?;
        Ok(len)
    }

    pub fn send_with_file_to<F: AsRawFd>(
        &self,
        message: &T,
        file: &F,
        destination: &PeerAddress,
    ) -> Result<usize, Error> {
        let mut buffer = [0; 512];
        let ancillary = file.as_raw_fd();
        let payload = postcard::to_slice(message, &mut buffer)?;
        let len = self
            .channel
            .send_with_ancillary_to(payload, ancillary, &destination.0)?;
        Ok(len)
    }
}

impl<T> AsRawFd for Sender<T> {
    fn as_raw_fd(&self) -> RawFd {
        self.channel.as_raw_fd()
    }
}

impl<T: DeserializeOwned> Receiver<T> {
    pub fn receive(&mut self) -> Result<T, Error> {
        let len = self.channel.receive(&mut self.buffer)?;
        let message = postcard::from_bytes(&self.buffer[..len])?;
        Ok(message)
    }

    pub fn receive_with_file(&mut self) -> Result<(T, Option<OwnedFd>), Error> {
        let (len, fd) = self.channel.receive_with_ancillary(&mut self.buffer)?;
        let message = postcard::from_bytes(&self.buffer[..len])?;
        Ok((message, fd))
    }

    pub fn receive_from(&mut self) -> Result<(T, PeerAddress), Error> {
        let (len, address) = self.channel.receive_from(&mut self.buffer)?;
        let message = postcard::from_bytes(&self.buffer[..len])?;
        Ok((message, PeerAddress(address)))
    }

    pub fn receive_with_file_from(&mut self) -> Result<(T, Option<OwnedFd>, PeerAddress), Error> {
        let (len, fd, address) = self.channel.receive_with_ancillary_from(&mut self.buffer)?;
        let message = postcard::from_bytes(&self.buffer[..len])?;
        Ok((message, fd, PeerAddress(address)))
    }
}

impl<T> AsRawFd for Receiver<T> {
    fn as_raw_fd(&self) -> RawFd {
        self.channel.as_raw_fd()
    }
}
