use crate::backend::c;
use crate::{backend, io};
use bitflags::bitflags;

/// `struct termios` for use with [`tcgetattr`] and [`tcsetattr`].
///
/// [`tcgetattr`]: crate::termios::tcgetattr
/// [`tcsetattr`]: crate::termios::tcsetattr
#[repr(C)]
#[derive(Clone)]
pub struct Termios {
    /// How is input interpreted?
    #[doc(alias = "c_iflag")]
    pub input_modes: InputModes,

    /// How is output translated?
    #[doc(alias = "c_oflag")]
    pub output_modes: OutputModes,

    /// Low-level configuration flags.
    #[doc(alias = "c_cflag")]
    pub control_modes: ControlModes,

    /// High-level configuration flags.
    #[doc(alias = "c_lflag")]
    pub local_modes: LocalModes,

    /// Line discipline.
    #[doc(alias = "c_line")]
    #[cfg(not(all(linux_raw, any(target_arch = "powerpc", target_arch = "powerpc64"))))]
    #[cfg(any(
        linux_like,
        target_env = "newlib",
        target_os = "fuchsia",
        target_os = "haiku",
        target_os = "redox"
    ))]
    pub line_discipline: c::cc_t,

    /// How are various special control codes handled?
    #[doc(alias = "c_cc")]
    #[cfg(not(target_os = "haiku"))]
    pub special_codes: SpecialCodes,

    #[cfg(target_os = "nto")]
    pub(crate) __reserved: [c::c_uint; 3],

    /// Line discipline.
    // On PowerPC, this field comes after `c_cc`.
    #[doc(alias = "c_line")]
    #[cfg(all(linux_raw, any(target_arch = "powerpc", target_arch = "powerpc64")))]
    pub line_discipline: c::cc_t,

    /// See the `input_speed` and `set_input_seed` functions.
    ///
    /// On Linux and BSDs, this is the arbitrary integer speed value. On all
    /// other platforms, this is the encoded speed value.
    #[cfg(not(any(solarish, all(libc, target_env = "newlib"), target_os = "aix")))]
    pub(crate) input_speed: c::speed_t,

    /// See the `output_speed` and `set_output_seed` functions.
    ///
    /// On Linux and BSDs, this is the integer speed value. On all other
    /// platforms, this is the encoded speed value.
    #[cfg(not(any(solarish, all(libc, target_env = "newlib"), target_os = "aix")))]
    pub(crate) output_speed: c::speed_t,

    /// How are various special control codes handled?
    #[doc(alias = "c_cc")]
    #[cfg(target_os = "haiku")]
    pub special_codes: SpecialCodes,
}

impl Termios {
    /// `cfmakeraw(self)`—Set a `Termios` value to the settings for “raw” mode.
    ///
    /// In raw mode, input is available a byte at a time, echoing is disabled,
    /// and special terminal input and output codes are disabled.
    #[cfg(not(target_os = "nto"))]
    #[doc(alias = "cfmakeraw")]
    #[inline]
    pub fn make_raw(&mut self) {
        backend::termios::syscalls::cfmakeraw(self)
    }

    /// Return the input communication speed.
    ///
    /// Unlike the `c_ispeed` field in glibc and others, this returns the
    /// integer value of the speed, rather than the `B*` encoded constant
    /// value.
    #[doc(alias = "c_ispeed")]
    #[doc(alias = "cfgetispeed")]
    #[doc(alias = "cfgetspeed")]
    #[inline]
    pub fn input_speed(&self) -> u32 {
        // On Linux and BSDs, `input_speed` is the arbitrary integer speed.
        #[cfg(any(linux_kernel, bsd))]
        {
            debug_assert!(u32::try_from(self.input_speed).is_ok());
            self.input_speed as u32
        }

        // On illumos, `input_speed` is not present.
        #[cfg(any(solarish, all(libc, target_env = "newlib"), target_os = "aix"))]
        unsafe {
            speed::decode(c::cfgetispeed(crate::utils::as_ptr(self).cast())).unwrap()
        }

        // On other platforms, it's the encoded speed.
        #[cfg(not(any(
            linux_kernel,
            bsd,
            solarish,
            all(libc, target_env = "newlib"),
            target_os = "aix"
        )))]
        {
            speed::decode(self.input_speed).unwrap()
        }
    }

    /// Return the output communication speed.
    ///
    /// Unlike the `c_ospeed` field in glibc and others, this returns the
    /// arbitrary integer value of the speed, rather than the `B*` encoded
    /// constant value.
    #[inline]
    pub fn output_speed(&self) -> u32 {
        // On Linux and BSDs, `output_speed` is the arbitrary integer speed.
        #[cfg(any(linux_kernel, bsd))]
        {
            debug_assert!(u32::try_from(self.output_speed).is_ok());
            self.output_speed as u32
        }

        // On illumos, `output_speed` is not present.
        #[cfg(any(solarish, all(libc, target_env = "newlib"), target_os = "aix"))]
        unsafe {
            speed::decode(c::cfgetospeed(crate::utils::as_ptr(self).cast())).unwrap()
        }

        // On other platforms, it's the encoded speed.
        #[cfg(not(any(
            linux_kernel,
            bsd,
            solarish,
            all(libc, target_env = "newlib"),
            target_os = "aix"
        )))]
        {
            speed::decode(self.output_speed).unwrap()
        }
    }

    /// Set the input and output communication speeds.
    ///
    /// Unlike the `c_ispeed` and `c_ospeed` fields in glibc and others, this
    /// takes the arbitrary integer value of the speed, rather than the `B*`
    /// encoded constant value. Not all implementations support all integer
    /// values; use the constants in the [`speed`] module for likely-supported
    /// speeds.
    #[cfg(not(target_os = "nto"))]
    #[doc(alias = "cfsetspeed")]
    #[doc(alias = "CBAUD")]
    #[doc(alias = "CBAUDEX")]
    #[doc(alias = "CIBAUD")]
    #[doc(alias = "CIBAUDEX")]
    #[inline]
    pub fn set_speed(&mut self, new_speed: u32) -> io::Result<()> {
        backend::termios::syscalls::set_speed(self, new_speed)
    }

    /// Set the input communication speed.
    ///
    /// Unlike the `c_ispeed` field in glibc and others, this takes the
    /// arbitrary integer value of the speed, rather than the `B*` encoded
    /// constant value. Not all implementations support all integer values; use
    /// the constants in the [`speed`] module for known-supported speeds.
    ///
    /// On some platforms, changing the input speed changes the output speed
    /// to the same speed.
    #[doc(alias = "c_ispeed")]
    #[doc(alias = "cfsetispeed")]
    #[doc(alias = "CIBAUD")]
    #[doc(alias = "CIBAUDEX")]
    #[inline]
    pub fn set_input_speed(&mut self, new_speed: u32) -> io::Result<()> {
        backend::termios::syscalls::set_input_speed(self, new_speed)
    }

    /// Set the output communication speed.
    ///
    /// Unlike the `c_ospeed` field in glibc and others, this takes the
    /// arbitrary integer value of the speed, rather than the `B*` encoded
    /// constant value. Not all implementations support all integer values; use
    /// the constants in the [`speed`] module for known-supported speeds.
    ///
    /// On some platforms, changing the output speed changes the input speed
    /// to the same speed.
    #[doc(alias = "c_ospeed")]
    #[doc(alias = "cfsetospeed")]
    #[doc(alias = "CBAUD")]
    #[doc(alias = "CBAUDEX")]
    #[inline]
    pub fn set_output_speed(&mut self, new_speed: u32) -> io::Result<()> {
        backend::termios::syscalls::set_output_speed(self, new_speed)
    }
}

