#include <j6/memutils.h>
#include <util/no_construct.h>

#include "kassert.h"
#include "capabilities.h"
#include "cpu.h"
#include "objects/process.h"
#include "objects/thread.h"
#include "objects/vm_area.h"
#include "scheduler.h"


// This object is initialized _before_ global constructors are called,
// so we don't want it to have a global constructor at all, lest it
// overwrite the previous initialization.
static util::no_construct<obj::process> __g_kernel_process_storage;
obj::process &g_kernel_process = __g_kernel_process_storage.value;


namespace obj {

process::process(const char *name) :
    kobject {kobject::type::process},
    m_state {state::running}
{
    if constexpr(__use_process_names) {
        memset(m_name, 0, sizeof(m_name));
        if (name) {
            static constexpr size_t charlen = sizeof(m_name) - 1;
            for (size_t i = 0; i < charlen && name[i]; ++i)
                m_name[i] = name[i];
        }
    }
}

// The "kernel process"-only constructor
process::process(page_table *kpml4) :
    kobject {kobject::type::process},
    m_space {kpml4},
    m_state {state::running}
{
    if constexpr(__use_process_names) {
        static constexpr char kernel[] = "KERNEL";
        memcpy(m_name, kernel, sizeof(kernel));
        memset(m_name + sizeof(kernel), 0, sizeof(m_name) - sizeof(kernel));
    }
}

process::~process()
{
}

process & process::current() { return *current_cpu().process; }
process & process::kernel_process() { return g_kernel_process; }

process *
process::create_kernel_process(page_table *pml4)
{
    return new (&g_kernel_process) process {pml4};
}

void
process::exit(int64_t code)
{
    if (m_state == state::exited)
        return;

    m_state = state::exited;
    m_return_code = code;

    thread &current = thread::current();

    util::scoped_lock lock {m_threads_lock};
    for (auto *thread : m_threads) {
        if (thread != &current) {
            thread->exit();
        }
    }

    lock.release();
    if (&current.parent() == this)
        current.exit();
}

thread *
process::create_thread(uintptr_t rsp3, uint8_t priority)
{
    util::scoped_lock lock {m_threads_lock};

    if (m_state == state::exited)
        return nullptr;

    if (priority == default_priority)
        priority = scheduler::default_priority;

    thread *th = new thread(*this, priority);
    kassert(th, "Failed to create thread!");
    th->handle_retain();

    if (rsp3)
        th->tcb()->rsp3 = rsp3;

    if (this != &g_kernel_process)
        th->init_xsave_area();

    m_threads.append(th);
    scheduler::get().add_thread(th->tcb());
    return th;
}

void
process::thread_exited(thread *th)
{
    kassert(&th->m_parent == this, "Process got thread_exited for non-child!");

    if (m_state != state::exited) {
        // if we're already going away, just release
        // the thread's handle and skip all this
        util::scoped_lock lock {m_threads_lock};
        m_threads.remove_swap(th);

        // TODO: delete the thread's stack VMA
        if (m_threads.empty()) {
            lock.release();
            exit(-1);
        }
    }

    th->handle_release();
}

void
process::add_handle(j6_handle_t handle)
{
    // Passing the invalid handle is fine, just don't add anything
    if (handle == j6_handle_invalid)
        return;

    capability *c = g_cap_table.retain(handle);
    kassert(c, "Trying to add a non-existant handle to a process!");

    if (c) {
        util::scoped_lock lock {m_handles_lock};
        m_handles.add(handle);
    }
}

bool
process::remove_handle(j6_handle_t handle)
{
    util::scoped_lock lock {m_handles_lock};
    bool removed = m_handles.remove(handle);
    lock.release();

    if (removed)
        g_cap_table.release(handle);

    return removed;
}

bool
process::has_handle(j6_handle_t handle)
{
    util::scoped_lock lock {m_handles_lock};
    return m_handles.contains(handle);
}

size_t
process::list_handles(j6_handle_descriptor *handles, size_t len)
{
    util::scoped_lock lock {m_handles_lock};

    size_t count = 0;
    for (j6_handle_t handle : m_handles) {

        capability *cap = g_cap_table.find_without_retain(handle);
        kassert(cap, "Found process handle that wasn't in the cap table");
        if (!cap) continue;

        j6_handle_descriptor &desc = handles[count];
        desc.handle = handle;
        desc.caps = cap->caps;
        desc.type = static_cast<j6_object_type>(cap->type);

        if (++count == len) break;
    }

    return m_handles.count();
}

} // namespace obj
