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

Result<u64> sys_getpid(Registers*, SyscallArgs)
{
    auto* current = Scheduler::current();
    TRY(check_pledge(current, Promise::p_stdio));
    return current->id;
}

Result<u64> sys_getppid(Registers*, SyscallArgs)
{
    auto* current = Scheduler::current();
    TRY(check_pledge(current, Promise::p_stdio));
    auto* parent = current->parent;
    return parent ? parent->id : 0;
}

Result<u64> sys_getuid(Registers*, SyscallArgs)
{
    auto* current = Scheduler::current();
    TRY(check_pledge(current, Promise::p_stdio));
    return current->auth.uid;
}

Result<u64> sys_geteuid(Registers*, SyscallArgs)
{
    auto* current = Scheduler::current();
    TRY(check_pledge(current, Promise::p_stdio));
    return current->auth.euid;
}

Result<u64> sys_getgid(Registers*, SyscallArgs)
{
    auto* current = Scheduler::current();
    TRY(check_pledge(current, Promise::p_stdio));
    return current->auth.gid;
}

Result<u64> sys_getegid(Registers*, SyscallArgs)
{
    auto* current = Scheduler::current();
    TRY(check_pledge(current, Promise::p_stdio));
    return current->auth.egid;
}

Result<u64> sys_setuid(Registers*, SyscallArgs args)
{
    u32 uid = (u32)args[0];

    auto* current = Scheduler::current();
    TRY(check_pledge(current, Promise::p_id));
    Credentials& auth = current->auth;

    if (auth.euid == 0)
    {
        auth.uid = auth.euid = auth.suid = uid;
        return 0;
    }

    if (uid != auth.uid && uid != auth.suid) return err(EPERM);
    auth.euid = uid;

    return 0;
}

Result<u64> sys_seteuid(Registers*, SyscallArgs args)
{
    u32 uid = (u32)args[0];

    auto* current = Scheduler::current();
    TRY(check_pledge(current, Promise::p_id));
    Credentials& auth = current->auth;

    if (auth.euid != 0 && uid != auth.uid && uid != auth.suid) return err(EPERM);
    auth.euid = uid;

    return 0;
}

Result<u64> sys_setgid(Registers*, SyscallArgs args)
{
    u32 gid = (u32)args[0];

    auto* current = Scheduler::current();
    TRY(check_pledge(current, Promise::p_id));
    Credentials& auth = current->auth;

    if (auth.euid == 0)
    {
        auth.gid = auth.egid = auth.sgid = gid;
        return 0;
    }

    if (gid != auth.gid && gid != auth.sgid) return err(EPERM);
    auth.egid = gid;

    return 0;
}

Result<u64> sys_setegid(Registers*, SyscallArgs args)
{
    u32 gid = (u32)args[0];

    auto* current = Scheduler::current();
    TRY(check_pledge(current, Promise::p_id));
    Credentials& auth = current->auth;

    if (auth.euid != 0 && gid != auth.gid && gid != auth.sgid) return err(EPERM);
    auth.egid = gid;

    return 0;
}

Result<u64> sys_setpgid(Registers*, SyscallArgs args)
{
    pid_t pid = (pid_t)args[0];
    pid_t pgid = (pid_t)args[1];

    auto* current = Scheduler::current();
    TRY(check_pledge(current, Promise::p_proc));
    if (pid == 0) pid = current->id;
    if (pgid == 0) pgid = current->id;

    if (pgid < 0) return err(EINVAL);

    auto* thread = TRY(Result<Thread*>::from_option(Scheduler::find_by_pid(pid), ESRCH));
    if (thread != current && thread->parent != current) return err(ESRCH);

    // FIXME: Weird session stuff, we don't have that currently.

    if (thread->has_called_exec) return err(EPERM);

    if (pgid != current->id)
    {
        bool pgid_exists = false;
        Scheduler::for_each_in_process_group(pgid, [&pgid_exists](Thread*) {
            pgid_exists = true;
            return false;
        });
        if (!pgid_exists) return err(EPERM);
    }

    thread->pgid = (u64)pgid;

    return 0;
}

Result<u64> sys_getpgid(Registers*, SyscallArgs args)
{
    pid_t pid = (pid_t)args[0];

    auto* current = Scheduler::current();
    TRY(check_pledge(current, Promise::p_stdio));
    if (pid == 0) pid = current->id;

    if (pid < 0) return err(EINVAL);

    auto* thread = TRY(Result<Thread*>::from_option(Scheduler::find_by_pid(pid), ESRCH));

    return (u64)thread->pgid;
}

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

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

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

    if (current->auth.euid != 0 && current->auth.euid != inode->metadata().uid) return err(EPERM);

    auto metadata = inode->metadata();
    metadata.mode = mode;
    TRY(inode->set_metadata(metadata));

    return 0;
}

Result<u64> sys_fchownat(Registers*, SyscallArgs args)
{
    int dirfd = (int)args[0];
    auto path = TRY(MemoryManager::strdup_from_user(args[1]));
    uid_t uid = (u32)args[2];
    gid_t gid = (u32)args[3];
    int flags = (int)args[4];

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

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

    if (current->auth.euid != 0) return err(EPERM);

    auto metadata = inode->metadata();
    if (uid != (uid_t)-1) metadata.uid = uid;
    if (gid != (gid_t)-1) metadata.gid = gid;
    TRY(inode->set_metadata(metadata));

    return 0;
}