impl core::fmt::Debug for Termios {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let mut d = f.debug_struct("Termios");
        d.field("input_modes", &self.input_modes);
        d.field("output_modes", &self.output_modes);
        d.field("control_modes", &self.control_modes);
        d.field("local_modes", &self.local_modes);
        #[cfg(any(
            linux_like,
            target_env = "newlib",
            target_os = "fuchsia",
            target_os = "haiku",
            target_os = "redox"
        ))]
        {
            d.field("line_discipline", &self.line_discipline);
        }
        d.field("special_codes", &self.special_codes);
        d.field("input_speed", &self.input_speed());
        d.field("output_speed", &self.output_speed());
        d.finish()
    }
}

bitflags! {
    /// Flags controlling terminal input.
    #[repr(transparent)]
    #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
    pub struct InputModes: c::tcflag_t {
        /// `IGNBRK`
        const IGNBRK = c::IGNBRK;

        /// `BRKINT`
        const BRKINT = c::BRKINT;

        /// `IGNPAR`
        const IGNPAR = c::IGNPAR;

        /// `PARMRK`
        const PARMRK = c::PARMRK;

        /// `INPCK`
        const INPCK = c::INPCK;

        /// `ISTRIP`
        const ISTRIP = c::ISTRIP;

        /// `INLCR`
        const INLCR = c::INLCR;

        /// `IGNCR`
        const IGNCR = c::IGNCR;

        /// `ICRNL`
        const ICRNL = c::ICRNL;

        /// `IUCLC`
        #[cfg(any(linux_kernel, solarish, target_os = "aix", target_os = "haiku", target_os = "nto"))]
        const IUCLC = c::IUCLC;

        /// `IXON`
        const IXON = c::IXON;

        /// `IXANY`
        #[cfg(not(target_os = "redox"))]
        const IXANY = c::IXANY;

        /// `IXOFF`
        const IXOFF = c::IXOFF;

        /// `IMAXBEL`
        #[cfg(not(any(target_os = "haiku", target_os = "redox")))]
        const IMAXBEL = c::IMAXBEL;

        /// `IUTF8`
        #[cfg(not(any(
            freebsdlike,
            netbsdlike,
            solarish,
            target_os = "aix",
            target_os = "emscripten",
            target_os = "haiku",
            target_os = "hurd",
            target_os = "redox",
        )))]
        const IUTF8 = c::IUTF8;

        /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
        const _ = !0;
    }
}

