use core::ffi::{c_char, c_int, c_short, VaList};

use yggdrasil_rt::io::{FileMode, OpenOptions};

use crate::{
    error::{CFdResult, CIntCountResult, EResult, TryFromExt},
    io::{raw::RawFile, IntoRawFd},
    util::{self, PointerStrExt},
};

use super::{
    errno::Errno,
    sys_types::{mode_t, off_t, pid_t},
};

#[repr(C)]
pub struct flock {
    pub l_type: c_short,
    pub l_whence: c_short,
    pub l_start: off_t,
    pub l_len: off_t,
    pub l_pid: pid_t,
}

pub const F_DUPFD: c_int = 1;
pub const F_DUPFD_CLOEXEC: c_int = 2;
pub const F_GETFD: c_int = 3;
pub const F_SETFD: c_int = 4;
pub const F_GETFL: c_int = 5;
pub const F_SETFL: c_int = 6;
pub const F_GETLK: c_int = 7;
pub const F_SETLK: c_int = 8;
pub const F_SETLKW: c_int = 9;
pub const F_GETOWN: c_int = 10;
pub const F_SETOWN: c_int = 11;

pub const FD_CLOEXEC: c_int = 1 << 0;

pub const F_RDLCK: c_int = 1;
pub const F_UNLCK: c_int = 2;
pub const F_WRLCK: c_int = 3;

pub const O_CLOEXEC: c_int = 1 << 16;
pub const O_CREAT: c_int = 1 << 17;
pub const O_DIRECTORY: c_int = 1 << 18;
pub const O_EXCL: c_int = 1 << 19;
pub const O_NOCTTY: c_int = 1 << 20;
pub const O_NOFOLLOW: c_int = 1 << 21;
pub const O_TRUNC: c_int = 1 << 22;
pub const O_TTY_INIT: c_int = 1 << 23;

pub const O_APPEND: c_int = 1 << 24;
pub const O_DSYNC: c_int = 1 << 25;
pub const O_NONBLOCK: c_int = 1 << 26;
pub const O_RSYNC: c_int = 1 << 27;
pub const O_SYNC: c_int = 1 << 28;

pub const O_ACCMODE: c_int = 0xFF;
pub const O_RDONLY: c_int = 1;
pub const O_WRONLY: c_int = 2;
pub const O_RDWR: c_int = 3;
pub const O_EXEC: c_int = 4;
pub const O_SEARCH: c_int = 5;

pub const AT_FDCWD: c_int = -65536;

pub const AT_EACCESS: c_int = 1 << 0;
pub const AT_SYMLINK_NOFOLLOW: c_int = 1 << 1;
pub const AT_REMOVEDIR: c_int = 1 << 2;

pub const POSIX_FADV_DONTNEED: c_int = 1;
pub const POSIX_FADV_NOREUSE: c_int = 2;
pub const POSIX_FADV_NORMAL: c_int = 3;
pub const POSIX_FADV_RANDOM: c_int = 4;
pub const POSIX_FADV_SEQUENTIAL: c_int = 5;
pub const POSIX_FADV_WILLNEED: c_int = 6;

enum OpenMode {
    File(OpenOptions, FileMode),
    Directory,
}

fn open_opts(opts: c_int, ap: &mut VaList) -> EResult<OpenMode> {
    if opts & O_DIRECTORY != 0 {
        if opts & !(O_DIRECTORY | O_SEARCH) != 0 {
            todo!("***open(O_DIRECTORY | {:#x})", opts & !O_DIRECTORY);
        }

        return EResult::Ok(OpenMode::Directory);
    }

    let need_mode = opts & O_CREAT != 0;
    let mut res = OpenOptions::empty();

    match opts & O_ACCMODE {
        O_RDONLY => res |= OpenOptions::READ,
        O_WRONLY => res |= OpenOptions::WRITE,
        O_RDWR => res |= OpenOptions::READ | OpenOptions::WRITE,
        O_EXEC => todo!(),
        O_SEARCH => todo!(),
        _ => {
            return EResult::Err(Errno::EINVAL);
        }
    }

    if opts & O_CREAT != 0 {
        res |= OpenOptions::CREATE;
    }
    if opts & O_EXCL != 0 {
        res |= OpenOptions::CREATE_EXCL;
    }
    if opts & O_APPEND != 0 {
        res |= OpenOptions::APPEND;
    }
    if opts & O_TRUNC != 0 {
        res |= OpenOptions::TRUNCATE;
    }
    if opts & O_NONBLOCK != 0 {
        // TODO O_NONBLOCK
    }
    if opts & O_NOCTTY != 0 {
        // TODO O_NOCTTY
    }
    // TODO O_CLOEXEC

    if opts & (O_DSYNC | O_RSYNC | O_SYNC | O_TTY_INIT | O_NOFOLLOW) != 0 {
        todo!("Unhandled options: {:#x}", opts);
    }

    let mode = if need_mode {
        let raw = unsafe { ap.arg::<c_int>() };
        unsafe { FileMode::from_raw(raw as _) }
    } else {
        FileMode::empty()
    };

    EResult::Ok(OpenMode::File(res, mode))
}

#[no_mangle]
unsafe extern "C" fn fcntl(fd: c_int, cmd: c_int, _args: ...) -> CIntCountResult {
    // TODO kernel support for fcntl
    let _file = RawFile::e_try_from(fd)?;

    match cmd {
        F_GETFD => CIntCountResult::success(0),
        // The only defined option is FD_CLOEXEC, which is not supported
        F_SETFD => CIntCountResult::success(0),
        _ => {
            todo!("fcntl({}, {}, ...)", fd, cmd);
        }
    }
}

unsafe fn vopenat(atfd: c_int, pathname: *const c_char, opts: c_int, mut ap: VaList) -> CFdResult {
    let atfd = util::at_fd(atfd)?;
    let pathname = pathname.ensure_str();

    let fd = match open_opts(opts, &mut ap)? {
        OpenMode::File(opts, mode) => RawFile::open_at(atfd, pathname, opts, mode)?.into_raw_fd(),
        OpenMode::Directory => RawFile::open_directory_at(atfd, pathname)?.into_raw_fd(),
    };

    CFdResult::success(fd)
}

#[no_mangle]
unsafe extern "C" fn creat(pathname: *const c_char, mode: mode_t) -> CFdResult {
    openat(AT_FDCWD, pathname, O_CREAT | O_WRONLY | O_TRUNC, mode)
}

#[no_mangle]
unsafe extern "C" fn open(pathname: *const c_char, opts: c_int, mut args: ...) -> CFdResult {
    vopenat(AT_FDCWD, pathname, opts, args.as_va_list())
}

#[no_mangle]
unsafe extern "C" fn openat(
    atfd: c_int,
    pathname: *const c_char,
    opts: c_int,
    mut args: ...
) -> CFdResult {
    vopenat(atfd, pathname, opts, args.as_va_list())
}

#[no_mangle]
unsafe extern "C" fn posix_fadvise(
    _fd: c_int,
    _offset: off_t,
    _len: off_t,
    _advice: c_int,
) -> c_int {
    log::warn!("posix_fadvise() not implemented yet");
    0
}

#[no_mangle]
unsafe extern "C" fn posix_fallocate(_fd: c_int, _offset: off_t, _size: off_t) -> c_int {
    // Can be a truncate() call with some extra arg for future compat
    todo!("posix_fallocate()")
}
