use core::{ffi::c_int, mem::MaybeUninit};

use yggdrasil_rt::{
    io::{FileAttr, FileMode, RawFd, SeekFrom},
    path::{Path, PathBuf},
    sys as syscall,
};

use crate::{
    error::{EResult, TryFromExt},
    headers::{
        errno::Errno,
        stdio::{SEEK_CUR, SEEK_END, SEEK_SET},
        sys_types::off_t,
    },
};

pub mod buffer;
pub mod dir;
pub mod managed;
pub mod raw;

pub trait ReadAt {
    fn read_at(&mut self, pos: u64, buffer: &mut [u8]) -> EResult<usize>;
}

pub trait WriteAt {
    fn write_at(&mut self, pos: u64, buffer: &[u8]) -> EResult<usize>;
}

pub trait Write {
    fn write(&mut self, data: &[u8]) -> EResult<usize>;
    fn flush(&mut self) -> EResult<()>;

    fn write_chunked(&mut self, data: &[u8]) -> (usize, EResult<()>) {
        let mut pos = 0;
        let len = data.len();

        while pos < len {
            match self.write(data) {
                EResult::Ok(0) => todo!(),
                EResult::Ok(n) => pos += n,
                EResult::Err(Errno::EINTR) => todo!(),
                EResult::Err(err) => return (pos, EResult::Err(err)),
            }
        }

        (pos, EResult::Ok(()))
    }

    fn write_all(&mut self, data: &[u8]) -> EResult<()> {
        match self.write(data) {
            EResult::Ok(n) if n == data.len() => EResult::Ok(()),
            EResult::Ok(_) => todo!(),
            EResult::Err(err) => EResult::Err(err),
        }
    }
}

pub trait Read {
    fn read(&mut self, data: &mut [u8]) -> EResult<usize>;

    fn read_exact(&mut self, buf: &mut [u8]) -> EResult<()> {
        match self.read(buf) {
            EResult::Ok(n) if n == buf.len() => EResult::Ok(()),
            EResult::Ok(_) => todo!(),
            EResult::Err(err) => EResult::Err(err),
        }
    }
}

pub trait BufRead {
    fn fill_buf(&mut self) -> EResult<&[u8]>;
    fn consume(&mut self, amount: usize);
}

pub trait Seek {
    fn seek(&mut self, pos: SeekFrom) -> EResult<u64>;

    fn stream_position(&mut self) -> EResult<u64> {
        self.seek(SeekFrom::Current(0))
    }
}

pub trait FromRawFd {
    unsafe fn from_raw_fd(fd: RawFd) -> Self;
}

pub trait IntoRawFd {
    fn into_raw_fd(self) -> RawFd;
}

pub trait AsRawFd {
    fn as_raw_fd(&self) -> RawFd;
}

impl TryFromExt<c_int> for RawFd {
    fn e_try_from(value: c_int) -> EResult<Self> {
        match value {
            0.. => EResult::Ok(unsafe { Self::from_raw(value as _) }),
            _ => EResult::Err(Errno::EBADF),
        }
    }
}

impl TryFromExt<(off_t, c_int)> for SeekFrom {
    fn e_try_from((offset, whence): (off_t, c_int)) -> EResult<Self> {
        let offset: i64 = offset.try_into().unwrap();
        match whence {
            SEEK_SET => {
                if offset < 0 {
                    return EResult::Err(Errno::EINVAL);
                }
                EResult::Ok(Self::Start(offset as _))
            }
            SEEK_CUR => EResult::Ok(Self::Current(offset)),
            SEEK_END => EResult::Ok(Self::End(offset)),
            _ => EResult::Err(Errno::EINVAL),
        }
    }
}

pub fn create_pipe(read_nonblocking: bool, write_nonblocking: bool) -> EResult<(RawFd, RawFd)> {
    let (read, write) = yggdrasil_rt::io::create_pipe_pair(read_nonblocking, write_nonblocking)?;
    EResult::Ok((read, write))
}

pub fn get_metadata(at: Option<RawFd>, path: &str, follow: bool) -> EResult<FileAttr> {
    let mut metadata = MaybeUninit::uninit();
    unsafe { syscall::get_metadata(at, path, &mut metadata, follow) }?;
    EResult::Ok(unsafe { metadata.assume_init() })
}

pub fn create_directory(at: Option<RawFd>, path: &str, mode: FileMode) -> EResult<()> {
    unsafe { syscall::create_directory(at, path, mode) }?;
    EResult::Ok(())
}

pub fn set_current_directory(at: Option<RawFd>, path: &str) -> EResult<()> {
    // TODO
    assert!(at.is_none());
    yggdrasil_rt::io::set_current_directory(path)?;
    EResult::Ok(())
}

pub fn get_current_directory<T, F: FnOnce(&str) -> EResult<T>>(mapper: F) -> EResult<T> {
    let result = yggdrasil_rt::io::current_directory(mapper)??;
    EResult::Ok(result)
}

pub fn realpath<P: AsRef<Path>>(path: P) -> EResult<PathBuf> {
    // Cases:
    // * /a/b/c -> /a/b/c
    // * /a/../c -> /c
    // * /a/./c -> /a/c
    // * a/./c -> /dir/a/c
    let mut path = path.as_ref();
    let mut buffer = PathBuf::from("/");

    log::debug!("realpath({path:?})");

    loop {
        if path.is_empty() {
            break;
        }
        let (head, tail) = path.split_left();

        log::debug!("* {head:?}");

        match head {
            ".." => {
                // Remove the last element
                buffer = buffer.split_right().0.into();
            }
            "." | "" => (),
            name => {
                buffer.push(name);
            }
        }

        path = tail;
    }

    log::debug!("-> {buffer:?}");

    EResult::Ok(buffer)
}

pub unsafe fn init() {
    managed::init();
}

pub unsafe fn cleanup() {
    managed::cleanup();
}
