use std::io;
use crate::sys;

pub(crate) const STRING_BUFFER_LENGTH: usize = 0x200;

pub(crate) fn convert_error(e: sys::Error) -> io::Error {
    e.into()
}

macro_rules! impl_iterable {
    (@impliter $name: ident($start:expr, $count:expr)) => {
        impl crate::bitmask::BitmaskTrait for $name {
            type Array = [u8; (Self::COUNT + 7) / 8];
            type Index = $name;
            const ZERO: Self::Array = [0u8; (Self::COUNT + 7) / 8];

            fn array_default() -> Self::Array { unsafe {std::mem::zeroed() } }
            fn array_slice(array: &Self::Array) -> &[u8] { array }
            fn array_slice_mut(array: &mut Self::Array) -> &mut [u8] { array }
            fn index(index: Self::Index) -> usize { index as usize }
        }

        impl crate::enum_iterator::IterableEnum for $name {
            fn iter_next(v: usize) -> Option<(usize, Self)> {
                if v < Self::COUNT {
                    unsafe {
                        Some((v + 1, std::mem::transmute(v as u16)))
                    }
                } else {
                    None
                }
            }

            fn iter() -> crate::enum_iterator::EnumIterator<Self> {
                crate::enum_iterator::EnumIterator::new($start)
            }
        }

        impl $name {
            /// The maximum valid value.
            pub const COUNT: usize = $count as usize;

            /// An iterator over all values of the enum.
            pub fn iter() -> crate::enum_iterator::EnumIterator<Self> {
                crate::enum_iterator::IterableEnum::iter()
            }

            /// A bitmask that can contain all values of the enum.
            pub fn bitmask() -> crate::bitmask::Bitmask<Self> {
                Default::default()
            }

            /// Get the raw [code](crate::InputEvent.code) value of the enum.
            pub const fn code(&self) -> u16 {
                *self as _
            }
        }

        impl From<$name> for u16 {
            fn from(v: $name) -> Self {
                v.code()
            }
        }
    };
    (@implcode $name: ident($start:expr, $count:expr)) => {
        impl $name {
            /// Instantiates the enum from a raw code value.
            pub fn from_code(code: u16) -> Result<Self, crate::kinds::RangeError> {
                use std::mem;

                if code < Self::COUNT as u16 {
                    Ok(unsafe { mem::transmute(code) })
                } else {
                    Err(crate::kinds::RangeError)
                }
            }
        }

        impl std::convert::TryFrom<u16> for $name {
            type Error = crate::kinds::RangeError;

            fn try_from(code: u16) -> Result<Self, Self::Error> {
                Self::from_code(code)
            }
        }
    };
    ($name: ident($start:expr, $count:expr)) => {
        impl_iterable! { @impliter $name($start, $count) }
        impl_iterable! { @implcode $name($start, $count) }
    };
    (@nofromcode $name: ident($start:expr, $count:expr)) => {
        impl_iterable! { @impliter $name($start, $count) }
    };
}

macro_rules! ioctl_impl {
    ($(#[$attr:meta])* @get $f:ident = $ev:ident -> $ret:ty) => {
        $(#[$attr])*
        pub fn $f(&self) -> io::Result<$ret> {
            unsafe {
                let mut v = std::mem::MaybeUninit::uninit();
                sys::$ev(self.fd(), &mut *v.as_mut_ptr())
                    .map(|_| v.assume_init().into())
                    .map_err(crate::macros::convert_error)
            }
        }
    };
    ($(#[$attr:meta])* @call $f:ident = $ev:ident) => {
        $(#[$attr])*
        pub fn $f(&self) -> io::Result<()> {
            unsafe {
                sys::$ev(self.fd())
                    .map(drop)
                    .map_err(crate::macros::convert_error)
            }
        }
    };
    ($(#[$attr:meta])* @get_buf $f:ident($ty:ty) = $ev:ident) => {
        $(#[$attr])*
        pub fn $f(&self, buffer: &mut [$ty]) -> io::Result<usize> {
            unsafe {
                sys::$ev(self.fd(), &mut *(buffer as *mut [$ty] as *mut [_]))
                    .map(|len| len as _)
                    .map_err(crate::macros::convert_error)
            }
        }
    };
    ($(#[$attr:meta])* @get_str $f:ident, $fbuf:ident = $ev:ident) => {
        ioctl_impl! {
            $(#[$attr])*
            @get_buf $fbuf(u8) = $ev
        }

        $(#[$attr])*
        pub fn $f(&self) -> io::Result<Vec<u8>> {
            let mut buf = vec![0; crate::macros::STRING_BUFFER_LENGTH];
            self.$fbuf(&mut buf[..]).map(move |len| {
                buf.truncate(len as _);
                buf
            })
        }
    };
    ($(#[$attr:meta])* @set_str $f:ident = $ev:ident) => {
        $(#[$attr])*
        pub fn $f(&self, value: &::std::ffi::CStr) -> io::Result<()> {
            unsafe {
                sys::$ev(self.fd(), value.as_ptr())
                    .map(drop)
                    .map_err(crate::macros::convert_error)
            }
        }
    };
    ($(#[$attr:meta])* @set $f:ident($in:ty) = $ev:ident) => {
        $(#[$attr])*
        pub fn $f(&self, value: $in) -> io::Result<()> {
            unsafe {
                sys::$ev(self.fd(), value as _)
                    .map(drop)
                    .map_err(crate::macros::convert_error)
            }
        }
    };
    ($({ $($tt:tt)* })*) => {
        $(
            ioctl_impl! {$($tt)*}
        )*
    };
}

macro_rules! impl_bitmasks {
    { $kind:path, $event: expr, $name_mask:ident, $name_mask_set:ident, $name_bits:ident } => {
        /// `EVIOCGMASK`
        pub fn $name_mask(&self) -> io::Result<crate::bitmask::Bitmask<$kind>> {
            let mut bitmask = crate::bitmask::Bitmask::default();
            self.event_mask_raw($event, &mut bitmask).map(|_| bitmask)
        }

        /// `EVIOCSMASK`
        pub fn $name_mask_set(&self, bitmask: &crate::bitmask::Bitmask<$kind>) -> io::Result<()> {
            self.set_event_mask_raw($event, bitmask)
        }

        /// `EVIOCGBIT`
        pub fn $name_bits(&self) -> io::Result<crate::bitmask::Bitmask<$kind>> {
            let mut bitmask = crate::bitmask::Bitmask::default();
            self.event_bits_raw($event, &mut bitmask).map(|_| bitmask)
        }
    };
}