bitflags! {
    /// Flags controlling terminal output.
    #[repr(transparent)]
    #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
    pub struct OutputModes: c::tcflag_t {
        /// `OPOST`
        const OPOST = c::OPOST;

        /// `OLCUC`
        #[cfg(not(any(
            apple,
            freebsdlike,
            target_os = "aix",
            target_os = "netbsd",
            target_os = "redox",
        )))]
        const OLCUC = c::OLCUC;

        /// `ONLCR`
        const ONLCR = c::ONLCR;

        /// `OCRNL`
        const OCRNL = c::OCRNL;

        /// `ONOCR`
        const ONOCR = c::ONOCR;

        /// `ONLRET`
        const ONLRET = c::ONLRET;

        /// `OFILL`
        #[cfg(not(bsd))]
        const OFILL = c::OFILL;

        /// `OFDEL`
        #[cfg(not(bsd))]
        const OFDEL = c::OFDEL;

        /// `NLDLY`
        #[cfg(not(any(bsd, solarish, target_os = "redox")))]
        const NLDLY = c::NLDLY;

        /// `NL0`
        #[cfg(not(any(bsd, solarish, target_os = "fuchsia", target_os = "redox")))]
        const NL0 = c::NL0;

        /// `NL1`
        #[cfg(not(any(bsd, solarish, target_os = "fuchsia", target_os = "redox")))]
        const NL1 = c::NL1;

        /// `CRDLY`
        #[cfg(not(any(bsd, solarish, target_os = "redox")))]
        const CRDLY = c::CRDLY;

        /// `CR0`
        #[cfg(not(any(bsd, solarish, target_os = "fuchsia", target_os = "redox")))]
        const CR0 = c::CR0;

        /// `CR1`
        #[cfg(not(any(
            target_env = "musl",
            bsd,
            solarish,
            target_os = "emscripten",
            target_os = "fuchsia",
            target_os = "redox",
        )))]
        const CR1 = c::CR1;

        /// `CR2`
        #[cfg(not(any(
            target_env = "musl",
            bsd,
            solarish,
            target_os = "emscripten",
            target_os = "fuchsia",
            target_os = "redox",
        )))]
        const CR2 = c::CR2;

        /// `CR3`
        #[cfg(not(any(
            target_env = "musl",
            bsd,
            solarish,
            target_os = "emscripten",
            target_os = "fuchsia",
            target_os = "redox",
        )))]
        const CR3 = c::CR3;

        /// `TABDLY`
        #[cfg(not(any(
            netbsdlike,
            solarish,
            target_os = "dragonfly",
            target_os = "redox",
        )))]
        const TABDLY = c::TABDLY;

        /// `TAB0`
        #[cfg(not(any(
            netbsdlike,
            solarish,
            target_os = "dragonfly",
            target_os = "fuchsia",
            target_os = "redox",
        )))]
        const TAB0 = c::TAB0;

        /// `TAB1`
        #[cfg(not(any(
            target_env = "musl",
            bsd,
            solarish,
            target_os = "emscripten",
            target_os = "fuchsia",
            target_os = "redox",
        )))]
        const TAB1 = c::TAB1;

        /// `TAB2`
        #[cfg(not(any(
            target_env = "musl",
            bsd,
            solarish,
            target_os = "emscripten",
            target_os = "fuchsia",
            target_os = "redox",
        )))]
        const TAB2 = c::TAB2;

        /// `TAB3`
        #[cfg(not(any(
            target_env = "musl",
            bsd,
            solarish,
            target_os = "emscripten",
            target_os = "fuchsia",
            target_os = "redox",
        )))]
        const TAB3 = c::TAB3;

        /// `XTABS`
        #[cfg(not(any(
            bsd,
            solarish,
            target_os = "aix",
            target_os = "haiku",
            target_os = "redox",
        )))]
        const XTABS = c::XTABS;

        /// `BSDLY`
        #[cfg(not(any(bsd, solarish, target_os = "redox")))]
        const BSDLY = c::BSDLY;

        /// `BS0`
        #[cfg(not(any(bsd, solarish, target_os = "fuchsia", target_os = "redox")))]
        const BS0 = c::BS0;

        /// `BS1`
        #[cfg(not(any(
            target_env = "musl",
            bsd,
            solarish,
            target_os = "emscripten",
            target_os = "fuchsia",
            target_os = "redox",
        )))]
        const BS1 = c::BS1;

        /// `FFDLY`
        #[cfg(not(any(bsd, solarish, target_os = "redox")))]
        const FFDLY = c::FFDLY;

        /// `FF0`
        #[cfg(not(any(bsd, solarish, target_os = "fuchsia", target_os = "redox")))]
        const FF0 = c::FF0;

        /// `FF1`
        #[cfg(not(any(
            target_env = "musl",
            bsd,
            solarish,
            target_os = "emscripten",
            target_os = "fuchsia",
            target_os = "redox",
        )))]
        const FF1 = c::FF1;

        /// `VTDLY`
        #[cfg(not(any(bsd, solarish, target_os = "redox")))]
        const VTDLY = c::VTDLY;

        /// `VT0`
        #[cfg(not(any(bsd, solarish, target_os = "fuchsia", target_os = "redox")))]
        const VT0 = c::VT0;

        /// `VT1`
        #[cfg(not(any(
            target_env = "musl",
            bsd,
            solarish,
            target_os = "emscripten",
            target_os = "fuchsia",
            target_os = "redox",
        )))]
        const VT1 = c::VT1;

        /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
        const _ = !0;
    }
}

bitflags! {
    /// Flags controlling special terminal modes.
    ///
    /// `CBAUD`, `CBAUDEX`, `CIBAUD`, and `CIBAUDEX` are not defined here,
    /// because they're handled automatically by [`Termios::set_speed`] and
    /// related functions.
    #[repr(transparent)]
    #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
    pub struct ControlModes: c::tcflag_t {
        /// `CSIZE`
        const CSIZE = c::CSIZE;

        /// `CS5`
        const CS5 = c::CS5;

        /// `CS6`
        const CS6 = c::CS6;

        /// `CS7`
        const CS7 = c::CS7;

        /// `CS8`
        const CS8 = c::CS8;

        /// `CSTOPB`
        const CSTOPB = c::CSTOPB;

        /// `CREAD`
        const CREAD = c::CREAD;

        /// `PARENB`
        const PARENB = c::PARENB;

        /// `PARODD`
        const PARODD = c::PARODD;

        /// `HUPCL`
        const HUPCL = c::HUPCL;

        /// `CLOCAL`
        const CLOCAL = c::CLOCAL;

        /// `CRTSCTS`
        #[cfg(not(any(target_os = "aix", target_os = "nto", target_os = "redox")))]
        const CRTSCTS = c::CRTSCTS;

        /// `CMSPAR`
        #[cfg(not(any(
            bsd,
            solarish,
            target_os = "aix",
            target_os = "emscripten",
            target_os = "haiku",
            target_os = "hurd",
            target_os = "nto",
            target_os = "redox",
        )))]
        const CMSPAR = c::CMSPAR;

        /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
        const _ = !0;
    }
}

