use std;
use std::error::Error;
use std::io::Cursor;
use std::os::unix::io::RawFd;
use byteorder::{NativeEndian, WriteBytesExt};
use nix;
use nix::sys::socket;
use nix::sys::uio;
use defs::{Logger, SkylaneError};
macro_rules! try_sock {
    ($action:expr, $path:expr, $expr:expr) => {
        match $expr {
            Ok(result) => result,
            Err(err) => {
                return Err(SkylaneError::Other(
                    format!("{} {:?}: {:?}", $action, $path, err.description())
                ));
            }
        }
    }
}
pub fn get_default_socket_path() -> Result<std::path::PathBuf, SkylaneError> {
    let mut path = std::path::PathBuf::from(std::env::var("XDG_RUNTIME_DIR")?);
    if let Ok(sock) = std::env::var("WAYLAND_DISPLAY") {
        path.push(sock);
    } else {
        path.push("wayland-0");
    }
    Ok(path)
}
#[derive(Clone)]
pub struct Socket {
    fd: RawFd,
    next_serial: std::cell::Cell<u32>,
    logger: Logger,
}
impl Socket {
        pub fn connect(path: &std::path::Path) -> Result<Self, SkylaneError> {
        let sockfd = try_sock!("Creating",
                               path,
                               socket::socket(socket::AddressFamily::Unix,
                                              socket::SockType::Stream,
                                              socket::SOCK_CLOEXEC,
                                              0));
        let unix_addr = try_sock!("Linking", path, socket::UnixAddr::new(path));
        let sock_addr = socket::SockAddr::Unix(unix_addr);
        try_sock!("Connecting", path, socket::connect(sockfd, &sock_addr));
        Ok(Socket {
               fd: sockfd,
                next_serial: std::cell::Cell::new(0),
                logger: None,
           })
    }
                pub fn connect_default() -> Result<Self, SkylaneError> {
        let path = get_default_socket_path()?;
        Self::connect(&path)
    }
        pub fn get_fd(&self) -> RawFd {
        self.fd
    }
        pub fn get_next_serial(&self) -> u32 {
        let serial = self.next_serial.get();
        self.next_serial.set(serial + 1);
        serial
    }
        pub fn set_logger(&mut self, logger: Logger) {
        self.logger = logger;
    }
        pub fn get_logger(&self) -> Logger {
        self.logger
    }
                            pub fn receive_message(&self,
                           bytes: &mut [u8],
                           fds: &mut [u8])
                           -> Result<(usize, usize), SkylaneError> {
        let mut cmsg: socket::CmsgSpace<[RawFd; 1]> = socket::CmsgSpace::new();
        let mut iov: [uio::IoVec<&mut [u8]>; 1] = [uio::IoVec::from_mut_slice(&mut bytes[..]); 1];
        let msg = socket::recvmsg(self.fd, &mut iov[..], Some(&mut cmsg), socket::MSG_DONTWAIT)?;
        let mut num_fds = 0;
        let mut buf = Cursor::new(fds);
        for cmsg in msg.cmsgs() {
            match cmsg {
                socket::ControlMessage::ScmRights(newfds) => {
                    buf.write_i32::<NativeEndian>(newfds[0])?;
                    num_fds += 1;
                }
                _ => {}
            }
        }
        Ok((msg.bytes, num_fds))
    }
        pub fn write(&self, bytes: &[u8]) -> Result<(), SkylaneError> {
        let iov: [uio::IoVec<&[u8]>; 1] = [uio::IoVec::from_slice(&bytes[..]); 1];
        let cmsgs: [socket::ControlMessage; 0] = unsafe { std::mem::uninitialized() };
        socket::sendmsg(self.fd, &iov[..], &cmsgs[..], socket::MSG_DONTWAIT, None)?;
        Ok(())
    }
        pub fn write_with_control_data(&self, bytes: &[u8], fds: &[RawFd]) -> Result<(), SkylaneError> {
        let iov: [uio::IoVec<&[u8]>; 1] = [uio::IoVec::from_slice(&bytes[..]); 1];
        let cmsgs = [socket::ControlMessage::ScmRights(fds)];
        socket::sendmsg(self.fd, &iov[..], &cmsgs[..], socket::MSG_DONTWAIT, None)?;
        Ok(())
    }
}
impl Socket {
                fn new(fd: RawFd) -> Self {
        Socket {
            fd: fd,
            next_serial: std::cell::Cell::new(0),
            logger: None,
        }
    }
}
#[derive(Clone)]
pub struct DisplaySocket {
    fd: RawFd,
    path: std::path::PathBuf,
}
impl DisplaySocket {
        pub fn new(path: &std::path::Path) -> Result<Self, SkylaneError> {
        let sockfd = try_sock!("Creating",
                               path,
                               socket::socket(socket::AddressFamily::Unix,
                                              socket::SockType::Stream,
                                              socket::SOCK_CLOEXEC,
                                              0));
        let unix_addr = try_sock!("Linking", path, socket::UnixAddr::new(path));
        let sock_addr = socket::SockAddr::Unix(unix_addr);
        try_sock!("Binding", path, socket::bind(sockfd, &sock_addr));
        try_sock!("Listening", path, socket::listen(sockfd, 128));
        Ok(DisplaySocket {
               fd: sockfd,
               path: path.to_owned(),
           })
    }
                pub fn new_default() -> Result<Self, SkylaneError> {
        let path = get_default_socket_path()?;
        Self::new(&path)
    }
        pub fn accept(&self) -> Result<Socket, SkylaneError> {
        let fd = socket::accept(self.fd)?;
        Ok(Socket::new(fd))
    }
        pub fn get_fd(&self) -> RawFd {
        self.fd
    }
}
impl Drop for DisplaySocket {
    fn drop(&mut self) {
                let _ = nix::unistd::unlink(self.path.as_path());
    }
}