use std::{
    collections::{hash_map::Entry, HashMap},
    io,
    mem::MaybeUninit,
    os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
    ptr::null_mut,
    time::Duration,
};

use crate::sys::Poll;

pub struct PollImpl {
    epoll_fd: OwnedFd,
    event_map: HashMap<u64, RawFd>,
    reverse_map: HashMap<RawFd, u64>,
    last_event_key: u64,
}

impl PollImpl {
    fn insert(&mut self, fd: RawFd) -> u64 {
        // Theoretically, might hang forever, but if you have u64::MAX keys in the map
        // your program's gonna die long before that
        loop {
            let key = self.last_event_key.wrapping_add(1);
            match self.event_map.entry(key) {
                Entry::Occupied(_) => continue,
                Entry::Vacant(entry) => {
                    entry.insert(fd);
                    self.reverse_map.insert(fd, key);
                    self.last_event_key = key;
                    break key;
                }
            }
        }
    }
}

impl Poll for PollImpl {
    fn new() -> io::Result<Self> {
        let epoll_fd = unsafe { libc::epoll_create1(0) };
        if epoll_fd < 0 {
            return Err(io::Error::last_os_error());
        }
        let epoll_fd = unsafe { OwnedFd::from_raw_fd(epoll_fd) };
        Ok(Self {
            epoll_fd,
            event_map: HashMap::new(),
            reverse_map: HashMap::new(),
            last_event_key: 1,
        })
    }

    fn add<F: AsRawFd>(&mut self, interest: &F) -> io::Result<()> {
        let fd = interest.as_raw_fd();
        let key = self.insert(fd);
        let mut ev = libc::epoll_event {
            events: libc::EPOLLIN as u32,
            u64: key,
        };

        let res =
            unsafe { libc::epoll_ctl(self.epoll_fd.as_raw_fd(), libc::EPOLL_CTL_ADD, fd, &mut ev) };
        if res >= 0 {
            Ok(())
        } else {
            Err(io::Error::last_os_error())
        }
    }

    fn remove<F: AsRawFd>(&mut self, interest: &F) -> io::Result<()> {
        // TODO
        let fd = interest.as_raw_fd();
        let key = self.reverse_map.remove(&fd).unwrap();
        self.event_map.remove(&key).unwrap();
        let res = unsafe {
            libc::epoll_ctl(
                self.epoll_fd.as_raw_fd(),
                libc::EPOLL_CTL_DEL,
                fd,
                null_mut(),
            )
        };
        if res >= 0 {
            Ok(())
        } else {
            Err(io::Error::last_os_error())
        }
    }

    fn wait(&mut self, timeout: Option<Duration>) -> io::Result<Option<RawFd>> {
        let mut ev = MaybeUninit::uninit();
        let timeout = match timeout {
            Some(duration) => duration.as_millis().try_into().unwrap(),
            None => -1,
        };

        // TODO handle interrupts
        let res =
            unsafe { libc::epoll_wait(self.epoll_fd.as_raw_fd(), ev.as_mut_ptr(), 1, timeout) };
        if res < 0 {
            return Err(io::Error::last_os_error());
        }

        match res {
            1 => {
                let key = unsafe { ev.assume_init_ref().u64 };
                let fd = self.event_map.get(&key).unwrap();
                Ok(Some(*fd))
            }
            0 => {
                debug_assert_ne!(timeout, -1);
                Ok(None)
            }
            _ => unreachable!(),
        }
    }
}

impl AsRawFd for PollImpl {
    fn as_raw_fd(&self) -> RawFd {
        self.epoll_fd.as_raw_fd()
    }
}
