//! An interface to the Linux uinput kernel module that can be used to create
//! virtual input devices.

use std::{io, fs, ptr};
use std::os::unix::io::{RawFd, AsRawFd, IntoRawFd, FromRawFd};
use std::os::unix::ffi::OsStrExt;
use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
use std::os::raw::c_char;
use std::mem::{MaybeUninit, size_of};
use std::path::{Path, PathBuf};
use std::slice::{from_raw_parts, from_raw_parts_mut};
use std::ffi::{OsStr, OsString, CStr};
use crate::sys;
use nix;
use crate::{Key, InputId, AbsoluteInfoSetup, kinds};
use crate::macros::convert_error;

pub use crate::sys::{UINPUT_MAX_NAME_SIZE, UINPUT_VERSION};

/// A handle to a uinput allowing the use of ioctls
pub struct UInputHandle<F>(F);

fn copy_name(dest: &mut [c_char; UINPUT_MAX_NAME_SIZE as usize], name: &[u8]) -> io::Result<()> {
    if name.len() >= UINPUT_MAX_NAME_SIZE as usize {
        Err(io::Error::new(io::ErrorKind::InvalidInput, "name too long"))
    } else {
        unsafe {
            ptr::copy_nonoverlapping(name.as_ptr() as *const _, dest.as_mut_ptr() as *mut _, name.len());
        }
        dest[name.len()] = 0;

        Ok(())
    }
}

impl<F> UInputHandle<F> {
    /// Create a new handle using an existing open file object.
    pub const fn new(fd: F) -> Self {
        UInputHandle(fd)
    }

    /// Extracts the contained handle.
    pub fn into_inner(self) -> F {
        self.0
    }

    /// A reference to the contained handle.
    pub const fn as_inner(&self) -> &F {
        &self.0
    }

    /// A mutable reference to the contained handle.
    pub fn as_inner_mut(&mut self) -> &mut F {
        &mut self.0
    }
}

impl<F: AsRawFd> AsFd for UInputHandle<F> {
    fn as_fd<'a>(&'a self) -> BorrowedFd<'a> {
        unsafe {
            BorrowedFd::borrow_raw(self.fd())
        }
    }
}

impl<F: IntoRawFd> IntoRawFd for UInputHandle<F> {
    fn into_raw_fd(self) -> RawFd {
        self.0.into_raw_fd()
    }
}

impl<F: FromRawFd> FromRawFd for UInputHandle<F> {
    unsafe fn from_raw_fd(fd: RawFd) -> Self {
        UInputHandle(FromRawFd::from_raw_fd(fd))
    }
}

impl UInputHandle<OwnedFd> {
    /// Create a new handle from a raw file descriptor.
    pub unsafe fn from_fd(fd: RawFd) -> Self {
        FromRawFd::from_raw_fd(fd)
    }
}

impl<F: AsRawFd> UInputHandle<F> {
    #[inline]
    fn fd(&self) -> RawFd {
        self.0.as_raw_fd()
    }

    /// Create a new uinput device using the legacy `UI_DEV_CREATE` interface
    pub fn create_legacy(&self, id: &InputId, name: &[u8], ff_effects_max: u32, abs: &[AbsoluteInfoSetup]) -> io::Result<()> {
        let mut setup: sys::uinput_user_dev = unsafe { MaybeUninit::zeroed().assume_init() };
        setup.id = (*id).into();
        setup.ff_effects_max = ff_effects_max;

        copy_name(&mut setup.name, name)?;

        for abs in abs {
            let code = abs.axis as usize;
            if code >= sys::ABS_CNT as _ {
                return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid abs axis code"))
            }

            let abs = &abs.info;
            setup.absmax[code] = abs.maximum;
            setup.absmin[code] = abs.minimum;
            setup.absfuzz[code] = abs.fuzz;
            setup.absflat[code] = abs.flat;
        }

        let setup = unsafe { from_raw_parts(&setup as *const _ as *const u8, size_of::<sys::uinput_user_dev>()) };
        nix::unistd::write(self, setup).map_err(convert_error)?;

        self.dev_create()
    }

    /// Create a new uinput device, and fall back on the legacy interface if necessary
    pub fn create(&self, id: &InputId, name: &[u8], ff_effects_max: u32, abs: &[AbsoluteInfoSetup]) -> io::Result<()> {
        let mut setup: sys::uinput_setup = unsafe { MaybeUninit::zeroed().assume_init() };
        setup.id = (*id).into();
        setup.ff_effects_max = ff_effects_max;

        copy_name(&mut setup.name, name)?;

        match self.dev_setup(&setup) {
            Err(ref e) if e.raw_os_error() == Some(sys::Errno::EINVAL as _) =>
                return self.create_legacy(id, name, ff_effects_max, abs),
            v => v,
        }?;

        for abs in abs {
            self.abs_setup(abs.into())?;
        }

        self.dev_create()
    }

