#include <luna/StringBuilder.h>
#include <luna/Vector.h>

#include <bits/errno-return.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>

extern "C" long arch_invoke_syscall(long, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);

extern char** environ;

extern "C"
{
    pid_t fork(void)
    {
        long rc = syscall(SYS_fork);
        __errno_return(rc, int);
    }

    pid_t getpid(void)
    {
        return (pid_t)syscall(SYS_getpid);
    }

    pid_t getppid(void)
    {
        return (pid_t)syscall(SYS_getppid);
    }

    uid_t getuid(void)
    {
        return (uid_t)syscall(SYS_getuid);
    }

    uid_t geteuid(void)
    {
        return (uid_t)syscall(SYS_geteuid);
    }

    gid_t getgid(void)
    {
        return (gid_t)syscall(SYS_getgid);
    }

    gid_t getegid(void)
    {
        return (gid_t)syscall(SYS_getegid);
    }

    int setuid(uid_t uid)
    {
        long rc = syscall(SYS_setuid, uid);
        __errno_return(rc, int);
    }

    int seteuid(uid_t uid)
    {
        long rc = syscall(SYS_seteuid, uid);
        __errno_return(rc, int);
    }

    int setgid(gid_t gid)
    {
        long rc = syscall(SYS_setgid, gid);
        __errno_return(rc, int);
    }

    int setegid(gid_t gid)
    {
        long rc = syscall(SYS_setegid, gid);
        __errno_return(rc, int);
    }

    int chmod(const char* path, mode_t mode)
    {
        long rc = syscall(SYS_chmod, path, mode);
        __errno_return(rc, int);
    }

    int chown(const char* path, uid_t uid, gid_t gid)
    {
        long rc = syscall(SYS_chown, path, uid, gid);
        __errno_return(rc, int);
    }

    int execv(const char* path, char* const* argv)
    {
        return execve(path, argv, environ);
    }

    int execve(const char* path, char* const* argv, char* const* envp)
    {
        long rc = syscall(SYS_execve, path, argv, envp);
        __errno_return(rc, int);
    }

    int execvpe(const char* name, char* const* argv, char* const* envp)
    {
        if (strchr(name, '/')) return execve(name, argv, envp);

        char* path = getenv("PATH");
        if (!path) path = const_cast<char*>("/bin:/sbin");

        Vector<String> paths;
        bool ok = StringView { path }.split(":").try_move_value_or_error(paths, errno);
        if (!ok) return -1;

        for (const auto& dir : paths)
        {
            // FIXME: Check for errors.
            StringBuilder sb;
            sb.add(dir);
            sb.add('/');
            sb.add(StringView { name });

            String file;
            ok = sb.string().try_move_value_or_error(file, errno);
            if (!ok) return -1;

            int err = errno;
            execve(file.chars(), argv, envp);

            if (errno != ENOENT && errno != EACCES && errno != ENOEXEC) return -1;

            if (errno == ENOEXEC)
            {
                Vector<char*> shell_argv;
                shell_argv.try_append(const_cast<char*>("sh"));
                char* const* arg = argv;
                do {
                    shell_argv.try_append(*arg);
                } while (*(arg++));

                execve("/bin/sh", shell_argv.data(), envp);
                errno = ENOEXEC;
                return -1;
            }

            if (err == EACCES) errno = err;
        }

        return -1;
    }

    int execvp(const char* name, char* const* argv)
    {
        return execvpe(name, argv, environ);
    }

    int execl(const char* path, const char* arg, ...)
    {
        va_list ap;
        va_start(ap, arg);

        Vector<char*> args;
        if (args.try_append(const_cast<char*>(arg)).has_error()) return -1;

        while (true)
        {
            char* str = va_arg(ap, char*);
            if (args.try_append(str).has_error()) return -1;
            if (!str) break;
        }

        va_end(ap);

        return execv(path, args.data());
    }

    long syscall(long num, ...)
    {
        va_list ap;
        va_start(ap, num);

        uintptr_t arg0 = va_arg(ap, uintptr_t);
        uintptr_t arg1 = va_arg(ap, uintptr_t);
        uintptr_t arg2 = va_arg(ap, uintptr_t);
        uintptr_t arg3 = va_arg(ap, uintptr_t);
        uintptr_t arg4 = va_arg(ap, uintptr_t);

        long rc = arch_invoke_syscall(num, arg0, arg1, arg2, arg3, arg4);

        va_end(ap);

        return rc;
    }

    int usleep(useconds_t us)
    {
        long rc = syscall(SYS_usleep, us);
        __errno_return(rc, int);
    }

    unsigned long sleep(unsigned long seconds)
    {
        syscall(SYS_usleep, seconds * 1000000);
        return 0;
    }

    int close(int fd)
    {
        long rc = syscall(SYS_close, fd);
        __errno_return(rc, int);
    }

    ssize_t read(int fd, void* buf, size_t size)
    {
        long rc = syscall(SYS_read, fd, buf, size);
        __errno_return(rc, ssize_t);
    }

    ssize_t write(int fd, const void* buf, size_t size)
    {
        long rc = syscall(SYS_write, fd, buf, size);
        __errno_return(rc, ssize_t);
    }

    off_t lseek(int fd, off_t offset, int whence)
    {
        long rc = syscall(SYS_lseek, fd, offset, whence);
        __errno_return(rc, off_t);
    }

    int dup(int fd)
    {
        return fcntl(fd, F_DUPFD, 0);
    }

    int chdir(const char* path)
    {
        long rc = syscall(SYS_chdir, path);
        __errno_return(rc, int);
    }

    static ssize_t __getcwd_wrapper(char* buf, size_t size)
    {
        long rc = syscall(SYS_getcwd, buf, size);
        __errno_return(rc, ssize_t);
    }

    char* getcwd(char* buf, size_t size)
    {
        if (buf)
        {
            ssize_t rc = __getcwd_wrapper(buf, size);
            if (rc < 0) return nullptr;

            if (rc > (ssize_t)size)
            {
                errno = ERANGE;
                return nullptr;
            }

            return buf;
        }
        else if (size)
        {
            buf = (char*)malloc(size);
            if (!buf) return nullptr;

            ssize_t rc = __getcwd_wrapper(buf, size);
            if (rc < 0)
            {
                free(buf);
                return nullptr;
            }

            if (rc > (ssize_t)size)
            {
                free(buf);
                errno = ERANGE;
                return nullptr;
            }

            return buf;
        }
        else
        {
            buf = (char*)malloc(1024);
            if (!buf) return nullptr;

            ssize_t rc = __getcwd_wrapper(buf, 1024);
            if (rc < 0)
            {
                free(buf);
                return nullptr;
            }

            if (rc > 1024)
            {
                free(buf);

                return getcwd(NULL, rc);
            }

            return buf;
        }
    }

    int unlink(const char* path)
    {
        long rc = syscall(SYS_unlinkat, AT_FDCWD, path, 0);
        __errno_return(rc, int);
    }

    int rmdir(const char* path)
    {
        long rc = syscall(SYS_unlinkat, AT_FDCWD, path, AT_REMOVEDIR);
        __errno_return(rc, int);
    }
}