bitflags! {
    /// Flags controlling “local” terminal modes.
    #[repr(transparent)]
    #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
    pub struct LocalModes: c::tcflag_t {
        /// `XCASE`
        #[cfg(any(linux_kernel, target_arch = "s390x", target_os = "haiku"))]
        const XCASE = c::XCASE;

        /// `ECHOCTL`
        #[cfg(not(target_os = "redox"))]
        const ECHOCTL = c::ECHOCTL;

        /// `ECHOPRT`
        #[cfg(not(any(target_os = "nto", target_os = "redox")))]
        const ECHOPRT = c::ECHOPRT;

        /// `ECHOKE`
        #[cfg(not(target_os = "redox"))]
        const ECHOKE = c::ECHOKE;

        /// `FLUSHO`
        #[cfg(not(any(target_os = "nto", target_os = "redox")))]
        const FLUSHO = c::FLUSHO;

        /// `PENDIN`
        #[cfg(not(any(target_os = "nto", target_os = "redox")))]
        const PENDIN = c::PENDIN;

        /// `EXTPROC`
        #[cfg(not(any(target_os = "aix", target_os = "haiku", target_os = "nto", target_os = "redox")))]
        const EXTPROC = c::EXTPROC;

        /// `ISIG`
        const ISIG = c::ISIG;

        /// `ICANON`—A flag for the `c_lflag` field of [`Termios`] indicating
        /// canonical mode.
        const ICANON = c::ICANON;

        /// `ECHO`
        const ECHO = c::ECHO;

        /// `ECHOE`
        const ECHOE = c::ECHOE;

        /// `ECHOK`
        const ECHOK = c::ECHOK;

        /// `ECHONL`
        const ECHONL = c::ECHONL;

        /// `NOFLSH`
        const NOFLSH = c::NOFLSH;

        /// `TOSTOP`
        const TOSTOP = c::TOSTOP;

        /// `IEXTEN`
        const IEXTEN = c::IEXTEN;

        /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
        const _ = !0;
    }
}

/// Speeds for use with [`Termios::set_input_speed`] and
/// [`Termios::set_output_speed`].
///
/// Unlike in some platforms' libc APIs, these always have the same numerical
/// value as their names; for example, `B50` has the value `50`, and so on.
/// Consequently, it's not necessary to use them. They are provided here
/// because they help identify speeds which are likely to be supported, on
/// platforms which don't support arbitrary speeds.
pub mod speed {
    #[cfg(not(bsd))]
    use crate::backend::c;

    /// `B0`
    pub const B0: u32 = 0;

    /// `B50`
    pub const B50: u32 = 50;

    /// `B75`
    pub const B75: u32 = 75;

    /// `B110`
    pub const B110: u32 = 110;

    /// `B134`
    pub const B134: u32 = 134;

    /// `B150`
    pub const B150: u32 = 150;

    /// `B200`
    pub const B200: u32 = 200;

    /// `B300`
    pub const B300: u32 = 300;

    /// `B600`
    pub const B600: u32 = 600;

    /// `B1200`
    pub const B1200: u32 = 1200;

    /// `B1800`
    pub const B1800: u32 = 1800;

    /// `B2400`
    pub const B2400: u32 = 2400;

    /// `B4800`
    pub const B4800: u32 = 4800;

    /// `B9600`
    pub const B9600: u32 = 9600;

    /// `B19200`
    #[doc(alias = "EXTA")]
    pub const B19200: u32 = 19200;

    /// `B38400`
    #[doc(alias = "EXTB")]
    pub const B38400: u32 = 38400;

    /// `B57600`
    #[cfg(not(target_os = "aix"))]
    pub const B57600: u32 = 57600;

    /// `B115200`
    #[cfg(not(target_os = "aix"))]
    pub const B115200: u32 = 115_200;

    /// `B230400`
    #[cfg(not(target_os = "aix"))]
    pub const B230400: u32 = 230_400;

