use std::{
    fs::{File, OpenOptions},
    io::{self, Read, Write},
    mem::MaybeUninit,
    os::{
        fd::{AsRawFd, IntoRawFd, RawFd},
        unix::fs::OpenOptionsExt,
    },
    path::Path,
};

use crate::{io::SerialPortSettings, sys::SerialPort};

pub struct SerialPortImpl(File);

impl SerialPort for SerialPortImpl {
    fn open<P: AsRef<Path>>(device: P, options: &SerialPortSettings) -> io::Result<Self> {
        let baud = match options.baud_rate {
            Some(9600) => libc::B9600,
            Some(115200) | None => libc::B115200,
            Some(230400) => libc::B230400,
            Some(921600) => libc::B921600,
            _ => libc::B115200,
        };

        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .custom_flags(libc::O_NOCTTY)
            .open(device)?;

        let mut termios = MaybeUninit::uninit();
        if unsafe { libc::tcgetattr(file.as_raw_fd(), termios.as_mut_ptr()) } != 0 {
            return Err(io::Error::last_os_error());
        }
        let mut termios = unsafe { termios.assume_init() };
        unsafe { libc::cfmakeraw(&raw mut termios) };
        termios.c_ispeed = baud;
        termios.c_ospeed = baud;

        if unsafe { libc::tcsetattr(file.as_raw_fd(), libc::TCSANOW, &raw const termios) } != 0 {
            return Err(io::Error::last_os_error());
        }

        Ok(Self(file))
    }
}

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

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

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

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

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