//
// Syd: rock-solid application kernel
// src/io.rs: I/O utilities
//
// Copyright (c) 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    fs::File,
    io::{Read, Stdin, Write},
    os::fd::{AsFd, AsRawFd},
};

use libc::{syscall, SYS_ioctl};
use nix::{
    errno::Errno,
    pty::Winsize,
    sys::sendfile::sendfile64,
    unistd::{read, write},
};

use crate::{
    err2no,
    ioctl::{TIOCGWINSZ, TIOCSWINSZ},
    retry::retry_on_eintr,
};

/// Get window-size from the given FD.
pub fn winsize_get<Fd: AsFd>(fd: Fd) -> Result<Winsize, Errno> {
    let fd = fd.as_fd().as_raw_fd();
    let req = TIOCGWINSZ.ok_or(Errno::ENOTTY)?;
    let mut ws = Winsize {
        ws_row: 0,
        ws_col: 0,
        ws_xpixel: 0,
        ws_ypixel: 0,
    };

    // SAFETY: In libc we trust.
    Errno::result(unsafe { syscall(SYS_ioctl, fd, req, &mut ws) })?;

    Ok(ws)
}

/// Set window-size for the given FD.
pub fn winsize_set<Fd: AsFd>(fd: Fd, ws: Winsize) -> Result<(), Errno> {
    let fd = fd.as_fd().as_raw_fd();
    let req = TIOCSWINSZ.ok_or(Errno::ENOTTY)?;

    // SAFETY: In libc we trust.
    Errno::result(unsafe { syscall(SYS_ioctl, fd, req, &ws) }).map(drop)
}

/// Read until EOF or `buf` is full from the given file.
///
/// Returns number of bytes read. NO-OP if `buf` is empty.
pub fn read_all<Fd: AsFd>(fd: Fd, buf: &mut [u8]) -> Result<usize, Errno> {
    let mut nread = 0;

    while nread < buf.len() {
        match retry_on_eintr(|| read(&fd, &mut buf[nread..]))? {
            0 => break,
            n => nread = nread.checked_add(n).ok_or(Errno::EOVERFLOW)?,
        }
    }

    Ok(nread)
}

/// Write all the data to the given file.
///
/// Returns `Errno::EPIPE` on EOF. NO-OP if data is empty.
pub fn write_all<Fd: AsFd>(fd: Fd, data: &[u8]) -> Result<(), Errno> {
    let mut nwrite = 0;

    while nwrite < data.len() {
        match retry_on_eintr(|| write(&fd, &data[nwrite..]))? {
            0 => return Err(Errno::EPIPE),
            n => nwrite = nwrite.checked_add(n).ok_or(Errno::EOVERFLOW)?,
        }
    }

    Ok(())
}

/// Super trait: AsFd + Read.
pub trait ReadFd: AsFd + Read {}

/// Super trait: AsFd + Write.
pub trait WriteFd: AsFd + Write {}

impl ReadFd for File {}
impl ReadFd for Stdin {}

impl WriteFd for File {}

/// Copy all available data from one file to another.
///
/// Uses `nix::fcntl::sendfile64` and falls back to `std::io::copy`
/// on errors `Err(Errno::EINVAL)` and `Err(Errno::ENOSYS)`.
pub fn copy<Fd1, Fd2>(src: &mut Fd1, dst: &mut Fd2) -> Result<u64, Errno>
where
    Fd1: ReadFd,
    Fd2: WriteFd,
{
    // sendfile() will transfer at most 0x7ffff000 (2,147,479,552) bytes,
    // returning the number of bytes actually transferred. (This is true on
    // both 32-bit and 64-bit systems.)
    const MAX: usize = 0x7ffff000;

    let mut ncopy = 0;
    loop {
        return match sendfile64(&dst, &src, None, MAX) {
            Ok(0) => Ok(ncopy),
            Ok(n) => {
                let n = n.try_into().or(Err(Errno::EOVERFLOW))?;
                ncopy = ncopy.checked_add(n).ok_or(Errno::EOVERFLOW)?;
                continue;
            }
            Err(Errno::EINTR) => continue,
            Err(Errno::EINVAL | Errno::ENOSYS) =>
            {
                #[expect(clippy::disallowed_methods)]
                std::io::copy(src, dst).map_err(|err| err2no(&err))
            }
            Err(errno) => Err(errno),
        };
    }
}
