use std::{
    io::{self, Read, Write},
    os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd},
    ptr::null_mut,
};

use crate::{
    io::TerminalSize,
    sys::{PtyMaster, PtySlave, TerminalOptions},
};

pub use libc::termios as TerminalOptionsImpl;

impl TerminalOptions for TerminalOptionsImpl {
    fn raw() -> Self {
        // iflag = 4500
        // oflag = 5
        // cflag = bf
        // lflag = 8a3b
        todo!()
    }

    fn normal() -> Self {
        let mut this = Self {
            c_iflag: libc::IXON | libc::IUTF8 | libc::ICRNL,
            c_cflag: libc::CS8 | libc::CREAD,
            c_lflag: libc::ISIG
                | libc::ICANON
                | libc::ECHO
                | libc::ECHOE
                | libc::ECHOK
                | libc::ECHOCTL
                | libc::ECHOKE
                | libc::IEXTEN,
            c_oflag: libc::ONLCR | libc::OPOST,
            c_line: 0,
            c_cc: [0; libc::NCCS],
            c_ispeed: libc::B38400,
            c_ospeed: libc::B38400,
        };

        this.c_cc[libc::VINTR] = 0x03;
        this.c_cc[libc::VEOF] = 0x04;
        this.c_cc[libc::VERASE] = 0x7F;
        this.c_cc[libc::VWERASE] = 0x17;
        this.c_cc[libc::VKILL] = 0x15;
        this.c_cc[libc::VQUIT] = 0x1C;
        this.c_cc[libc::VSTART] = 0x11;
        this.c_cc[libc::VSTOP] = 0x13;
        this.c_cc[libc::VSUSP] = 0x1A;
        this.c_cc[libc::VLNEXT] = 0x16;
        this.c_cc[libc::VREPRINT] = 0x12;
        this.c_cc[libc::VDISCARD] = 0x0F;
        this.c_cc[libc::VMIN] = 1;

        this
    }

    fn eof_char(&self) -> u8 {
        self.c_cc[libc::VEOF]
    }

    fn erase_char(&self) -> u8 {
        self.c_cc[libc::VERASE]
    }

    fn word_erase_char(&self) -> u8 {
        self.c_cc[libc::VWERASE]
    }

    fn interrupt_char(&self) -> u8 {
        self.c_cc[libc::VINTR]
    }
}

struct PtyFd(OwnedFd);

pub struct PtyMasterImpl {
    fd: PtyFd,
}

pub struct PtySlaveImpl {
    fd: PtyFd,
}

impl Read for PtyFd {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let len = unsafe { libc::read(self.0.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) };
        if len < 0 {
            Err(io::Error::last_os_error())
        } else {
            Ok(len as _)
        }
    }
}

impl Write for PtyFd {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let len = unsafe { libc::write(self.0.as_raw_fd(), buf.as_ptr().cast(), buf.len()) };
        if len < 0 {
            Err(io::Error::last_os_error())
        } else {
            Ok(len as _)
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

impl PtyMaster for PtyMasterImpl {
    fn resize_terminal(&mut self, size: TerminalSize) -> io::Result<()> {
        // let term_size = TerminalSize { rows, columns };
        // let mut buffer = [0; 64];
        // device::device_request::<device::SetTerminalSize>(
        //     pty_master.as_raw_fd(),
        //     &mut buffer,
        //     &term_size,
        // )
        // .unwrap();
        let size = libc::winsize {
            ws_row: size.rows,
            ws_col: size.columns,
            ws_xpixel: size.x_pixels as _,
            ws_ypixel: size.y_pixels as _,
        };

        if unsafe { libc::ioctl(self.as_raw_fd(), libc::TIOCSWINSZ, &raw const size) } < 0 {
            return Err(io::Error::last_os_error());
        }

        Ok(())
    }
}

impl Read for PtyMasterImpl {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.fd.read(buf)
    }
}

impl Write for PtyMasterImpl {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.fd.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.fd.flush()
    }
}

impl AsRawFd for PtyMasterImpl {
    fn as_raw_fd(&self) -> RawFd {
        self.fd.0.as_raw_fd()
    }
}

impl IntoRawFd for PtyMasterImpl {
    fn into_raw_fd(self) -> RawFd {
        self.fd.0.into_raw_fd()
    }
}

impl PtySlave for PtySlaveImpl {}

impl Read for PtySlaveImpl {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.fd.read(buf)
    }
}

impl Write for PtySlaveImpl {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.fd.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.fd.flush()
    }
}

impl AsRawFd for PtySlaveImpl {
    fn as_raw_fd(&self) -> RawFd {
        self.fd.0.as_raw_fd()
    }
}

impl IntoRawFd for PtySlaveImpl {
    fn into_raw_fd(self) -> RawFd {
        self.fd.0.into_raw_fd()
    }
}

pub fn open(
    options: &TerminalOptionsImpl,
    size: TerminalSize,
) -> io::Result<(PtyMasterImpl, PtySlaveImpl)> {
    let mut amaster = 0;
    let mut aslave = 0;
    let winp = libc::winsize {
        ws_row: size.rows,
        ws_col: size.columns,
        ws_xpixel: size.x_pixels as _,
        ws_ypixel: size.y_pixels as _,
    };

    if unsafe {
        libc::openpty(
            &raw mut amaster,
            &raw mut aslave,
            null_mut(),
            options,
            &raw const winp,
        )
    } != 0
    {
        return Err(io::Error::last_os_error());
    }

    let amaster = PtyFd(unsafe { OwnedFd::from_raw_fd(amaster) });
    let aslave = PtyFd(unsafe { OwnedFd::from_raw_fd(aslave) });

    Ok((PtyMasterImpl { fd: amaster }, PtySlaveImpl { fd: aslave }))
}
