//! unistd implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/unistd.h.html

use core::{convert::TryFrom, mem, ptr, slice};

pub use self::{brk::*, getopt::*, pathconf::*, sysconf::*};
use crate::unix::platform;
use crate::unix::{
    c_str::CStr,
    header::{errno, limits, stdlib::getenv, sys_time, termios},
};
use alloc::collections::LinkedList;
use ioctl;
use TIOCGPGRP;
use TIOCSPGRP;

mod brk;
mod getopt;
mod pathconf;
mod sysconf;

pub use self::brk::*;

pub const F_OK: ::c_int = 0;
pub const R_OK: ::c_int = 4;
pub const W_OK: ::c_int = 2;
pub const X_OK: ::c_int = 1;

pub const SEEK_SET: ::c_int = 0;
pub const SEEK_CUR: ::c_int = 1;
pub const SEEK_END: ::c_int = 2;

pub const F_ULOCK: ::c_int = 0;
pub const F_LOCK: ::c_int = 1;
pub const F_TLOCK: ::c_int = 2;
pub const F_TEST: ::c_int = 3;

pub const STDIN_FILENO: ::c_int = 0;
pub const STDOUT_FILENO: ::c_int = 1;
pub const STDERR_FILENO: ::c_int = 2;

#[cfg(not(target_os = "dragonos"))]
#[thread_local]
pub static mut fork_hooks_static: Option<[LinkedList<extern "C" fn()>; 3]> = None;

/// due to dragonos not supporting thread_local, we need to use a static directly
#[cfg(target_os = "dragonos")]
pub static mut fork_hooks_static: Option<[LinkedList<extern "C" fn()>; 3]> = None;

unsafe fn init_fork_hooks<'a>() -> &'a mut [LinkedList<extern "C" fn()>; 3] {
    // Transmute the lifetime so we can return here. Should be safe as
    // long as one does not access the original fork_hooks.
    mem::transmute(
        fork_hooks_static
            .get_or_insert_with(|| [LinkedList::new(), LinkedList::new(), LinkedList::new()]),
    )
}

#[no_mangle]
pub extern "C" fn _exit(status: ::c_int) {
    platform::pal::exit(status)
}

pub unsafe fn access(path: *const ::c_char, mode: ::c_int) -> ::c_int {
    platform::pal::access(path, mode)
}

#[no_mangle]
pub extern "C" fn alarm(seconds: ::c_uint) -> ::c_uint {
    let mut timer = sys_time::itimerval {
        it_value: sys_time::timeval {
            tv_sec: seconds as ::time_t,
            tv_usec: 0,
        },
        ..Default::default()
    };
    let errno_backup = unsafe { platform::errno };
    let secs = if ::setitimer(sys_time::ITIMER_REAL, &timer, &mut timer) < 0 {
        0
    } else {
        timer.it_value.tv_sec as ::c_uint + if timer.it_value.tv_usec > 0 { 1 } else { 0 }
    };
    unsafe {
        platform::errno = errno_backup;
    }

    secs
}

// #[no_mangle]
// pub unsafe extern "C" fn chdir(path: *const ::c_char) -> ::c_int {
//     platform::pal::chdir(path)
// }

#[no_mangle]
pub extern "C" fn chroot(_path: *const ::c_char) -> ::c_int {
    // TODO: Implement
    unsafe {
        platform::errno = crate::unix::header::errno::EPERM;
    }

    -1
}

// #[no_mangle]
// pub unsafe extern "C" fn chown(path: *const ::c_char, owner: ::uid_t, group: ::gid_t) -> ::c_int {
//     platform::pal::chown(path, owner, group)
// }

// #[no_mangle]
// pub extern "C" fn close(fildes: ::c_int) -> ::c_int {
//     platform::pal::close(fildes)
// }

// #[no_mangle]
// pub extern "C" fn confstr(name: ::c_int, buf: *mut ::c_char, len: ::size_t) -> ::size_t {
//     unimplemented!();
// }