    /// `B460800`
    #[cfg(not(any(
        apple,
        target_os = "aix",
        target_os = "dragonfly",
        target_os = "haiku",
        target_os = "openbsd"
    )))]
    pub const B460800: u32 = 460_800;

    /// `B500000`
    #[cfg(not(any(bsd, solarish, target_os = "aix", target_os = "haiku")))]
    pub const B500000: u32 = 500_000;

    /// `B576000`
    #[cfg(not(any(bsd, solarish, target_os = "aix", target_os = "haiku")))]
    pub const B576000: u32 = 576_000;

    /// `B921600`
    #[cfg(not(any(
        apple,
        target_os = "aix",
        target_os = "dragonfly",
        target_os = "haiku",
        target_os = "openbsd"
    )))]
    pub const B921600: u32 = 921_600;

    /// `B1000000`
    #[cfg(not(any(bsd, target_os = "aix", target_os = "haiku", target_os = "solaris")))]
    pub const B1000000: u32 = 1_000_000;

    /// `B1152000`
    #[cfg(not(any(bsd, target_os = "aix", target_os = "haiku", target_os = "solaris")))]
    pub const B1152000: u32 = 1_152_000;

    /// `B1500000`
    #[cfg(not(any(bsd, target_os = "aix", target_os = "haiku", target_os = "solaris")))]
    pub const B1500000: u32 = 1_500_000;

    /// `B2000000`
    #[cfg(not(any(bsd, target_os = "aix", target_os = "haiku", target_os = "solaris")))]
    pub const B2000000: u32 = 2_000_000;

    /// `B2500000`
    #[cfg(not(any(
        target_arch = "sparc",
        target_arch = "sparc64",
        bsd,
        target_os = "aix",
        target_os = "haiku",
        target_os = "solaris",
    )))]
    pub const B2500000: u32 = 2_500_000;

    /// `B3000000`
    #[cfg(not(any(
        target_arch = "sparc",
        target_arch = "sparc64",
        bsd,
        target_os = "aix",
        target_os = "haiku",
        target_os = "solaris",
    )))]
    pub const B3000000: u32 = 3_000_000;

    /// `B3500000`
    #[cfg(not(any(
        target_arch = "sparc",
        target_arch = "sparc64",
        bsd,
        target_os = "aix",
        target_os = "haiku",
        target_os = "solaris",
    )))]
    pub const B3500000: u32 = 3_500_000;

    /// `B4000000`
    #[cfg(not(any(
        target_arch = "sparc",
        target_arch = "sparc64",
        bsd,
        target_os = "aix",
        target_os = "haiku",
        target_os = "solaris",
    )))]
    pub const B4000000: u32 = 4_000_000;

    /// Translate from a `c::speed_t` code to an arbitrary integer speed value
    /// `u32`.
    ///
    /// On BSD platforms, integer speed values are already the same as their
    /// encoded values.
    ///
    /// On Linux on PowerPC, `TCGETS`/`TCSETS` support the `c_ispeed` and
    /// `c_ospeed` fields.
    ///
    /// On Linux on architectures other than PowerPC, `TCGETS`/`TCSETS` don't
    /// support the `c_ispeed` and `c_ospeed` fields, so we have to fall back
    /// to `TCGETS2`/`TCSETS2` to support them.
    #[cfg(not(any(
        bsd,
        all(linux_kernel, any(target_arch = "powerpc", target_arch = "powerpc64"))
    )))]
    pub(crate) const fn decode(encoded_speed: c::speed_t) -> Option<u32> {
        match encoded_speed {
            c::B0 => Some(0),
            c::B50 => Some(50),
            c::B75 => Some(75),
            c::B110 => Some(110),
            c::B134 => Some(134),
            c::B150 => Some(150),
            c::B200 => Some(200),
            c::B300 => Some(300),
            c::B600 => Some(600),
            c::B1200 => Some(1200),
            c::B1800 => Some(1800),
            c::B2400 => Some(2400),
            c::B4800 => Some(4800),
            c::B9600 => Some(9600),
            c::B19200 => Some(19200),
            c::B38400 => Some(38400),
            #[cfg(not(target_os = "aix"))]
            c::B57600 => Some(57600),
            #[cfg(not(target_os = "aix"))]
            c::B115200 => Some(115_200),
            #[cfg(not(any(target_os = "aix", target_os = "nto")))]
            c::B230400 => Some(230_400),
            #[cfg(not(any(
                apple,
                target_os = "aix",
                target_os = "dragonfly",
                target_os = "haiku",
                target_os = "nto",
                target_os = "openbsd"
            )))]
            c::B460800 => Some(460_800),
            #[cfg(not(any(
                bsd,
                solarish,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto"
            )))]
            c::B500000 => Some(500_000),
            #[cfg(not(any(
                bsd,
                solarish,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto"
            )))]
            c::B576000 => Some(576_000),
            #[cfg(not(any(
                apple,
                target_os = "aix",
                target_os = "dragonfly",
                target_os = "haiku",
                target_os = "nto",
                target_os = "openbsd"
            )))]
            c::B921600 => Some(921_600),
            #[cfg(not(any(
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris"
            )))]
            c::B1000000 => Some(1_000_000),
            #[cfg(not(any(
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris"
            )))]
            c::B1152000 => Some(1_152_000),
            #[cfg(not(any(
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris"
            )))]
            c::B1500000 => Some(1_500_000),
            #[cfg(not(any(
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris"
            )))]
            c::B2000000 => Some(2_000_000),
            #[cfg(not(any(
                target_arch = "sparc",
                target_arch = "sparc64",
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris",
            )))]
            c::B2500000 => Some(2_500_000),
            #[cfg(not(any(
                target_arch = "sparc",
                target_arch = "sparc64",
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris",
            )))]
            c::B3000000 => Some(3_000_000),
            #[cfg(not(any(
                target_arch = "sparc",
                target_arch = "sparc64",
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris",
            )))]
            c::B3500000 => Some(3_500_000),
            #[cfg(not(any(
                target_arch = "sparc",
                target_arch = "sparc64",
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris",
            )))]
            c::B4000000 => Some(4_000_000),
            _ => None,
        }
    }

    /// Translate from an arbitrary `u32` arbitrary integer speed value to a
    /// `c::speed_t` code.
    #[cfg(not(bsd))]
    pub(crate) const fn encode(speed: u32) -> Option<c::speed_t> {
        match speed {
            0 => Some(c::B0),
            50 => Some(c::B50),
            75 => Some(c::B75),
            110 => Some(c::B110),
            134 => Some(c::B134),
            150 => Some(c::B150),
            200 => Some(c::B200),
            300 => Some(c::B300),
            600 => Some(c::B600),
            1200 => Some(c::B1200),
            1800 => Some(c::B1800),
            2400 => Some(c::B2400),
            4800 => Some(c::B4800),
            9600 => Some(c::B9600),
            19200 => Some(c::B19200),
            38400 => Some(c::B38400),
            #[cfg(not(target_os = "aix"))]
            57600 => Some(c::B57600),
            #[cfg(not(target_os = "aix"))]
            115_200 => Some(c::B115200),
            #[cfg(not(any(target_os = "aix", target_os = "nto")))]
            230_400 => Some(c::B230400),
            #[cfg(not(any(
                apple,
                target_os = "aix",
                target_os = "dragonfly",
                target_os = "haiku",
                target_os = "nto",
                target_os = "openbsd",
            )))]
            460_800 => Some(c::B460800),
            #[cfg(not(any(
                bsd,
                solarish,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto"
            )))]
            500_000 => Some(c::B500000),
            #[cfg(not(any(
                bsd,
                solarish,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto"
            )))]
            576_000 => Some(c::B576000),
            #[cfg(not(any(
                apple,
                target_os = "aix",
                target_os = "dragonfly",
                target_os = "haiku",
                target_os = "nto",
                target_os = "openbsd"
            )))]
            921_600 => Some(c::B921600),
            #[cfg(not(any(
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris"
            )))]
            1_000_000 => Some(c::B1000000),
            #[cfg(not(any(
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris"
            )))]
            1_152_000 => Some(c::B1152000),
            #[cfg(not(any(
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris"
            )))]
            1_500_000 => Some(c::B1500000),
            #[cfg(not(any(
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris"
            )))]
            2_000_000 => Some(c::B2000000),
            #[cfg(not(any(
                target_arch = "sparc",
                target_arch = "sparc64",
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris",
            )))]
            2_500_000 => Some(c::B2500000),
            #[cfg(not(any(
                target_arch = "sparc",
                target_arch = "sparc64",
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris",
            )))]
            3_000_000 => Some(c::B3000000),
            #[cfg(not(any(
                target_arch = "sparc",
                target_arch = "sparc64",
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris",
            )))]
            3_500_000 => Some(c::B3500000),
            #[cfg(not(any(
                target_arch = "sparc",
                target_arch = "sparc64",
                bsd,
                target_os = "aix",
                target_os = "haiku",
                target_os = "nto",
                target_os = "solaris",
            )))]
            4_000_000 => Some(c::B4000000),
            _ => None,
        }
    }
}

