use std::{
    io::{self, Read, Write},
    os::fd::{AsRawFd, IntoRawFd, RawFd},
    path::Path,
    process::Stdio,
    time::Duration,
};

use crate::sys::{
    self, PidFd as SysPidFd, Pipe as SysPipe, Poll as SysPoll, PtyMaster as SysPtyMaster,
    RawStdin as SysRawStdin, SerialPort as SysSerialPort, TimerFd as SysTimerFd,
};

use self::sys::PipeImpl;

pub use crate::sys::{TerminalOptions, TerminalOptionsImpl};

#[repr(transparent)]
pub struct Poll(sys::PollImpl);

#[repr(transparent)]
pub struct TimerFd(sys::TimerFdImpl);

#[repr(transparent)]
pub struct PidFd(sys::PidFdImpl);

#[repr(transparent)]
pub struct Pipe(sys::PipeImpl);

#[repr(transparent)]
pub struct RawStdin(sys::RawStdinImpl);

#[repr(transparent)]
pub struct PtySlave(sys::PtySlaveImpl);

#[repr(transparent)]
pub struct PtyMaster(sys::PtyMasterImpl);

#[repr(transparent)]
pub struct SerialPort(sys::SerialPortImpl);

#[derive(Clone, Copy, PartialEq, Debug)]
pub struct TerminalSize {
    pub rows: u16,
    pub columns: u16,
    pub x_pixels: u32,
    pub y_pixels: u32,
}

#[derive(Clone, Copy, PartialEq, Debug)]
pub struct SerialPortSettings {
    pub baud_rate: Option<u32>,
    // TODO parity, stop bits, etc
}

impl Poll {
    pub fn new() -> io::Result<Self> {
        sys::PollImpl::new().map(Self)
    }

    pub fn add<F: AsRawFd>(&mut self, interest: &F) -> io::Result<()> {
        self.0.add(interest)
    }

    pub fn remove<F: AsRawFd>(&mut self, interest: &F) -> io::Result<()> {
        self.0.remove(interest)
    }

    pub fn wait(&mut self, timeout: Option<Duration>) -> io::Result<Option<RawFd>> {
        self.0.wait(timeout)
    }
}

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

impl TimerFd {
    pub fn new() -> io::Result<Self> {
        sys::TimerFdImpl::new().map(Self)
    }

    pub fn start(&mut self, timeout: Duration) -> io::Result<()> {
        self.0.start(timeout)
    }

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

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

impl PidFd {
    pub fn new(pid: u32) -> io::Result<Self> {
        sys::PidFdImpl::new(pid).map(Self)
    }

    pub fn exit_status(&self) -> io::Result<i32> {
        self.0.exit_status()
    }
}

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

impl Pipe {
    pub fn new(read_nonblocking: bool, write_nonblocking: bool) -> io::Result<(Self, Self)> {
        let (read, write) = PipeImpl::new(read_nonblocking, write_nonblocking)?;
        Ok((Self(read), Self(write)))
    }

    pub fn to_child_stdio(&self) -> Stdio {
        self.0.to_child_stdio()
    }
}

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

impl Write for Pipe {
    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 Pipe {
    fn as_raw_fd(&self) -> RawFd {
        self.0.as_raw_fd()
    }
}

impl RawStdin {
    pub fn open() -> io::Result<Self> {
        sys::RawStdinImpl::open().map(Self)
    }

    pub fn set_options(&mut self, options: TerminalOptionsImpl) -> io::Result<TerminalOptionsImpl> {
        self.0.set_options(options)
    }
}

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

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

impl PtyMaster {
    pub fn resize_terminal(&mut self, size: TerminalSize) -> io::Result<()> {
        self.0.resize_terminal(size)
    }
}

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

impl Write for PtyMaster {
    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 PtyMaster {
    fn as_raw_fd(&self) -> RawFd {
        self.0.as_raw_fd()
    }
}

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

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

impl Write for PtySlave {
    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 PtySlave {
    fn as_raw_fd(&self) -> RawFd {
        self.0.as_raw_fd()
    }
}

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

impl SerialPort {
    pub fn open<P: AsRef<Path>>(device: P, options: &SerialPortSettings) -> io::Result<Self> {
        sys::SerialPortImpl::open(device, options).map(Self)
    }
}

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

impl Write for SerialPort {
    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 SerialPort {
    fn as_raw_fd(&self) -> RawFd {
        self.0.as_raw_fd()
    }
}

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

pub fn open_pty(
    options: &TerminalOptionsImpl,
    size: TerminalSize,
) -> io::Result<(PtyMaster, PtySlave)> {
    let (m, s) = sys::open_pty(options, size)?;
    Ok((PtyMaster(m), PtySlave(s)))
}

pub fn clone_fd(fd: RawFd) -> io::Result<RawFd> {
    sys::clone_fd(fd)
}