// #[no_mangle]
// pub extern "C" fn crypt(key: *const ::c_char, salt: *const ::c_char) -> *mut ::c_char {
//     unimplemented!();
// }

// #[no_mangle]
// pub extern "C" fn dup(fildes: ::c_int) -> ::c_int {
//     platform::pal::dup(fildes)
// }

// #[no_mangle]
// pub extern "C" fn dup2(fildes: ::c_int, fildes2: ::c_int) -> ::c_int {
//     platform::pal::dup2(fildes, fildes2)
// }

// #[no_mangle]
// pub extern "C" fn encrypt(block: [::c_char; 64], edflag: ::c_int) {
//     unimplemented!();
// }

// #[no_mangle]
// pub extern "C" fn execl(path: *const ::c_char, args: *const *mut ::c_char) -> ::c_int {
//     unimplemented!();
// }

// #[no_mangle]
// pub extern "C" fn execle(
//   path: *const ::c_char,
//   args: *const *mut ::c_char,
//   envp: *const *mut ::c_char,
// ) -> ::c_int {
//     unimplemented!();
// }

// #[no_mangle]
// pub extern "C" fn execlp(file: *const ::c_char, args: *const *mut ::c_char) -> ::c_int {
//     unimplemented!();
// }

#[no_mangle]
pub unsafe extern "C" fn execv(path: *const ::c_char, argv: *const *const ::c_char) -> ::c_int {
    ::execve(path, argv, platform::environ as *const *const ::c_char)
}

// #[no_mangle]
// pub unsafe extern "C" fn execve(
//     path: *const ::c_char,
//     argv: *const *mut ::c_char,
//     envp: *const *mut ::c_char,
// ) -> ::c_int {
//     platform::pal::execve(path, argv as *const *const i8, envp as *const *const i8)
// }

#[cfg(target_os = "linux")]
const PATH_SEPARATOR: u8 = b':';

#[cfg(target_os = "dragonos")]
const PATH_SEPARATOR: u8 = b':';

#[cfg(target_os = "redox")]
const PATH_SEPARATOR: u8 = b';';

#[no_mangle]
pub unsafe extern "C" fn execvp(file: *const ::c_char, argv: *const *const ::c_char) -> ::c_int {
    let file = CStr::from_ptr(file);

    if file.to_bytes().contains(&b'/')
        || (cfg!(target_os = "redox") && file.to_bytes().contains(&b':'))
    {
        execv(file.as_ptr(), argv)
    } else {
        let mut error = errno::ENOENT;

        let path_env = getenv(c_str!("PATH\0").as_ptr());
        if !path_env.is_null() {
            let path_env = CStr::from_ptr(path_env);
            for path in path_env.to_bytes().split(|&b| b == PATH_SEPARATOR) {
                let mut program = path.to_vec();
                program.push(b'/');
                program.extend_from_slice(file.to_bytes());
                program.push(b'\0');

                let program_c = CStr::from_bytes_with_nul(&program).unwrap();
                execv(program_c.as_ptr(), argv);

                match platform::errno {
                    errno::ENOENT => (),
                    other => error = other,
                }
            }
        }

        platform::errno = error;
        -1
    }
}

// #[no_mangle]
// pub extern "C" fn fchown(fildes: ::c_int, owner: ::uid_t, group: ::gid_t) -> ::c_int {
//     platform::pal::fchown(fildes, owner, group)
// }

// #[no_mangle]
// pub extern "C" fn fchdir(fildes: ::c_int) -> ::c_int {
//     platform::pal::fchdir(fildes)
// }

// #[no_mangle]
pub extern "C" fn fdatasync(_fildes: ::c_int) -> ::c_int {
    unimplemented!();
}

