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

Result<u64> sys_waitpid(Registers* regs, SyscallArgs args)
{
    pid_t pid = (pid_t)args[0];
    int* status_ptr = (int*)args[1];
    int options = (int)args[2];

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

    Thread* thread;

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

        if (thread->parent && thread->parent != current) return err(ECHILD);

        if (options & WNOHANG) return err(EAGAIN);

    wait_for_child:
        if (thread->state != ThreadState::Exited) kernel_wait(pid);
        if (current->interrupted)
        {
            kdbgln("signal: waitpid interrupted by signal");
            if (current->will_ignore_pending_signal())
            {
                current->process_pending_signals(regs);
                goto wait_for_child;
            }
            return err(EINTR);
        }

        check(thread->state == ThreadState::Exited);
    }
    else if (pid == -1)
    {
        if (!Scheduler::has_children(current)) return err(ECHILD);

        auto child = Scheduler::find_exited_child(current);
        if (!child.has_value())
        {
            if (options & WNOHANG) return err(EAGAIN);

        wait_for_any_child:
            kernel_wait(pid);
            if (current->interrupted)
            {
                kdbgln("signal: waitpid interrupted by signal");
                if (current->will_ignore_pending_signal())
                {
                    current->process_pending_signals(regs);
                    goto wait_for_any_child;
                }
                return err(EINTR);
            }

            check(current->child_being_waited_for.value_or(-1) != -1);

            thread = TRY(Result<Thread*>::from_option(Scheduler::find_by_pid(*current->child_being_waited_for), ESRCH));
            check(thread->state == ThreadState::Exited);
        }
        else
            thread = child.value();
    }
    else // FIXME: Now that we have process groups, implement the cases where pid = 0 and pid < -1.
        return err(ENOTSUP);

    current->child_being_waited_for = {};

    int status = (int)thread->status;
    u64 id = thread->id;

    current->user_ticks_children += thread->user_ticks_self + thread->user_ticks_children;
    current->kernel_ticks_children += thread->kernel_ticks_self + thread->kernel_ticks_children;

    thread->state = ThreadState::Dying;
    Scheduler::signal_reap_thread();

    if (status_ptr)
        if (!MemoryManager::copy_to_user_typed(status_ptr, &status)) return err(EFAULT);

    return id;
}