#include "Pledge.h"
#include "memory/MemoryManager.h"
#include "sys/Syscall.h"
#include "thread/Scheduler.h"
#include <bits/access.h>
#include <bits/atfile.h>
#include <bits/modes.h>
#include <bits/struct_stat.h>

static mode_t make_mode(mode_t mode, VFS::InodeType type)
{
    mode_t result = mode;

    switch (type)
    {
    case VFS::InodeType::RegularFile: result |= S_IFREG; break;
    case VFS::InodeType::Directory: result |= S_IFDIR; break;
    case VFS::InodeType::CharacterDevice: result |= S_IFCHR; break;
    case VFS::InodeType::BlockDevice: result |= S_IFBLK; break;
    case VFS::InodeType::Symlink: result |= S_IFLNK; break;
    case VFS::InodeType::FIFO: result |= S_IFIFO; break;
    case VFS::InodeType::Socket: result |= S_IFSOCK; break;
    default: break;
    }

    return result;
}

Result<u64> sys_fstatat(Registers*, SyscallArgs args)
{
    int dirfd = (int)args[0];
    auto path = TRY(MemoryManager::strdup_from_user(args[1]));
    stat* st = (stat*)args[2];
    int flags = (int)args[3];

    Thread* current = Scheduler::current();
    TRY(check_pledge(current, Promise::p_rpath));

    auto inode = TRY(current->resolve_atfile(dirfd, path, flags & AT_EMPTY_PATH, !(flags & AT_SYMLINK_NOFOLLOW)));

    stat kstat;

    const auto& metadata = inode->metadata();

    kstat.st_ino = metadata.inum;
    kstat.st_mode = make_mode(metadata.mode, inode->type());
    kstat.st_nlink = metadata.nlinks;
    kstat.st_uid = metadata.uid;
    kstat.st_gid = metadata.gid;
    kstat.st_size = metadata.size;
    kstat.st_dev = inode->fs() ? inode->fs()->host_device_id() : 0;
    kstat.st_rdev = metadata.devid;
    kstat.st_atim = metadata.atime;
    kstat.st_mtim = metadata.mtime;
    kstat.st_ctim = metadata.ctime;

    if (!MemoryManager::copy_to_user_typed(st, &kstat)) return err(EFAULT);

    return 0;
}

Result<u64> sys_faccessat(Registers*, SyscallArgs args)
{
    int dirfd = (int)args[0];
    auto path = TRY(MemoryManager::strdup_from_user(args[1]));
    int amode = (int)args[2];
    int flags = (int)args[3];

    Credentials creds;

    auto* current = Scheduler::current();
    TRY(check_pledge(current, Promise::p_rpath));

    if (flags & AT_EACCESS) creds = current->auth;
    else
    {
        auto auth = current->auth;
        creds.euid = auth.uid;
        creds.egid = auth.gid;
    }

    auto inode = TRY(current->resolve_atfile(dirfd, path, false, true));

    if ((amode & R_OK) && !VFS::can_read(inode, creds)) return err(EACCES);
    if ((amode & W_OK) && !VFS::can_write(inode, creds)) return err(EACCES);
    if ((amode & X_OK) && !VFS::can_execute(inode, creds)) return err(EACCES);

    // Either all checks succeeded, or amode == F_OK and the file exists, since resolve_atfile() would have failed
    // otherwise.
    return 0;
}