#[no_mangle]
pub extern "C" fn fork() -> ::pid_t {
    let fork_hooks = unsafe { init_fork_hooks() };
    for prepare in &fork_hooks[0] {
        prepare();
    }
    let pid = platform::pal::fork();
    if pid == 0 {
        for child in &fork_hooks[2] {
            child();
        }
    } else if pid != -1 {
        for parent in &fork_hooks[1] {
            parent();
        }
    }
    pid
}

// #[no_mangle]
// pub extern "C" fn fsync(fildes: ::c_int) -> ::c_int {
//     platform::pal::fsync(fildes)
// }

// #[no_mangle]
// pub extern "C" fn ftruncate(fildes: ::c_int, length: ::off_t) -> ::c_int {
//     platform::pal::ftruncate(fildes, length)
// }

#[no_mangle]
pub extern "C" fn getcwd(mut buf: *mut ::c_char, mut size: ::size_t) -> *mut ::c_char {
    let alloc = buf.is_null();
    let mut stack_buf = [0; limits::PATH_MAX];
    if alloc {
        buf = stack_buf.as_mut_ptr();
        size = stack_buf.len();
    }

    let ret = platform::pal::getcwd(buf, size);
    if ret.is_null() {
        return ptr::null_mut();
    }

    if alloc {
        let len = stack_buf
            .iter()
            .position(|b| *b == 0)
            .expect("no nul-byte in getcwd string")
            + 1;
        let heap_buf = unsafe { platform::alloc(len) as *mut ::c_char };
        for i in 0..len {
            unsafe {
                *heap_buf.add(i) = stack_buf[i];
            }
        }
        heap_buf
    } else {
        ret
    }
}

// #[no_mangle]
pub extern "C" fn getdtablesize() -> ::c_int {
    unimplemented!();
}

// #[no_mangle]
// pub extern "C" fn getegid() -> ::gid_t {
//     platform::pal::getegid()
// }

// #[no_mangle]
// pub extern "C" fn geteuid() -> ::uid_t {
//     platform::pal::geteuid()
// }

// #[no_mangle]
// pub extern "C" fn getgid() -> ::gid_t {
//     platform::pal::getgid()
// }

// #[no_mangle]
// pub extern "C" fn getgroups(gidsetsize: ::c_int, grouplist: *mut ::gid_t) -> ::c_int {
//     unimplemented!();
// }

// #[no_mangle]
// pub extern "C" fn gethostid() -> ::c_long {
//     unimplemented!();
// }

#[no_mangle]
pub unsafe extern "C" fn gethostname(mut name: *mut ::c_char, mut len: ::size_t) -> ::c_int {
    let mut uts = mem::MaybeUninit::<::utsname>::uninit();
    let err = platform::pal::uname(uts.as_mut_ptr());
    if err < 0 {
        mem::forget(uts);
        return err;
    }
    for c in uts.assume_init().nodename.iter() {
        if len == 0 {
            break;
        }
        len -= 1;

        *name = *c;

        if *name == 0 {
            // We do want to copy the zero also, so we check this after the copying.
            break;
        }

        name = name.offset(1);
    }
    0
}

#[no_mangle]
pub unsafe extern "C" fn getlogin() -> *mut ::c_char {
    static mut LOGIN: [::c_char; 256] = [0; 256];
    if getlogin_r(LOGIN.as_mut_ptr(), LOGIN.len()) == 0 {
        LOGIN.as_mut_ptr()
    } else {
        ptr::null_mut()
    }
}

#[no_mangle]
pub extern "C" fn getlogin_r(_name: *mut ::c_char, _namesize: ::size_t) -> ::c_int {
    //TODO: Determine correct getlogin result on Redox
    unsafe { platform::errno = errno::ENOENT };
    -1
}

#[no_mangle]
pub extern "C" fn getpagesize() -> ::c_int {
    match ::c_int::try_from(sysconf(_SC_PAGESIZE)) {
        Ok(page_size) => page_size,
        Err(_) => {
            /* Behavior not specified by POSIX for this case. The -1
             * value mimics sysconf()'s behavior, though.
             *
             * As specified for the limits.h header, the minimum
             * acceptable value for {PAGESIZE} is 1. The -1 value thus
             * cannot be mistaken for an acceptable value.
             *
             * POSIX does not specify any possible errors for this
             * function, hence no errno setting. */
            -1
        }
    }
}

