use std::os::unix::io::RawFd;

use bitflags::bitflags;
use nix::libc;

use crate::Result;

// These are wrapped in a module because they're `pub` by default
mod raw {
    use nix::libc;
    use nix::{ioctl_none_bad, ioctl_read, ioctl_read_bad, ioctl_write_ptr, ioctl_write_ptr_bad};

    ioctl_none_bad!(tiocexcl, libc::TIOCEXCL);
    ioctl_none_bad!(tiocnxcl, libc::TIOCNXCL);
    ioctl_read_bad!(tiocmget, libc::TIOCMGET, libc::c_int);
    ioctl_none_bad!(tiocsbrk, libc::TIOCSBRK);
    ioctl_none_bad!(tioccbrk, libc::TIOCCBRK);

    #[cfg(any(target_os = "android", target_os = "linux"))]
    ioctl_read_bad!(fionread, libc::FIONREAD, libc::c_int);

    // See: /usr/include/sys/filio.h
    #[cfg(any(
        target_os = "dragonfly",
        target_os = "freebsd",
        target_os = "ios",
        target_os = "macos",
        target_os = "netbsd",
        target_os = "openbsd"
    ))]
    ioctl_read!(fionread, b'f', 127, libc::c_int);

    #[cfg(any(target_os = "android", target_os = "linux"))]
    ioctl_read_bad!(tiocoutq, libc::TIOCOUTQ, libc::c_int);

    // See: /usr/include/sys/ttycom.h
    #[cfg(any(
        target_os = "dragonfly",
        target_os = "freebsd",
        target_os = "ios",
        target_os = "macos",
        target_os = "netbsd",
        target_os = "openbsd"
    ))]
    ioctl_read!(tiocoutq, b't', 115, libc::c_int);

    ioctl_write_ptr_bad!(tiocmbic, libc::TIOCMBIC, libc::c_int);
    ioctl_write_ptr_bad!(tiocmbis, libc::TIOCMBIS, libc::c_int);
    ioctl_read!(
        #[cfg(any(
            target_os = "android",
            all(
                target_os = "linux",
                not(any(
                    target_env = "musl",
                    target_arch = "powerpc",
                    target_arch = "powerpc64"
                ))
            )
        ))]
        tcgets2,
        b'T',
        0x2A,
        libc::termios2
    );
    ioctl_write_ptr!(
        #[cfg(any(
            target_os = "android",
            all(
                target_os = "linux",
                not(any(
                    target_env = "musl",
                    target_arch = "powerpc",
                    target_arch = "powerpc64"
                ))
            )
        ))]
        tcsets2,
        b'T',
        0x2B,
        libc::termios2
    );
    #[cfg(any(target_os = "ios", target_os = "macos"))]
    const IOSSIOSPEED: libc::c_ulong = 0x80045402;
    ioctl_write_ptr_bad!(
        #[cfg(any(target_os = "ios", target_os = "macos"))]
        iossiospeed,
        IOSSIOSPEED,
        libc::speed_t
    );
}

bitflags! {
    /// Flags to indicate which wires in a serial connection to use
    pub struct SerialLines: libc::c_int {
        const DATA_SET_READY = libc::TIOCM_DSR;
        const DATA_TERMINAL_READY = libc::TIOCM_DTR;
        const REQUEST_TO_SEND = libc::TIOCM_RTS;
        const SECONDARY_TRANSMIT = libc::TIOCM_ST;
        const SECONDARY_RECEIVE = libc::TIOCM_SR;
        const CLEAR_TO_SEND = libc::TIOCM_CTS;
        const DATA_CARRIER_DETECT = libc::TIOCM_CAR;
        const RING = libc::TIOCM_RNG;
    }
}

pub fn tiocexcl(fd: RawFd) -> Result<()> {
    unsafe { raw::tiocexcl(fd) }
        .map(|_| ())
        .map_err(|e| e.into())
}

pub fn tiocnxcl(fd: RawFd) -> Result<()> {
    unsafe { raw::tiocnxcl(fd) }
        .map(|_| ())
        .map_err(|e| e.into())
}

pub fn tiocmget(fd: RawFd) -> Result<SerialLines> {
    let mut status: libc::c_int = 0;
    unsafe { raw::tiocmget(fd, &mut status) }
        .map(|_| SerialLines::from_bits_truncate(status))
        .map_err(|e| e.into())
}

pub fn tiocsbrk(fd: RawFd) -> Result<()> {
    unsafe { raw::tiocsbrk(fd) }
        .map(|_| ())
        .map_err(|e| e.into())
}

pub fn tioccbrk(fd: RawFd) -> Result<()> {
    unsafe { raw::tioccbrk(fd) }
        .map(|_| ())
        .map_err(|e| e.into())
}

pub fn fionread(fd: RawFd) -> Result<u32> {
    let mut retval: libc::c_int = 0;
    unsafe { raw::fionread(fd, &mut retval) }
        .map(|_| retval as u32)
        .map_err(|e| e.into())
}

pub fn tiocoutq(fd: RawFd) -> Result<u32> {
    let mut retval: libc::c_int = 0;
    unsafe { raw::tiocoutq(fd, &mut retval) }
        .map(|_| retval as u32)
        .map_err(|e| e.into())
}

pub fn tiocmbic(fd: RawFd, status: SerialLines) -> Result<()> {
    let bits = status.bits() as libc::c_int;
    unsafe { raw::tiocmbic(fd, &bits) }
        .map(|_| ())
        .map_err(|e| e.into())
}

pub fn tiocmbis(fd: RawFd, status: SerialLines) -> Result<()> {
    let bits = status.bits() as libc::c_int;
    unsafe { raw::tiocmbis(fd, &bits) }
        .map(|_| ())
        .map_err(|e| e.into())
}

#[cfg(any(
    target_os = "android",
    all(
        target_os = "linux",
        not(any(
            target_env = "musl",
            target_arch = "powerpc",
            target_arch = "powerpc64"
        ))
    )
))]
pub fn tcgets2(fd: RawFd) -> Result<libc::termios2> {
    let mut options = std::mem::MaybeUninit::uninit();
    match unsafe { raw::tcgets2(fd, options.as_mut_ptr()) } {
        Ok(_) => unsafe { Ok(options.assume_init()) },
        Err(e) => Err(e.into()),
    }
}

#[cfg(any(
    target_os = "android",
    all(
        target_os = "linux",
        not(any(
            target_env = "musl",
            target_arch = "powerpc",
            target_arch = "powerpc64"
        ))
    )
))]
pub fn tcsets2(fd: RawFd, options: &libc::termios2) -> Result<()> {
    unsafe { raw::tcsets2(fd, options) }
        .map(|_| ())
        .map_err(|e| e.into())
}

#[cfg(any(target_os = "ios", target_os = "macos"))]
pub fn iossiospeed(fd: RawFd, baud_rate: &libc::speed_t) -> Result<()> {
    unsafe { raw::iossiospeed(fd, baud_rate) }
        .map(|_| ())
        .map_err(|e| e.into())
}
