use std::{
    fs::File,
    io::{self, Read, Write},
    mem::MaybeUninit,
    os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
};

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

pub use runtime::abi::io::TerminalOptions as TerminalOptionsImpl;
use runtime::{
    abi::io::{
        TerminalControlCharacters, TerminalInputOptions, TerminalLineOptions, TerminalOutputOptions,
    },
    rt::io::device,
};

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

    fn normal() -> Self {
        Self {
            output: TerminalOutputOptions::NL_TO_CRNL,
            input: TerminalInputOptions::CR_TO_NL,
            line: TerminalLineOptions::SIGNAL
                | TerminalLineOptions::CANONICAL
                | TerminalLineOptions::ECHO
                | TerminalLineOptions::ECHO_KILL
                | TerminalLineOptions::ECHO_ERASE,
            chars: TerminalControlCharacters {
                interrupt: 0x03,
                eof: 0x04,
                erase: 0x7F,
                kill: 0x15,
                werase: 0x17,
            },
            baud_rate: 115200,
        }
    }

    fn eof_char(&self) -> u8 {
        self.chars.eof
    }

    fn erase_char(&self) -> u8 {
        self.chars.erase
    }

    fn word_erase_char(&self) -> u8 {
        self.chars.werase
    }

    fn interrupt_char(&self) -> u8 {
        self.chars.interrupt
    }
}

pub struct PtyMasterImpl {
    fd: File,
}

pub struct PtySlaveImpl {
    fd: File,
}

impl PtyMaster for PtyMasterImpl {
    fn resize_terminal(&mut self, size: TerminalSize) -> io::Result<()> {
        let term_size = runtime::rt::io::terminal::TerminalSize {
            rows: size.rows as _,
            columns: size.columns as _,
        };
        let mut buffer = [0; 64];
        device::device_request::<device::SetTerminalSize>(
            self.fd.as_raw_fd(),
            &mut buffer,
            &term_size,
        )?;

        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.as_raw_fd()
    }
}

impl IntoRawFd for PtyMasterImpl {
    fn into_raw_fd(self) -> RawFd {
        self.fd.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.as_raw_fd()
    }
}

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

pub fn open(
    options: &TerminalOptionsImpl,
    size: TerminalSize,
) -> io::Result<(PtyMasterImpl, PtySlaveImpl)> {
    let size = runtime::abi::io::TerminalSize {
        rows: size.rows as _,
        columns: size.columns as _,
    };

    let mut fds = [MaybeUninit::uninit(); 2];
    unsafe { runtime::rt::sys::create_pty(options, &size, &mut fds) }?;
    let fds = unsafe { MaybeUninit::array_assume_init(fds) };

    let master = unsafe { File::from_raw_fd(fds[0]) };
    let slave = unsafe { File::from_raw_fd(fds[1]) };

    Ok((PtyMasterImpl { fd: master }, PtySlaveImpl { fd: slave }))
}