// #[no_mangle]
// pub extern "C" fn getpass(prompt: *const ::c_char) -> *mut ::c_char {
//     unimplemented!();
// }

// #[no_mangle]
// pub extern "C" fn getpgid(pid: ::pid_t) -> ::pid_t {
//     platform::pal::getpgid(pid)
// }

#[no_mangle]
pub extern "C" fn getpgrp() -> ::pid_t {
    platform::pal::getpgid(platform::pal::getpid())
}

// #[no_mangle]
// pub extern "C" fn getpid() -> ::pid_t {
//     platform::pal::getpid()
// }

// #[no_mangle]
// pub extern "C" fn getppid() -> ::pid_t {
//     platform::pal::getppid()
// }

// #[no_mangle]
// pub extern "C" fn getsid(pid: ::pid_t) -> ::pid_t {
//     platform::pal::getsid(pid)
// }

// #[no_mangle]
// pub extern "C" fn getuid() -> ::uid_t {
//     platform::pal::getuid()
// }

#[no_mangle]
pub extern "C" fn getwd(path_name: *mut ::c_char) -> *mut ::c_char {
    getcwd(path_name, limits::PATH_MAX)
}

#[no_mangle]
pub extern "C" fn isatty(fd: ::c_int) -> ::c_int {
    let mut t = termios::termios::default();
    if unsafe { termios::tcgetattr(fd, &mut t as *mut termios::termios) == 0 } {
        1
    } else {
        0
    }
}

// #[no_mangle]
// pub unsafe extern "C" fn lchown(path: *const ::c_char, owner: ::uid_t, group: ::gid_t) -> ::c_int {
//     platform::pal::lchown(path, owner, group)
// }

// #[no_mangle]
// pub unsafe extern "C" fn link(path1: *const ::c_char, path2: *const ::c_char) -> ::c_int {
//     platform::pal::link(path1, path2)
// }

// #[no_mangle]
// pub extern "C" fn lockf(fildes: ::c_int, function: ::c_int, size: ::off_t) -> ::c_int {
//     unimplemented!();
// }

// #[no_mangle]
// pub extern "C" fn lseek(fildes: ::c_int, offset: ::off_t, whence: ::c_int) -> ::off_t {
//     platform::pal::lseek(fildes, offset, whence)
// }

// #[no_mangle]
pub extern "C" fn nice(_incr: ::c_int) -> ::c_int {
    unimplemented!();
}

// #[no_mangle]
pub extern "C" fn pause() -> ::c_int {
    unimplemented!();
}

//#[no_mangle]
pub unsafe extern "C" fn pipe(fildes: *mut ::c_int) -> ::c_int {
    ::pipe2(fildes, 0)
}

// #[no_mangle]
// pub unsafe extern "C" fn pipe2(fildes: *mut ::c_int, flags: ::c_int) -> ::c_int {
//     platform::pal::pipe2(fildes, flags)
// }

#[no_mangle]
pub unsafe extern "C" fn pread(
    fildes: ::c_int,
    buf: *mut ::c_void,
    nbyte: ::size_t,
    offset: ::off_t,
) -> ::ssize_t {
    //TODO: better pread using system calls

    let previous = ::lseek(fildes, offset, SEEK_SET);
    if previous == -1 {
        return -1;
    }

    let res = ::read(fildes, buf, nbyte);
    if res < 0 {
        return res;
    }

    if ::lseek(fildes, previous, SEEK_SET) == -1 {
        return -1;
    }

    res
}

