use libc;
use std::io;
use std::net::Shutdown;
use std::os::unix::net;
use std::os::unix::prelude::*;
use std::path::Path;

use super::cvt;
use super::socket::{sockaddr_un, Socket};
use crate::lxar::event::{Evented, EventedFd, PollOpt, Ready};
use crate::lxar::{Poll, Token};

/// A Unix datagram socket.
#[derive(Debug)]
pub struct UnixDatagram {
    inner: net::UnixDatagram,
}

impl UnixDatagram {
    /// Creates a Unix datagram socket bound to the given path.
    pub fn bind<P: AsRef<Path>>(path: P) -> io::Result<UnixDatagram> {
        UnixDatagram::_bind(path.as_ref())
    }

    fn _bind(path: &Path) -> io::Result<UnixDatagram> {
        unsafe {
            let (addr, len) = sockaddr_un(path)?;
            let fd = Socket::new(libc::SOCK_DGRAM)?;

            let addr = &addr as *const _ as *const _;
            cvt(libc::bind(fd.fd(), addr, len))?;

            Ok(UnixDatagram::from_raw_fd(fd.into_fd()))
        }
    }

    /// Consumes a standard library `UnixDatagram` and returns a wrapped
    /// `UnixDatagram` compatible with mio.
    ///
    /// The returned stream is moved into nonblocking mode and is otherwise
    /// ready to get associated with an event loop.
    pub fn from_datagram(stream: net::UnixDatagram) -> io::Result<UnixDatagram> {
        stream.set_nonblocking(true)?;
        Ok(UnixDatagram { inner: stream })
    }

    /// Create an unnamed pair of connected sockets.
    ///
    /// Returns two `UnixDatagrams`s which are connected to each other.
    pub fn pair() -> io::Result<(UnixDatagram, UnixDatagram)> {
        unsafe {
            let (a, b) = Socket::pair(libc::SOCK_DGRAM)?;
            Ok((
                UnixDatagram::from_raw_fd(a.into_fd()),
                UnixDatagram::from_raw_fd(b.into_fd()),
            ))
        }
    }

    /// Creates a Unix Datagram socket which is not bound to any address.
    pub fn unbound() -> io::Result<UnixDatagram> {
        let stream = net::UnixDatagram::unbound()?;
        stream.set_nonblocking(true)?;
        Ok(UnixDatagram { inner: stream })
    }

    /// Connects the socket to the specified address.
    ///
    /// The `send` method may be used to send data to the specified address.
    /// `recv` and `recv_from` will only receive data from that address.
    pub fn connect<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
        self.inner.connect(path)
    }

    /// Creates a new independently owned handle to the underlying socket.
    ///
    /// The returned `UnixListener` is a reference to the same socket that this
    /// object references. Both handles can be used to accept incoming
    /// connections and options set on one listener will affect the other.
    pub fn try_clone(&self) -> io::Result<UnixDatagram> {
        self.inner.try_clone().map(|i| UnixDatagram { inner: i })
    }

    /// Returns the address of this socket.
    pub fn local_addr(&self) -> io::Result<net::SocketAddr> {
        self.inner.local_addr()
    }

    /// Returns the address of this socket's peer.
    ///
    /// The `connect` method will connect the socket to a peer.
    pub fn peer_addr(&self) -> io::Result<net::SocketAddr> {
        self.inner.peer_addr()
    }

    /// Receives data from the socket.
    ///
    /// On success, returns the number of bytes read and the address from
    /// whence the data came.
    pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, net::SocketAddr)> {
        self.inner.recv_from(buf)
    }

    /// Receives data from the socket.
    ///
    /// On success, returns the number of bytes read.
    pub fn recv(&self, buf: &mut [u8]) -> io::Result<usize> {
        self.inner.recv(buf)
    }

    /// Sends data on the socket to the specified address.
    ///
    /// On success, returns the number of bytes written.
    pub fn send_to<P: AsRef<Path>>(&self, buf: &[u8], path: P) -> io::Result<usize> {
        self.inner.send_to(buf, path)
    }

    /// Sends data on the socket to the socket's peer.
    ///
    /// The peer address may be set by the `connect` method, and this method
    /// will return an error if the socket has not already been connected.
    ///
    /// On success, returns the number of bytes written.
    pub fn send(&self, buf: &[u8]) -> io::Result<usize> {
        self.inner.send(buf)
    }

    /// Returns the value of the `SO_ERROR` option.
    pub fn take_error(&self) -> io::Result<Option<io::Error>> {
        self.inner.take_error()
    }

    /// Shut down the read, write, or both halves of this connection.
    ///
    /// This function will cause all pending and future I/O calls on the
    /// specified portions to immediately return with an appropriate value
    /// (see the documentation of `Shutdown`).
    pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
        self.inner.shutdown(how)
    }
}

impl Evented for UnixDatagram {
    fn register(&self, poll: &Poll, token: Token, events: Ready, opts: PollOpt) -> io::Result<()> {
        EventedFd(&self.as_raw_fd()).register(poll, token, events, opts)
    }

    fn reregister(
        &self,
        poll: &Poll,
        token: Token,
        events: Ready,
        opts: PollOpt,
    ) -> io::Result<()> {
        EventedFd(&self.as_raw_fd()).reregister(poll, token, events, opts)
    }

    fn deregister(&self, poll: &Poll) -> io::Result<()> {
        EventedFd(&self.as_raw_fd()).deregister(poll)
    }
}

impl AsRawFd for UnixDatagram {
    fn as_raw_fd(&self) -> i32 {
        self.inner.as_raw_fd()
    }
}

impl IntoRawFd for UnixDatagram {
    fn into_raw_fd(self) -> i32 {
        self.inner.into_raw_fd()
    }
}

impl FromRawFd for UnixDatagram {
    unsafe fn from_raw_fd(fd: i32) -> UnixDatagram {
        UnixDatagram {
            inner: net::UnixDatagram::from_raw_fd(fd),
        }
    }
}
