/*
 * Description: I/O traits more general than std::io.
 *
 * Copyright (C) 2025 d@nny mc² <dmc2@amass.energy>
 * SPDX-License-Identifier: AGPL-3.0-or-later
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

/* Ensure any doctest warnings fail the doctest! */
#![doc(test(attr(deny(warnings))))]
/* #![warn(missing_docs)] */
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(
  feature = "nightly-specialization",
  allow(incomplete_features),
  feature(specialization)
)]
#![cfg_attr(feature = "nightly-iter", feature(trusted_len))]

//! I/O traits more general than [`std::io`](https://doc.rust-lang.org/std/io/index.html).

use core::{mem, ops};
#[cfg(feature = "std-io")]
use std::io;
#[cfg(all(unix, feature = "std-io"))]
use std::{
  fs::File,
  io::{PipeReader, PipeWriter},
  os::fd::AsRawFd,
};

#[cfg(all(unix, feature = "std-io"))]
use libc;

pub mod os;
#[cfg(feature = "vectored")]
pub mod vectored;

pub trait BufError {
  fn would_block(&self) -> bool;
  fn was_interrupted(&self) -> bool;
}

#[cfg(feature = "std-io")]
impl BufError for io::Error {
  #[inline(always)]
  fn would_block(&self) -> bool {
    match self.kind() {
      io::ErrorKind::WouldBlock => true,
      _ => false,
    }
  }
  #[inline(always)]
  fn was_interrupted(&self) -> bool {
    match self.kind() {
      io::ErrorKind::Interrupted => true,
      _ => false,
    }
  }
}

#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct BlockingPipe<P>(P);
impl<P> BlockingPipe<P> {
  #[inline(always)]
  pub fn into_inner(self) -> P {
    let Self(pipe) = self;
    pipe
  }
}
impl<P> ops::Deref for BlockingPipe<P> {
  type Target = P;
  #[inline(always)]
  fn deref(&self) -> &Self::Target { &self.0 }
}
impl<P> ops::DerefMut for BlockingPipe<P> {
  #[inline(always)]
  fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}
#[cfg(all(unix, feature = "std-io"))]
impl BlockingPipe<PipeReader> {
  #[inline(always)]
  pub fn new_reader(pipe: PipeReader) -> io::Result<Self> {
    let fd = pipe.as_raw_fd();
    match unsafe { libc::fcntl(fd, libc::F_GETFL) } {
      -1 => Err(io::Error::last_os_error()),
      n if n | libc::O_NONBLOCK != 0 => Err(io::Error::new(
        io::ErrorKind::InvalidInput,
        "was given nonblocking pipe reader fd",
      )),
      _ => Ok(Self(pipe)),
    }
  }
}
#[cfg(all(unix, feature = "std-io"))]
impl BlockingPipe<PipeWriter> {
  #[inline(always)]
  pub fn new_writer(pipe: PipeWriter) -> io::Result<Self> {
    let fd = pipe.as_raw_fd();
    match unsafe { libc::fcntl(fd, libc::F_GETFL) } {
      -1 => Err(io::Error::last_os_error()),
      n if n | libc::O_NONBLOCK != 0 => Err(io::Error::new(
        io::ErrorKind::InvalidInput,
        "was given nonblocking pipe writer fd",
      )),
      _ => Ok(Self(pipe)),
    }
  }
}

#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct NonBlockingPipe<P>(P);
impl<P> NonBlockingPipe<P> {
  #[inline(always)]
  pub fn into_inner(self) -> P {
    let Self(pipe) = self;
    pipe
  }
}
impl<P> ops::Deref for NonBlockingPipe<P> {
  type Target = P;
  #[inline(always)]
  fn deref(&self) -> &Self::Target { &self.0 }
}
impl<P> ops::DerefMut for NonBlockingPipe<P> {
  #[inline(always)]
  fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}
#[cfg(all(unix, feature = "std-io"))]
impl NonBlockingPipe<PipeReader> {
  #[inline(always)]
  pub fn new_reader(pipe: PipeReader) -> io::Result<Self> {
    let fd = pipe.as_raw_fd();
    match unsafe { libc::fcntl(fd, libc::F_GETFL) } {
      -1 => Err(io::Error::last_os_error()),
      n if n | libc::O_NONBLOCK == 0 => Err(io::Error::new(
        io::ErrorKind::InvalidInput,
        "was given blocking pipe reader fd",
      )),
      _ => Ok(Self(pipe)),
    }
  }
}
#[cfg(all(unix, feature = "std-io"))]
impl NonBlockingPipe<PipeWriter> {
  #[inline(always)]
  pub fn new_writer(pipe: PipeWriter) -> io::Result<Self> {
    let fd = pipe.as_raw_fd();
    match unsafe { libc::fcntl(fd, libc::F_GETFL) } {
      -1 => Err(io::Error::last_os_error()),
      n if n | libc::O_NONBLOCK == 0 => Err(io::Error::new(
        io::ErrorKind::InvalidInput,
        "was given blocking pipe writer fd",
      )),
      _ => Ok(Self(pipe)),
    }
  }
}