#[no_mangle]
pub extern "C" fn pthread_atfork(
    prepare: Option<extern "C" fn()>,
    parent: Option<extern "C" fn()>,
    child: Option<extern "C" fn()>,
) -> ::c_int {
    let fork_hooks = unsafe { init_fork_hooks() };
    if let Some(prepare) = prepare {
        fork_hooks[0].push_back(prepare);
    }
    if let Some(parent) = parent {
        fork_hooks[1].push_back(parent);
    }
    if let Some(child) = child {
        fork_hooks[2].push_back(child);
    }
    0
}

#[no_mangle]
pub unsafe extern "C" fn pwrite(
    fildes: ::c_int,
    buf: *const ::c_void,
    nbyte: ::size_t,
    offset: ::off_t,
) -> ::ssize_t {
    //TODO: better pwrite using system calls

    let previous = ::lseek(fildes, offset, SEEK_SET);
    if previous == -1 {
        return -1;
    }

    let res = ::write(fildes, buf, nbyte);
    if res < 0 {
        return res;
    }

    if ::lseek(fildes, previous, SEEK_SET) == -1 {
        return -1;
    }

    res
}

// #[no_mangle]
// pub extern "C" fn read(fildes: ::c_int, buf: *const ::c_void, nbyte: ::size_t) -> ::ssize_t {
//     trace_expr!(
//         platform::pal::read(fildes, buf as *mut ::c_void,nbyte),
//         "read({}, {:p}, {})",
//         fildes,
//         buf,
//         nbyte
//     )
// }

// #[no_mangle]
// pub unsafe extern "C" fn readlink(
//     path: *const ::c_char,
//     buf: *mut ::c_char,
//     bufsize: ::size_t,
// ) -> ::ssize_t {
//     platform::pal::readlink(path, buf,bufsize)
// }

// #[no_mangle]
// pub unsafe extern "C" fn rmdir(path: *const ::c_char) -> ::c_int {
//     platform::pal::rmdir(path)
// }

#[no_mangle]
pub extern "C" fn setgid(gid: ::gid_t) -> ::c_int {
    platform::pal::setregid(gid, gid)
}

// #[no_mangle]
// pub extern "C" fn setpgid(pid: ::pid_t, pgid: ::pid_t) -> ::c_int {
//     platform::pal::setpgid(pid, pgid)
// }

#[no_mangle]
pub unsafe extern "C" fn setpgrp() -> ::pid_t {
    ::setpgid(0, 0)
}

// #[no_mangle]
// pub extern "C" fn setregid(rgid: ::gid_t, egid: ::gid_t) -> ::c_int {
//     platform::pal::setregid(rgid, egid)
// }

// #[no_mangle]
// pub extern "C" fn setreuid(ruid: ::uid_t, euid: ::uid_t) -> ::c_int {
//     platform::pal::setreuid(ruid, euid)
// }

// #[no_mangle]
// pub extern "C" fn setsid() -> ::pid_t {
//     unimplemented!();
// }

// #[no_mangle]
// pub extern "C" fn setuid(uid: ::uid_t) -> ::c_int {
//     platform::pal::setreuid(uid, uid)
// }

#[no_mangle]
pub extern "C" fn sleep(seconds: ::c_uint) -> ::c_uint {
    let rqtp = ::timespec {
        tv_sec: seconds as ::time_t,
        tv_nsec: 0,
    };
    let rmtp = ptr::null_mut();
    platform::pal::nanosleep(&rqtp, rmtp);
    0
}

#[no_mangle]
pub extern "C" fn swab(src: *const ::c_void, dest: *mut ::c_void, nbytes: ::ssize_t) {
    if nbytes <= 0 {
        return;
    }
    let number_of_swaps = nbytes / 2;
    let mut offset = 0;
    for _i in 0..number_of_swaps {
        unsafe {
            src.offset(offset).copy_to(dest.offset(offset + 1), 1);
            src.offset(offset + 1).copy_to(dest.offset(offset), 1);
        }
        offset += 2;
    }
}

// #[no_mangle]
// pub unsafe extern "C" fn symlink(path1: *const ::c_char, path2: *const ::c_char) -> ::c_int {
//     platform::pal::symlink(path1, path2)
// }

