pub mod constants;
mod structs;

use constants::*;
use structs::{InputEvent, UInputSetup};

use nix::ioctl_none;
use nix::ioctl_write_int;
use nix::ioctl_write_ptr;

ioctl_write_int!(ui_set_evbit, b'U', 100);
ioctl_write_int!(ui_set_keybit, b'U', 101);
ioctl_write_ptr!(ui_dev_setup, b'U', 3, UInputSetup);
ioctl_none!(ui_dev_create, b'U', 1);
ioctl_none!(ui_dev_destroy, b'U', 2);

use std::os::unix::io::RawFd;
use std::thread::sleep;
use std::time::Duration;

pub struct InputDevice {
    fd: RawFd,
}

impl InputDevice {
    pub fn new() -> Self {
        let fd = Self::open_uinput();
        Self::setup_device(fd);
        Self { fd: fd }
    }

    pub fn click(&self, key: u16) {
        self.press(key);
        sleep(Duration::from_millis(5));
        self.release(key);
        sleep(Duration::from_millis(10));
    }

    pub fn press(&self, key: u16) {
        Self::emit(self.fd, EV_KEY, key, 1);
        Self::emit(self.fd, EV_SYN, SYN_REPORT, 0);
    }

    pub fn release(&self, key: u16) {
        Self::emit(self.fd, EV_KEY, key, 0);
        Self::emit(self.fd, EV_SYN, SYN_REPORT, 0);
    }

    fn open_uinput() -> RawFd {
        let path = std::ffi::CString::new("/dev/uinput").unwrap();
        let fd = unsafe { libc::open(path.as_ptr(), libc::O_WRONLY | libc::O_NONBLOCK) };
        if fd < 0 {
            panic!(
                "open /dev/uinput failed: {}",
                std::io::Error::last_os_error()
            );
        }
        return fd;
    }

    fn setup_device(fd: RawFd) {
        unsafe {
            ui_set_evbit(fd, EV_KEY as u64).unwrap();
            for key in KEY_ESC..=KEY_SCROLLLOCK {
                ui_set_keybit(fd, key as u64).unwrap();
            }

            let mut setup: UInputSetup = std::mem::zeroed();
            setup.id.bustype = BUS_USB;
            setup.id.vendor = 0x1234;
            setup.id.product = 0x5678;
            setup.id.version = 0;
            setup.ff_effects_max = 0;

            let name = b"Example device\0";
            setup.name[..name.len()].copy_from_slice(name);

            ui_dev_setup(fd, &setup).unwrap();
            ui_dev_create(fd).unwrap();
        }
        sleep(Duration::from_millis(500));
    }

    fn emit(fd: RawFd, type_: u16, code: u16, value: i32) {
        let ev = InputEvent {
            time: libc::timeval {
                tv_sec: 0,
                tv_usec: 0,
            },
            type_,
            code,
            value,
        };

        let size = std::mem::size_of::<InputEvent>();
        let ret = unsafe { libc::write(fd, &ev as *const _ as *const libc::c_void, size) };
        if ret != size as isize {
            panic!(
                "write failed or partial write: {}",
                std::io::Error::last_os_error()
            );
        }
    }
}

impl Drop for InputDevice {
    fn drop(&mut self) {
        unsafe {
            let _ = ui_dev_destroy(self.fd);
            let _ = libc::close(self.fd);
        }
    }
}
