use std::{
    io,
    mem::size_of,
    os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
    ptr::null_mut,
    time::Duration,
};

use crate::sys::TimerFd;

pub struct TimerFdImpl {
    fd: OwnedFd,
}

impl TimerFd for TimerFdImpl {
    fn new() -> io::Result<Self> {
        let fd = unsafe {
            libc::timerfd_create(
                libc::CLOCK_MONOTONIC,
                libc::TFD_NONBLOCK | libc::TFD_CLOEXEC,
            )
        };
        if fd < 0 {
            return Err(io::Error::last_os_error());
        }
        let fd = unsafe { OwnedFd::from_raw_fd(fd) };
        Ok(Self { fd })
    }

    fn start(&mut self, timeout: Duration) -> io::Result<()> {
        let tv_sec = timeout.as_secs() as _;
        let tv_nsec = timeout.subsec_nanos().into();
        let spec = libc::itimerspec {
            it_value: libc::timespec { tv_sec, tv_nsec },
            it_interval: libc::timespec {
                tv_sec: 0,
                tv_nsec: 0,
            },
        };
        let res = unsafe { libc::timerfd_settime(self.fd.as_raw_fd(), 0, &spec, null_mut()) };
        if res >= 0 {
            Ok(())
        } else {
            Err(io::Error::last_os_error())
        }
    }

    fn is_expired(&mut self) -> io::Result<bool> {
        let mut buf = [0; size_of::<u64>()];
        let len = unsafe { libc::read(self.fd.as_raw_fd(), buf.as_mut_ptr() as _, buf.len()) };
        if len != 8 {
            match io::Error::last_os_error() {
                e if e.kind() == io::ErrorKind::WouldBlock => return Ok(false),
                e => return Err(e),
            }
        }
        Ok(true)
    }
}

impl AsRawFd for TimerFdImpl {
    fn as_raw_fd(&self) -> RawFd {
        self.fd.as_raw_fd()
    }
}