    /// Write input events to the device
    pub fn write(&self, events: &[sys::input_event]) -> io::Result<usize> {
        let events = unsafe { from_raw_parts(events.as_ptr() as *const u8, size_of::<sys::input_event>() * events.len()) };
        nix::unistd::write(self, events)
            .map(|c| c / size_of::<sys::input_event>()).map_err(convert_error)
    }

    /// Read events from uinput (see `EV_UINPUT`)
    pub fn read(&self, events: &mut [sys::input_event]) -> io::Result<usize> {
        let events = unsafe { from_raw_parts_mut(events.as_mut_ptr() as *mut u8, size_of::<sys::input_event>() * events.len()) };
        nix::unistd::read(self.fd(), events)
            .map(|len| len / size_of::<sys::input_event>()).map_err(convert_error)
    }

    /// Returns the sysfs directory for the input device.
    ///
    /// Note that this path may not exist if sysfs is not mounted in the standard `/sys` location.
    pub fn sys_path(&self) -> io::Result<PathBuf> {
        let sys = self.sys_name()?;
        let sys = CStr::from_bytes_with_nul(&sys).map(|c| c.to_bytes()).unwrap_or(&sys);
        Ok(Path::new("/sys/devices/virtual/input/").join(OsStr::from_bytes(sys)))
    }

    /// The device name of the input device.
    pub fn evdev_name(&self) -> io::Result<OsString> {
        let sys = self.sys_path()?;
        fs::read_dir(&sys)?.filter_map(|e| match e {
            Err(err) => Some(Err(err)),
            Ok(e) => match e.file_type() {
                Err(err) => Some(Err(err)),
                Ok(ty) if ty.is_dir() => {
                    let name = e.file_name();
                    if name.as_bytes().starts_with(b"event") {
                        Some(Ok(e.file_name()))
                    } else {
                        None
                    }
                },
                Ok(..) => None,
            },
        }).next().unwrap_or_else(|| Err(io::Error::new(io::ErrorKind::NotFound, "event input device not found")))
    }

    /// The device node path of the input device.
    ///
    /// Note that this path may not exist if `/dev/input/*` isn't mounted properly.
    pub fn evdev_path(&self) -> io::Result<PathBuf> {
        self.evdev_name().map(|ev| Path::new("/dev/input/").join(ev))
    }

    ioctl_impl! {
        {
            /// `UI_DEV_CREATE`
            @call dev_create = ui_dev_create
        }
        {
            /// `UI_DEV_DESTROY`
            @call dev_destroy = ui_dev_destroy
        }
        {
            /// `UI_DEV_SETUP`
            @set dev_setup(&sys::uinput_setup) = ui_dev_setup
        }
        {
            /// `UI_ABS_SETUP`
            @set abs_setup(&sys::uinput_abs_setup) = ui_abs_setup
        }
        {
            /// `UI_SET_EVBIT`
            @set set_evbit(kinds::EventKind) = ui_set_evbit
        }
        {
            /// `UI_SET_KEYBIT`
            @set set_keybit(Key) = ui_set_keybit
        }
        {
            /// `UI_SET_RELBIT`
            @set set_relbit(kinds::RelativeAxis) = ui_set_relbit
        }
        {
            /// `UI_SET_ABSBIT`
            @set set_absbit(kinds::AbsoluteAxis) = ui_set_absbit
        }
        {
            /// `UI_SET_MSCBIT`
            @set set_mscbit(kinds::MiscKind) = ui_set_mscbit
        }
        {
            /// `UI_SET_LEDBIT`
            @set set_ledbit(kinds::LedKind) = ui_set_ledbit
        }
        {
            /// `UI_SET_SNDBIT`
            @set set_sndbit(kinds::SoundKind) = ui_set_sndbit
        }
        {
            /// `UI_SET_FFBIT`
            @set set_ffbit(kinds::ForceFeedbackKind) = ui_set_ffbit
        }
        {
            /// `UI_SET_PHYS`
            @set_str set_phys = ui_set_phys
        }
        {
            /// `UI_SET_SWBIT`
            @set set_swbit(kinds::SwitchKind) = ui_set_swbit
        }
        {
            /// `UI_SET_PROPBIT`
            @set set_propbit(kinds::InputProperty) = ui_set_propbit
        }
        {
            /// `UI_BEGIN_FF_UPLOAD`
            @set ff_upload_begin(&mut sys::uinput_ff_upload) = ui_begin_ff_upload
        }
        {
            /// `UI_END_FF_UPLOAD`
            @set ff_upload_end(&sys::uinput_ff_upload) = ui_end_ff_upload
        }
        {
            /// `UI_BEGIN_FF_ERASE`
            @set ff_erase_begin(&mut sys::uinput_ff_erase) = ui_begin_ff_erase
        }
        {
            /// `UI_END_FF_ERASE`
            @set ff_erase_end(&sys::uinput_ff_erase) = ui_end_ff_erase
        }
        {
            /// `UI_GET_SYSNAME`
            @get_str sys_name, sys_name_buf = ui_get_sysname
        }
        {
            /// `UI_GET_VERSION`
            @get version = ui_get_version -> u32
        }
    }
}
