use std::{
    io::{self, Stdout},
    mem::MaybeUninit,
    os::fd::AsRawFd,
};

pub struct RawMode {
    saved_termios: libc::termios,
}

impl RawMode {
    pub unsafe fn enter<F: AsRawFd>(stdin: &F) -> Result<Self, io::Error> {
        let mut old = MaybeUninit::uninit();

        if libc::tcgetattr(stdin.as_raw_fd(), old.as_mut_ptr()) != 0 {
            return Err(io::Error::last_os_error());
        }

        let old = old.assume_init();
        let mut new = old;
        new.c_lflag &= !(libc::ECHO | libc::ECHONL | libc::ISIG | libc::ICANON | libc::IEXTEN);
        // new.c_lflag &= !(libc::ECHO | libc::ECHONL | libc::ICANON | libc::ISIG | libc::IEXTEN);
        // new.c_iflag &= !(libc::IGNBRK
        //     | libc::BRKINT
        //     | libc::PARMRK
        //     | libc::ISTRIP
        //     | libc::INLCR
        //     | libc::IGNCR
        //     | libc::ICRNL
        //     | libc::IXON);
        // new.c_oflag &= !libc::OPOST;
        new.c_cflag &= !(libc::PARENB | libc::CSIZE);
        new.c_cflag |= libc::CS8;

        if libc::tcsetattr(stdin.as_raw_fd(), libc::TCSANOW, &new) != 0 {
            return Err(io::Error::last_os_error());
        }

        Ok(Self { saved_termios: old })
    }

    pub unsafe fn leave<F: AsRawFd>(&self, stdin: &F) {
        libc::tcsetattr(stdin.as_raw_fd(), libc::TCSANOW, &self.saved_termios);
    }
}

pub unsafe fn terminal_size(stdout: &Stdout) -> io::Result<(usize, usize)> {
    let mut size: MaybeUninit<libc::winsize> = MaybeUninit::uninit();
    if libc::ioctl(stdout.as_raw_fd(), libc::TIOCGWINSZ, size.as_mut_ptr()) != 0 {
        return Err(io::Error::last_os_error());
    }
    let size = size.assume_init();
    Ok((size.ws_col as _, size.ws_row as _))
}