/// An array indexed by [`SpecialCodeIndex`] indicating the current values
/// of various special control codes.
#[repr(transparent)]
#[derive(Clone, Debug)]
pub struct SpecialCodes(pub(crate) [c::cc_t; c::NCCS as usize]);

impl core::ops::Index<SpecialCodeIndex> for SpecialCodes {
    type Output = c::cc_t;

    fn index(&self, index: SpecialCodeIndex) -> &Self::Output {
        &self.0[index.0]
    }
}

impl core::ops::IndexMut<SpecialCodeIndex> for SpecialCodes {
    fn index_mut(&mut self, index: SpecialCodeIndex) -> &mut Self::Output {
        &mut self.0[index.0]
    }
}

/// Indices for use with [`Termios::special_codes`].
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct SpecialCodeIndex(usize);

#[rustfmt::skip]
impl SpecialCodeIndex {
    /// `VINTR`
    pub const VINTR: Self = Self(c::VINTR as usize);

    /// `VQUIT`
    pub const VQUIT: Self = Self(c::VQUIT as usize);

    /// `VERASE`
    pub const VERASE: Self = Self(c::VERASE as usize);

    /// `VKILL`
    pub const VKILL: Self = Self(c::VKILL as usize);

    /// `VEOF`
    pub const VEOF: Self = Self(c::VEOF as usize);

    /// `VTIME`
    pub const VTIME: Self = Self(c::VTIME as usize);

    /// `VMIN`
    pub const VMIN: Self = Self(c::VMIN as usize);

    /// `VSWTC`
    #[cfg(not(any(
        bsd,
        solarish,
        target_os = "aix",
        target_os = "haiku",
        target_os = "hurd",
        target_os = "nto",
    )))]
    pub const VSWTC: Self = Self(c::VSWTC as usize);

    /// `VSTART`
    pub const VSTART: Self = Self(c::VSTART as usize);

    /// `VSTOP`
    pub const VSTOP: Self = Self(c::VSTOP as usize);

    /// `VSUSP`
    pub const VSUSP: Self = Self(c::VSUSP as usize);

    /// `VEOL`
    pub const VEOL: Self = Self(c::VEOL as usize);

    /// `VREPRINT`
    #[cfg(not(target_os = "haiku"))]
    pub const VREPRINT: Self = Self(c::VREPRINT as usize);

    /// `VDISCARD`
    #[cfg(not(any(target_os = "aix", target_os = "haiku")))]
    pub const VDISCARD: Self = Self(c::VDISCARD as usize);

    /// `VWERASE`
    #[cfg(not(any(target_os = "aix", target_os = "haiku")))]
    pub const VWERASE: Self = Self(c::VWERASE as usize);

    /// `VLNEXT`
    #[cfg(not(target_os = "haiku"))]
    pub const VLNEXT: Self = Self(c::VLNEXT as usize);

    /// `VEOL2`
    pub const VEOL2: Self = Self(c::VEOL2 as usize);

    /// `VSWTCH`
    #[cfg(any(solarish, target_os = "haiku", target_os = "nto"))]
    pub const VSWTCH: Self = Self(c::VSWTCH as usize);

    /// `VDSUSP`
    #[cfg(any(bsd, solarish, target_os = "aix", target_os = "hurd", target_os = "nto"))]
    pub const VDSUSP: Self = Self(c::VDSUSP as usize);

    /// `VSTATUS`
    #[cfg(any(bsd, target_os = "hurd", target_os = "illumos"))]
    pub const VSTATUS: Self = Self(c::VSTATUS as usize);

    /// `VERASE2`
    #[cfg(any(freebsdlike, target_os = "illumos"))]
    pub const VERASE2: Self = Self(c::VERASE2 as usize);
}

impl core::fmt::Debug for SpecialCodeIndex {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match *self {
            Self::VINTR => write!(f, "VINTR"),
            Self::VQUIT => write!(f, "VQUIT"),
            Self::VERASE => write!(f, "VERASE"),
            Self::VKILL => write!(f, "VKILL"),
            #[cfg(not(any(
                solarish,
                all(linux_kernel, any(target_arch = "sparc", target_arch = "sparc64"))
            )))]
            Self::VEOF => write!(f, "VEOF"),
            #[cfg(not(any(
                solarish,
                all(linux_kernel, any(target_arch = "sparc", target_arch = "sparc64"))
            )))]
            Self::VTIME => write!(f, "VTIME"),
            #[cfg(not(any(
                solarish,
                all(linux_kernel, any(target_arch = "sparc", target_arch = "sparc64"))
            )))]
            Self::VMIN => write!(f, "VMIN"),

            // On Solarish platforms, and Linux on SPARC, `VMIN` and `VTIME`
            // have the same value as `VEOF` and `VEOL`.
            #[cfg(any(
                solarish,
                all(linux_kernel, any(target_arch = "sparc", target_arch = "sparc64"))
            ))]
            Self::VMIN => write!(f, "VMIN/VEOF"),
            #[cfg(any(
                solarish,
                all(linux_kernel, any(target_arch = "sparc", target_arch = "sparc64"))
            ))]
            Self::VTIME => write!(f, "VTIME/VEOL"),

            #[cfg(not(any(
                bsd,
                solarish,
                target_os = "aix",
                target_os = "haiku",
                target_os = "hurd",
                target_os = "nto",
            )))]
            Self::VSWTC => write!(f, "VSWTC"),
            Self::VSTART => write!(f, "VSTART"),
            Self::VSTOP => write!(f, "VSTOP"),
            Self::VSUSP => write!(f, "VSUSP"),
            #[cfg(not(any(
                solarish,
                all(linux_kernel, any(target_arch = "sparc", target_arch = "sparc64"))
            )))]
            Self::VEOL => write!(f, "VEOL"),
            #[cfg(not(target_os = "haiku"))]
            Self::VREPRINT => write!(f, "VREPRINT"),
            #[cfg(not(any(target_os = "aix", target_os = "haiku")))]
            Self::VDISCARD => write!(f, "VDISCARD"),
            #[cfg(not(any(target_os = "aix", target_os = "haiku")))]
            Self::VWERASE => write!(f, "VWERASE"),
            #[cfg(not(target_os = "haiku"))]
            Self::VLNEXT => write!(f, "VLNEXT"),
            Self::VEOL2 => write!(f, "VEOL2"),
            #[cfg(any(solarish, target_os = "haiku", target_os = "nto"))]
            Self::VSWTCH => write!(f, "VSWTCH"),
            #[cfg(any(
                bsd,
                solarish,
                target_os = "aix",
                target_os = "hurd",
                target_os = "nto"
            ))]
            Self::VDSUSP => write!(f, "VDSUSP"),
            #[cfg(any(bsd, target_os = "hurd", target_os = "illumos"))]
            Self::VSTATUS => write!(f, "VSTATUS"),
            #[cfg(any(freebsdlike, target_os = "illumos"))]
            Self::VERASE2 => write!(f, "VERASE2"),

            _ => write!(f, "unknown"),
        }
    }
}