pub trait ReadBuf<T> {
  type E: BufError;
  fn read_buf<'buf>(&mut self, buf: &'buf mut [mem::MaybeUninit<T>]) -> (usize, Option<Self::E>);
  #[inline]
  fn read_buf_exact<'buf>(
    &mut self,
    mut buf: &'buf mut [mem::MaybeUninit<T>],
  ) -> Result<(), (usize, Self::E)> {
    let mut all_read: usize = 0;
    while !buf.is_empty() {
      let (cur_read, err) = self.read_buf(buf);
      all_read += cur_read;
      if let Some(err) = err {
        if err.would_block() {
          unreachable!("read_buf() is allowed to block!");
        }
        if !err.was_interrupted() {
          return Err((all_read, err));
        }
      }
      buf = &mut buf[cur_read..];
    }
    Ok(())
  }
}
#[cfg(all(unix, feature = "std-io"))]
impl ReadBuf<u8> for File {
  type E = io::Error;
  #[inline]
  fn read_buf<'buf>(&mut self, buf: &'buf mut [mem::MaybeUninit<u8>]) -> (usize, Option<Self::E>) {
    let fd = self.as_raw_fd();
    let count = buf.len();
    let buf: *mut libc::c_void = buf.as_mut_ptr().cast();
    match unsafe { libc::read(fd, buf, count) } {
      -1 => (0, Some(io::Error::last_os_error())),
      n => (n as usize, None),
    }
  }
}
#[cfg(all(unix, feature = "std-io"))]
impl ReadBuf<u8> for BlockingPipe<PipeReader> {
  type E = io::Error;
  #[inline]
  fn read_buf<'buf>(&mut self, buf: &'buf mut [mem::MaybeUninit<u8>]) -> (usize, Option<Self::E>) {
    let fd = self.as_raw_fd();
    let count = buf.len();
    let buf: *mut libc::c_void = buf.as_mut_ptr().cast();
    match unsafe { libc::read(fd, buf, count) } {
      -1 => (0, Some(io::Error::last_os_error())),
      n => (n as usize, None),
    }
  }
}

pub trait ReadBufAtomic<T> {
  type E: BufError;
  fn read_buf_atomic<'buf>(&mut self, buf: &'buf mut [mem::MaybeUninit<T>]) -> Result<(), Self::E>;
}

pub trait ReadBufNonBlocking<T> {
  type E: BufError;
  fn read_buf_nonblocking<'buf>(
    &mut self,
    buf: &'buf mut [mem::MaybeUninit<T>],
  ) -> Result<usize, Self::E>;
}
#[cfg(all(unix, feature = "std-io"))]
impl ReadBufNonBlocking<u8> for NonBlockingPipe<PipeReader> {
  type E = io::Error;
  #[inline]
  fn read_buf_nonblocking<'buf>(
    &mut self,
    buf: &'buf mut [mem::MaybeUninit<u8>],
  ) -> Result<usize, Self::E> {
    let fd = self.as_raw_fd();
    let count = buf.len();
    let buf: *mut libc::c_void = buf.as_mut_ptr().cast();
    match unsafe { libc::read(fd, buf, count) } {
      -1 => Err(io::Error::last_os_error()),
      n => Ok(n as usize),
    }
  }
}

pub trait WriteData<T> {
  type E: BufError;
  fn write_data<'data>(&mut self, data: &'data [T]) -> (usize, Option<Self::E>);
  #[inline]
  fn write_data_exact<'data>(&mut self, mut data: &'data [T]) -> Result<(), (usize, Self::E)> {
    let mut all_written: usize = 0;
    while !data.is_empty() {
      let (cur_written, err) = self.write_data(data);
      all_written += cur_written;
      if let Some(err) = err {
        if err.would_block() {
          unreachable!("write_data() is allowed to block!");
        }
        if !err.was_interrupted() {
          return Err((all_written, err));
        }
      }
      data = &data[cur_written..];
    }
    Ok(())
  }
}
#[cfg(all(unix, feature = "std-io"))]
impl WriteData<u8> for File {
  type E = io::Error;
  #[inline]
  fn write_data<'data>(&mut self, data: &'data [u8]) -> (usize, Option<Self::E>) {
    let fd = self.as_raw_fd();
    let count = data.len();
    let buf: *const libc::c_void = data.as_ptr().cast();
    match unsafe { libc::write(fd, buf, count) } {
      -1 => (0, Some(io::Error::last_os_error())),
      n => (n as usize, None),
    }
  }
}
#[cfg(all(unix, feature = "std-io"))]
impl WriteData<u8> for BlockingPipe<PipeWriter> {
  type E = io::Error;
  #[inline]
  fn write_data<'data>(&mut self, data: &'data [u8]) -> (usize, Option<Self::E>) {
    let fd = self.as_raw_fd();
    let count = data.len();
    let buf: *const libc::c_void = data.as_ptr().cast();
    match unsafe { libc::write(fd, buf, count) } {
      -1 => (0, Some(io::Error::last_os_error())),
      n => (n as usize, None),
    }
  }
}

pub trait WriteDataAtomic<T> {
  type E: BufError;
  fn write_data_atomic<'data>(&mut self, data: &'data [T]) -> Result<(), Self::E>;
}

pub trait WriteDataNonBlocking<T> {
  type E: BufError;
  fn write_data_nonblocking<'data>(&mut self, data: &'data [T]) -> Result<usize, Self::E>;
}
#[cfg(all(unix, feature = "std-io"))]
impl WriteDataNonBlocking<u8> for NonBlockingPipe<PipeWriter> {
  type E = io::Error;
  #[inline]
  fn write_data_nonblocking<'data>(&mut self, data: &'data [u8]) -> Result<usize, Self::E> {
    let fd = self.as_raw_fd();
    let count = data.len();
    let buf: *const libc::c_void = data.as_ptr().cast();
    match unsafe { libc::write(fd, buf, count) } {
      -1 => Err(io::Error::last_os_error()),
      n => Ok(n as usize),
    }
  }
}
