#![allow(non_camel_case_types,dead_code)]

use libc;

use std::io;
use std::time::Duration;

use libc::{c_int, c_short};

#[cfg(target_os = "linux")]
type nfds_t = libc::c_ulong;

#[cfg(not(target_os = "linux"))]
type nfds_t = libc::c_uint;

#[derive(Debug)]
#[repr(C)]
struct pollfd {
    fd: c_int,
    events: c_short,
    revents: c_short,
}

const POLLIN:   c_short = 0x0001;
const POLLPRI:  c_short = 0x0002;
const POLLOUT:  c_short = 0x0004;

const POLLERR:  c_short = 0x0008;
const POLLHUP:  c_short = 0x0010;
const POLLNVAL: c_short = 0x0020;

pub fn wait_read_fd(fd: c_int, timeout: Duration) -> io::Result<()> {
    wait_fd(fd, POLLIN, timeout)
}

pub fn wait_write_fd(fd: c_int, timeout: Duration) -> io::Result<()> {
    wait_fd(fd, POLLOUT, timeout)
}

fn wait_fd(fd: c_int, events: c_short, timeout: Duration) -> io::Result<()> {
    use libc::{EINTR, EPIPE, EIO};

    let mut fds = vec!(pollfd { fd: fd, events: events, revents: 0 });

    let wait = do_poll(&mut fds, timeout);

    if wait < 0 {
        let errno = super::error::errno();

        let kind = match errno {
            EINTR => io::ErrorKind::Interrupted,
            _ => io::ErrorKind::Other,
        };

        return Err(io::Error::new(kind, super::error::error_string(errno)));
    }

    if wait == 0 {
        return Err(io::Error::new(io::ErrorKind::TimedOut, "Operation timed out"));
    }

    if fds[0].revents & events != 0 {
        return Ok(());
    }

    if fds[0].revents & (POLLHUP | POLLNVAL) != 0 {
        return Err(io::Error::new(io::ErrorKind::BrokenPipe, super::error::error_string(EPIPE)));
    }

    Err(io::Error::new(io::ErrorKind::Other, super::error::error_string(EIO)))
}

#[cfg(target_os = "linux")]
#[inline]
fn do_poll(fds: &mut Vec<pollfd>, timeout: Duration) -> c_int {
    use std::ptr;

    use libc::c_void;

    #[repr(C)]
    struct sigset_t {
        __private: c_void,
    }

    extern "C" {
        fn ppoll(fds: *mut pollfd, nfds: nfds_t, timeout_ts: *mut libc::timespec, sigmask: *const sigset_t) -> c_int;
    }

    let mut timeout_ts = libc::timespec {
        tv_sec: timeout.as_secs() as libc::time_t,
        tv_nsec: timeout.subsec_nanos() as libc::c_long,
    };

    unsafe {
        ppoll((&mut fds[..]).as_mut_ptr(),
              fds.len() as nfds_t,
              &mut timeout_ts,
              ptr::null())
    }
}

#[cfg(not(target_os = "linux"))]
#[inline]
fn do_poll(fds: &mut Vec<pollfd>, timeout: Duration) -> c_int {
    extern "C" {
        fn poll(fds: *mut pollfd, nfds: nfds_t, timeout: c_int) -> c_int;
    }

    let milliseconds = timeout.as_secs() * 1000 + timeout.subsec_nanos() as u64 / 1_000_000;

    unsafe {
        poll((&mut fds[..]).as_mut_ptr(),
             fds.len() as nfds_t,
             milliseconds as c_int)
    }
}