/// `TCSA*` values for use with [`tcsetattr`].
///
/// [`tcsetattr`]: crate::termios::tcsetattr
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[repr(u32)]
pub enum OptionalActions {
    /// `TCSANOW`—Make the change immediately.
    #[doc(alias = "TCSANOW")]
    Now = c::TCSANOW as u32,

    /// `TCSADRAIN`—Make the change after all output has been transmitted.
    #[doc(alias = "TCSADRAIN")]
    Drain = c::TCSADRAIN as u32,

    /// `TCSAFLUSH`—Discard any pending input and then make the change
    /// after all output has been transmitted.
    #[doc(alias = "TCSAFLUSH")]
    Flush = c::TCSAFLUSH as u32,
}

/// `TC*` values for use with [`tcflush`].
///
/// [`tcflush`]: crate::termios::tcflush
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[repr(u32)]
pub enum QueueSelector {
    /// `TCIFLUSH`—Flush data received but not read.
    #[doc(alias = "TCIFLUSH")]
    IFlush = c::TCIFLUSH as u32,

    /// `TCOFLUSH`—Flush data written but not transmitted.
    #[doc(alias = "TCOFLUSH")]
    OFlush = c::TCOFLUSH as u32,

    /// `TCIOFLUSH`—`IFlush` and `OFlush` combined.
    #[doc(alias = "TCIOFLUSH")]
    IOFlush = c::TCIOFLUSH as u32,
}

/// `TC*` values for use with [`tcflow`].
///
/// [`tcflow`]: crate::termios::tcflow
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[repr(u32)]
pub enum Action {
    /// `TCOOFF`—Suspend output.
    #[doc(alias = "TCOOFF")]
    OOff = c::TCOOFF as u32,

    /// `TCOON`—Restart suspended output.
    #[doc(alias = "TCOON")]
    OOn = c::TCOON as u32,

    /// `TCIOFF`—Transmits a STOP byte.
    #[doc(alias = "TCIOFF")]
    IOff = c::TCIOFF as u32,

    /// `TCION`—Transmits a START byte.
    #[doc(alias = "TCION")]
    IOn = c::TCION as u32,
}

/// `struct winsize` for use with [`tcgetwinsize`].
///
/// [`tcgetwinsize`]: crate::termios::tcgetwinsize
#[doc(alias = "winsize")]
pub type Winsize = c::winsize;