// #[no_mangle]
pub extern "C" fn sync() {
    unimplemented!();
}

#[no_mangle]
pub extern "C" fn tcgetpgrp(fd: ::c_int) -> ::pid_t {
    let mut pgrp = 0;
    if unsafe { ioctl(fd, TIOCGPGRP, &mut pgrp as *mut ::pid_t as *mut ::c_void) } < 0 {
        return -1;
    }
    pgrp
}

#[no_mangle]
pub extern "C" fn tcsetpgrp(fd: ::c_int, pgrp: ::pid_t) -> ::c_int {
    if unsafe { ioctl(fd, TIOCSPGRP, &pgrp as *const ::pid_t as *mut ::c_void) } < 0 {
        return -1;
    }
    pgrp
}

#[no_mangle]
pub extern "C" fn truncate(path: *const ::c_char, length: ::off_t) -> ::c_int {
    let fd = platform::pal::open(path, ::O_WRONLY, 0);
    if fd < 0 {
        return -1;
    }

    let res = unsafe { ::ftruncate(fd, length) };

    platform::pal::close(fd);

    res
}

#[no_mangle]
pub unsafe extern "C" fn ttyname(fildes: ::c_int) -> *mut ::c_char {
    static mut TTYNAME: [::c_char; 4096] = [0; 4096];
    if ttyname_r(fildes, TTYNAME.as_mut_ptr(), TTYNAME.len()) == 0 {
        TTYNAME.as_mut_ptr()
    } else {
        ptr::null_mut()
    }
}

#[no_mangle]
pub extern "C" fn ttyname_r(fildes: ::c_int, name: *mut ::c_char, namesize: ::size_t) -> ::c_int {
    let name = unsafe { slice::from_raw_parts_mut(name as *mut u8, namesize) };
    if name.is_empty() {
        return errno::ERANGE;
    }

    let len = platform::pal::fpath(fildes, (&mut name[..namesize - 1]).as_ptr() as *const i8);
    if len < 0 {
        return unsafe { -platform::errno };
    }
    name[len as usize] = 0;

    0
}

#[no_mangle]
pub extern "C" fn ualarm(usecs: ::useconds_t, interval: ::useconds_t) -> ::useconds_t {
    let mut timer = sys_time::itimerval {
        it_value: sys_time::timeval {
            tv_sec: 0,
            tv_usec: usecs as ::suseconds_t,
        },
        it_interval: sys_time::timeval {
            tv_sec: 0,
            tv_usec: interval as ::suseconds_t,
        },
    };
    let errno_backup = unsafe { platform::errno };
    let ret = if ::setitimer(sys_time::ITIMER_REAL, &timer, &mut timer) < 0 {
        0
    } else {
        timer.it_value.tv_sec as ::useconds_t * 1_000_000 + timer.it_value.tv_usec as ::useconds_t
    };
    unsafe {
        platform::errno = errno_backup;
    }

    ret
}

// #[no_mangle]
// pub unsafe extern "C" fn unlink(path: *const ::c_char) -> ::c_int {
//     platform::pal::unlink(path)
// }

#[no_mangle]
pub extern "C" fn usleep(useconds: ::useconds_t) -> ::c_int {
    let rqtp = ::timespec {
        tv_sec: (useconds / 1_000_000) as ::time_t,
        tv_nsec: ((useconds % 1_000_000) * 1000) as ::c_long,
    };
    let rmtp = ptr::null_mut();
    platform::pal::nanosleep(&rqtp, rmtp)
}

// #[no_mangle]
// pub extern "C" fn vfork() -> ::pid_t {
//     unimplemented!();
// }

// #[no_mangle]
// pub extern "C" fn write(fildes: ::c_int, buf: *const ::c_void, nbyte: ::size_t) -> ::ssize_t {
//     platform::pal::write(fildes, buf, nbyte)
// }