#[test]
fn termios_layouts() {
    check_renamed_type!(InputModes, tcflag_t);
    check_renamed_type!(OutputModes, tcflag_t);
    check_renamed_type!(ControlModes, tcflag_t);
    check_renamed_type!(LocalModes, tcflag_t);

    // On platforms with a termios/termios2 split, check `termios`.
    #[cfg(linux_raw)]
    {
        check_renamed_type!(Termios, termios2);
        check_renamed_struct_renamed_field!(Termios, termios2, input_modes, c_iflag);
        check_renamed_struct_renamed_field!(Termios, termios2, output_modes, c_oflag);
        check_renamed_struct_renamed_field!(Termios, termios2, control_modes, c_cflag);
        check_renamed_struct_renamed_field!(Termios, termios2, local_modes, c_lflag);
        check_renamed_struct_renamed_field!(Termios, termios2, line_discipline, c_line);
        check_renamed_struct_renamed_field!(Termios, termios2, special_codes, c_cc);
        check_renamed_struct_renamed_field!(Termios, termios2, input_speed, c_ispeed);
        check_renamed_struct_renamed_field!(Termios, termios2, output_speed, c_ospeed);

        // We assume that `termios` has the same layout as `termios2` minus the
        // `c_ispeed` and `c_ospeed` fields.
        check_renamed_struct_renamed_field!(Termios, termios, input_modes, c_iflag);
        check_renamed_struct_renamed_field!(Termios, termios, output_modes, c_oflag);
        check_renamed_struct_renamed_field!(Termios, termios, control_modes, c_cflag);
        check_renamed_struct_renamed_field!(Termios, termios, local_modes, c_lflag);
        check_renamed_struct_renamed_field!(Termios, termios, special_codes, c_cc);

        // On everything except PowerPC, `termios` matches `termios2` except
        // for the addition of `c_ispeed` and `c_ospeed`.
        #[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
        const_assert_eq!(
            memoffset::offset_of!(Termios, input_speed),
            core::mem::size_of::<c::termios>()
        );

        // On PowerPC, `termios2` is `termios`.
        #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
        assert_eq_size!(c::termios2, c::termios);
    }

    #[cfg(not(linux_raw))]
    {
        // On MIPS, SPARC, and Android, the libc lacks the ospeed and ispeed
        // fields.
        #[cfg(all(
            not(all(
                target_env = "gnu",
                any(
                    target_arch = "mips",
                    target_arch = "mips32r6",
                    target_arch = "mips64",
                    target_arch = "mips64r6",
                    target_arch = "sparc",
                    target_arch = "sparc64"
                )
            )),
            not(all(libc, target_os = "android"))
        ))]
        check_renamed_type!(Termios, termios);
        #[cfg(not(all(
            not(all(
                target_env = "gnu",
                any(
                    target_arch = "mips",
                    target_arch = "mips32r6",
                    target_arch = "mips64",
                    target_arch = "mips64r6",
                    target_arch = "sparc",
                    target_arch = "sparc64"
                )
            )),
            not(all(libc, target_os = "android"))
        )))]
        const_assert!(core::mem::size_of::<Termios>() >= core::mem::size_of::<c::termios>());

        check_renamed_struct_renamed_field!(Termios, termios, input_modes, c_iflag);
        check_renamed_struct_renamed_field!(Termios, termios, output_modes, c_oflag);
        check_renamed_struct_renamed_field!(Termios, termios, control_modes, c_cflag);
        check_renamed_struct_renamed_field!(Termios, termios, local_modes, c_lflag);
        #[cfg(any(
            linux_like,
            target_env = "newlib",
            target_os = "fuchsia",
            target_os = "haiku",
            target_os = "redox"
        ))]
        check_renamed_struct_renamed_field!(Termios, termios, line_discipline, c_line);
        check_renamed_struct_renamed_field!(Termios, termios, special_codes, c_cc);
        #[cfg(not(any(
            linux_kernel,
            solarish,
            target_os = "emscripten",
            target_os = "fuchsia"
        )))]
        {
            check_renamed_struct_renamed_field!(Termios, termios, input_speed, c_ispeed);
            check_renamed_struct_renamed_field!(Termios, termios, output_speed, c_ospeed);
        }
        #[cfg(any(target_env = "musl", target_os = "fuchsia"))]
        {
            check_renamed_struct_renamed_field!(Termios, termios, input_speed, __c_ispeed);
            check_renamed_struct_renamed_field!(Termios, termios, output_speed, __c_ospeed);
        }
    }

    check_renamed_type!(OptionalActions, c_int);
    check_renamed_type!(QueueSelector, c_int);
    check_renamed_type!(Action, c_int);
}

#[test]
#[cfg(not(any(
    solarish,
    target_os = "emscripten",
    target_os = "haiku",
    target_os = "redox"
)))]
fn termios_legacy() {
    // Check that our doc aliases above are correct.
    const_assert_eq!(c::EXTA, c::B19200);
    const_assert_eq!(c::EXTB, c::B38400);
}

#[cfg(bsd)]
#[test]
fn termios_bsd() {
    // On BSD platforms we can assume that the `B*` constants have their
    // arbitrary integer speed value. Confirm this.
    const_assert_eq!(c::B0, 0);
    const_assert_eq!(c::B50, 50);
    const_assert_eq!(c::B19200, 19200);
    const_assert_eq!(c::B38400, 38400);
}

#[test]
#[cfg(not(bsd))]
fn termios_speed_encoding() {
    assert_eq!(speed::encode(0), Some(c::B0));
    assert_eq!(speed::encode(50), Some(c::B50));
    assert_eq!(speed::encode(19200), Some(c::B19200));
    assert_eq!(speed::encode(38400), Some(c::B38400));
    assert_eq!(speed::encode(1), None);
    assert_eq!(speed::encode(!0), None);

    #[cfg(not(linux_kernel))]
    {
        assert_eq!(speed::decode(c::B0), Some(0));
        assert_eq!(speed::decode(c::B50), Some(50));
        assert_eq!(speed::decode(c::B19200), Some(19200));
        assert_eq!(speed::decode(c::B38400), Some(38400));
    }
}

#[cfg(linux_kernel)]
#[test]
fn termios_ioctl_contiguity() {
    // When using `termios2`, we assume that we can add the optional actions
    // value to the ioctl request code. Test this assumption.

    const_assert_eq!(c::TCSETS2, c::TCSETS2 + 0);
    const_assert_eq!(c::TCSETSW2, c::TCSETS2 + 1);
    const_assert_eq!(c::TCSETSF2, c::TCSETS2 + 2);

    const_assert_eq!(c::TCSANOW - c::TCSANOW, 0);
    const_assert_eq!(c::TCSADRAIN - c::TCSANOW, 1);
    const_assert_eq!(c::TCSAFLUSH - c::TCSANOW, 2);

    // MIPS is different here.
    #[cfg(any(
        target_arch = "mips",
        target_arch = "mips32r6",
        target_arch = "mips64",
        target_arch = "mips64r6"
    ))]
    {
        assert_eq!(i128::from(c::TCSANOW) - i128::from(c::TCSETS), 0);
        assert_eq!(i128::from(c::TCSADRAIN) - i128::from(c::TCSETS), 1);
        assert_eq!(i128::from(c::TCSAFLUSH) - i128::from(c::TCSETS), 2);
    }
    #[cfg(not(any(
        target_arch = "mips",
        target_arch = "mips32r6",
        target_arch = "mips64",
        target_arch = "mips64r6"
    )))]
    {
        const_assert_eq!(c::TCSANOW, 0);
        const_assert_eq!(c::TCSADRAIN, 1);
        const_assert_eq!(c::TCSAFLUSH, 2);
    }
}

#[cfg(linux_kernel)]
#[test]
fn termios_cibaud() {
    // Test an assumption.
    const_assert_eq!(c::CIBAUD, c::CBAUD << c::IBSHIFT);
}
