#include <uacpi/namespace.h>
#include <uacpi/internal/namespace.h>
#include <uacpi/internal/types.h>
#include <uacpi/internal/stdlib.h>
#include <uacpi/internal/interpreter.h>
#include <uacpi/internal/opregion.h>
#include <uacpi/internal/log.h>
#include <uacpi/internal/utilities.h>
#include <uacpi/internal/mutex.h>
#include <uacpi/kernel_api.h>

#ifndef UACPI_BAREBONES_MODE

#define UACPI_REV_VALUE 2
#define UACPI_OS_VALUE "Microsoft Windows NT"

#define MAKE_PREDEFINED(c0, c1, c2, c3)          \
    {                                            \
        .name.text = { c0, c1, c2, c3 },         \
        .flags = UACPI_NAMESPACE_NODE_PREDEFINED \
    }

static uacpi_namespace_node
predefined_namespaces[UACPI_PREDEFINED_NAMESPACE_MAX + 1] = {
    [UACPI_PREDEFINED_NAMESPACE_ROOT] = MAKE_PREDEFINED('\\', 0, 0, 0),
    [UACPI_PREDEFINED_NAMESPACE_GPE] = MAKE_PREDEFINED('_', 'G', 'P', 'E'),
    [UACPI_PREDEFINED_NAMESPACE_PR] = MAKE_PREDEFINED('_', 'P', 'R', '_'),
    [UACPI_PREDEFINED_NAMESPACE_SB] = MAKE_PREDEFINED('_', 'S', 'B', '_'),
    [UACPI_PREDEFINED_NAMESPACE_SI] = MAKE_PREDEFINED('_', 'S', 'I', '_'),
    [UACPI_PREDEFINED_NAMESPACE_TZ] = MAKE_PREDEFINED('_', 'T', 'Z', '_'),
    [UACPI_PREDEFINED_NAMESPACE_GL] = MAKE_PREDEFINED('_', 'G', 'L', '_'),
    [UACPI_PREDEFINED_NAMESPACE_OS] = MAKE_PREDEFINED('_', 'O', 'S', '_'),
    [UACPI_PREDEFINED_NAMESPACE_OSI] = MAKE_PREDEFINED('_', 'O', 'S', 'I'),
    [UACPI_PREDEFINED_NAMESPACE_REV] = MAKE_PREDEFINED('_', 'R', 'E', 'V'),
};

static struct uacpi_rw_lock namespace_lock;

uacpi_status uacpi_namespace_read_lock(void)
{
    return uacpi_rw_lock_read(&namespace_lock);
}

uacpi_status uacpi_namespace_read_unlock(void)
{
    return uacpi_rw_unlock_read(&namespace_lock);
}

uacpi_status uacpi_namespace_write_lock(void)
{
    return uacpi_rw_lock_write(&namespace_lock);
}

uacpi_status uacpi_namespace_write_unlock(void)
{
    return uacpi_rw_unlock_write(&namespace_lock);
}

static uacpi_object *make_object_for_predefined(
    enum uacpi_predefined_namespace ns
)
{
    uacpi_object *obj;

    switch (ns) {
    case UACPI_PREDEFINED_NAMESPACE_ROOT:
        /*
         * The real root object is stored in the global context, whereas the \
         * node gets a placeholder uninitialized object instead. This is to
         * protect against CopyObject(JUNK, \), so that all of the opregion and
         * notify handlers are preserved if AML decides to do that.
         */
        g_uacpi_rt_ctx.root_object = uacpi_create_object(UACPI_OBJECT_DEVICE);
        if (uacpi_unlikely(g_uacpi_rt_ctx.root_object == UACPI_NULL))
            return UACPI_NULL;

        obj = uacpi_create_object(UACPI_OBJECT_UNINITIALIZED);
        break;

    case UACPI_PREDEFINED_NAMESPACE_OS:
        obj = uacpi_create_object(UACPI_OBJECT_STRING);
        if (uacpi_unlikely(obj == UACPI_NULL))
            return obj;

        obj->buffer->text = uacpi_kernel_alloc(sizeof(UACPI_OS_VALUE));
        if (uacpi_unlikely(obj->buffer->text == UACPI_NULL)) {
            uacpi_object_unref(obj);
            return UACPI_NULL;
        }

        obj->buffer->size = sizeof(UACPI_OS_VALUE);
        uacpi_memcpy(obj->buffer->text, UACPI_OS_VALUE, obj->buffer->size);
        break;

    case UACPI_PREDEFINED_NAMESPACE_REV:
        obj = uacpi_create_object(UACPI_OBJECT_INTEGER);
        if (uacpi_unlikely(obj == UACPI_NULL))
            return obj;

        obj->integer = UACPI_REV_VALUE;
        break;

    case UACPI_PREDEFINED_NAMESPACE_GL:
        obj = uacpi_create_object(UACPI_OBJECT_MUTEX);
        if (uacpi_likely(obj != UACPI_NULL)) {
            uacpi_shareable_ref(obj->mutex);
            g_uacpi_rt_ctx.global_lock_mutex = obj->mutex;
        }
        break;

    case UACPI_PREDEFINED_NAMESPACE_OSI:
        obj = uacpi_create_object(UACPI_OBJECT_METHOD);
        if (uacpi_unlikely(obj == UACPI_NULL))
            return obj;

        obj->method->native_call = UACPI_TRUE;
        obj->method->handler = uacpi_osi;
        obj->method->args = 1;
        break;

    default:
        obj = uacpi_create_object(UACPI_OBJECT_UNINITIALIZED);
        break;
    }

    return obj;
}

static void namespace_node_detach_object(uacpi_namespace_node *node)
{
    uacpi_object *object;

    object = uacpi_namespace_node_get_object(node);
    if (object != UACPI_NULL) {
        if (object->type == UACPI_OBJECT_OPERATION_REGION)
            uacpi_opregion_uninstall_handler(node);

        uacpi_object_unref(node->object);
        node->object = UACPI_NULL;
    }
}

static void free_namespace_node(uacpi_handle handle)
{
    uacpi_namespace_node *node = handle;

    if (uacpi_likely(!uacpi_namespace_node_is_predefined(node))) {
        uacpi_free(node, sizeof(*node));
        return;
    }

    node->flags = UACPI_NAMESPACE_NODE_PREDEFINED;
    node->object = UACPI_NULL;
    node->parent = UACPI_NULL;
    node->child = UACPI_NULL;
    node->next = UACPI_NULL;
}

uacpi_status uacpi_initialize_namespace(void)
{
    enum uacpi_predefined_namespace ns;
    uacpi_object *obj;
    uacpi_namespace_node *node;
    uacpi_status ret;

    ret = uacpi_rw_lock_init(&namespace_lock);
    if (uacpi_unlikely_error(ret))
        return ret;

    for (ns = 0; ns <= UACPI_PREDEFINED_NAMESPACE_MAX; ns++) {
        node = &predefined_namespaces[ns];
        uacpi_shareable_init(node);

        obj = make_object_for_predefined(ns);
        if (uacpi_unlikely(obj == UACPI_NULL))
            return UACPI_STATUS_OUT_OF_MEMORY;

        node->object = uacpi_create_internal_reference(
            UACPI_REFERENCE_KIND_NAMED, obj
        );
        if (uacpi_unlikely(node->object == UACPI_NULL)) {
            uacpi_object_unref(obj);
            return UACPI_STATUS_OUT_OF_MEMORY;
        }

        uacpi_object_unref(obj);
    }

    for (ns = UACPI_PREDEFINED_NAMESPACE_GPE;
         ns <= UACPI_PREDEFINED_NAMESPACE_MAX; ns++) {

        /*
         * Skip the installation of \_OSI if it was disabled by user.
         * We still create the object, but it's not attached to the namespace.
         */
        if (ns == UACPI_PREDEFINED_NAMESPACE_OSI &&
            uacpi_check_flag(UACPI_FLAG_NO_OSI))
            continue;

        uacpi_namespace_node_install(
            uacpi_namespace_root(), &predefined_namespaces[ns]
        );
    }

    return UACPI_STATUS_OK;
}

void uacpi_deinitialize_namespace(void)
{
    uacpi_status ret;
    uacpi_namespace_node *current, *next = UACPI_NULL;
    uacpi_u32 depth = 1;

    current = uacpi_namespace_root();

    ret = uacpi_namespace_write_lock();

    while (depth) {
        next = next == UACPI_NULL ? current->child : next->next;

        /*
         * The previous value of 'next' was the last child of this subtree,
         * we can now remove the entire scope of 'current->child'
         */
        if (next == UACPI_NULL) {
            depth--;

            // Wipe the subtree
            while (current->child != UACPI_NULL)
                uacpi_namespace_node_uninstall(current->child);

            // Reset the pointers back as if this iteration never happened
            next = current;
            current = current->parent;

            continue;
        }

        /*
         * We have more nodes to process, proceed to the next one, either the
         * child of the 'next' node, if one exists, or its peer
         */
        if (next->child) {
            depth++;
            current = next;
            next = UACPI_NULL;
        }

        // This node has no children, move on to its peer
    }

    namespace_node_detach_object(uacpi_namespace_root());
    free_namespace_node(uacpi_namespace_root());

    if (ret == UACPI_STATUS_OK)
        uacpi_namespace_write_unlock();

    uacpi_object_unref(g_uacpi_rt_ctx.root_object);
    g_uacpi_rt_ctx.root_object = UACPI_NULL;

    uacpi_mutex_unref(g_uacpi_rt_ctx.global_lock_mutex);
    g_uacpi_rt_ctx.global_lock_mutex = UACPI_NULL;

    uacpi_rw_lock_deinit(&namespace_lock);
}

uacpi_namespace_node *uacpi_namespace_root(void)
{
    return &predefined_namespaces[UACPI_PREDEFINED_NAMESPACE_ROOT];
}

uacpi_namespace_node *uacpi_namespace_get_predefined(
    enum uacpi_predefined_namespace ns
)
{
    if (uacpi_unlikely(ns > UACPI_PREDEFINED_NAMESPACE_MAX)) {
        uacpi_warn("requested invalid predefined namespace %d\n", ns);
        return UACPI_NULL;
    }

    return &predefined_namespaces[ns];
}

uacpi_namespace_node *uacpi_namespace_node_alloc(uacpi_object_name name)
{
    uacpi_namespace_node *ret;

    ret = uacpi_kernel_alloc_zeroed(sizeof(*ret));
    if (uacpi_unlikely(ret == UACPI_NULL))
        return ret;

    uacpi_shareable_init(ret);
    ret->name = name;
    return ret;
}

void uacpi_namespace_node_unref(uacpi_namespace_node *node)
{
    uacpi_shareable_unref_and_delete_if_last(node, free_namespace_node);
}

uacpi_status uacpi_namespace_node_install(
    uacpi_namespace_node *parent,
    uacpi_namespace_node *node
)
{
    if (parent == UACPI_NULL)
        parent = uacpi_namespace_root();

    if (uacpi_unlikely(uacpi_namespace_node_is_dangling(node))) {
        uacpi_warn("attempting to install a dangling namespace node %.4s\n",
                   node->name.text);
        return UACPI_STATUS_NAMESPACE_NODE_DANGLING;
    }

    if (parent->child == UACPI_NULL) {
        parent->child = node;
    } else {
        uacpi_namespace_node *prev = parent->child;

        while (prev->next != UACPI_NULL)
            prev = prev->next;

        prev->next = node;
    }

    node->parent = parent;
    return UACPI_STATUS_OK;
}

uacpi_bool uacpi_namespace_node_is_alias(uacpi_namespace_node *node)
{
    return node->flags & UACPI_NAMESPACE_NODE_FLAG_ALIAS;
}

uacpi_bool uacpi_namespace_node_is_dangling(uacpi_namespace_node *node)
{
    return node->flags & UACPI_NAMESPACE_NODE_FLAG_DANGLING;
}

uacpi_bool uacpi_namespace_node_is_temporary(uacpi_namespace_node *node)
{
    return node->flags & UACPI_NAMESPACE_NODE_FLAG_TEMPORARY;
}

uacpi_bool uacpi_namespace_node_is_predefined(uacpi_namespace_node *node)
{
    return node->flags & UACPI_NAMESPACE_NODE_PREDEFINED;
}

uacpi_status uacpi_namespace_node_uninstall(uacpi_namespace_node *node)
{
    uacpi_namespace_node *prev;

    if (uacpi_unlikely(uacpi_namespace_node_is_dangling(node))) {
        uacpi_warn("attempting to uninstall a dangling namespace node %.4s\n",
                   node->name.text);
        return UACPI_STATUS_INTERNAL_ERROR;
    }

    /*
     * The way to trigger this is as follows:
     *
     * Method (FOO) {
     *     // Temporary device, will be deleted upon returning from FOO
     *     Device (\BAR) {
     *     }
     *
     *     //
     *     // Load TBL where TBL is:
     *     //     Scope (\BAR) {
     *     //         Name (TEST, 123)
     *     //     }
     *     //
     *     Load(TBL)
     * }
     *
     * In the above example, TEST is a permanent node attached by bad AML to a
     * temporary node created inside the FOO method at \BAR. The cleanup code
     * will attempt to remove the \BAR device upon exit from FOO, but that is
     * no longer possible as there's now a permanent child attached to it.
     */
    if (uacpi_unlikely(node->child != UACPI_NULL)) {
        uacpi_warn(
            "refusing to uninstall node %.4s with a child (%.4s)\n",
            node->name.text, node->child->name.text
        );
        return UACPI_STATUS_DENIED;
    }

    /*
     * Even though namespace_node is reference-counted it still has an 'invalid'
     * state that is entered after it is uninstalled from the global namespace.
     *
     * Reference counting is only needed to combat dangling pointer issues
     * whereas bad AML might try to prolong a local object lifetime by
     * returning it from a method, or CopyObject it somewhere. In that case the
     * namespace node object itself is still alive, but no longer has a valid
     * object associated with it.
     *
     * Example:
     *     Method (BAD) {
     *         OperationRegion(REG, SystemMemory, 0xDEADBEEF, 4)
     *         Field (REG, AnyAcc, NoLock) {
     *             FILD, 8,
     *         }
     *
     *         Return (RefOf(FILD))
     *     }
     *
     *     // Local0 is now the sole owner of the 'FILD' object that under the
     *     // hood is still referencing the 'REG' operation region object from
     *     // the 'BAD' method.
     *     Local0 = DerefOf(BAD())
     *
     * This is done to prevent potential very deep recursion where an object
     * frees a namespace node that frees an attached object that frees a
     * namespace node as well as potential infinite cycles between a namespace
     * node and an object.
     */
    namespace_node_detach_object(node);

    prev = node->parent ? node->parent->child : UACPI_NULL;

    if (prev == node) {
        node->parent->child = node->next;
    } else {
        while (uacpi_likely(prev != UACPI_NULL) && prev->next != node)
            prev = prev->next;

        if (uacpi_unlikely(prev == UACPI_NULL)) {
            uacpi_warn(
                "trying to uninstall a node %.4s (%p) not linked to any peer\n",
                node->name.text, node
            );
            return UACPI_STATUS_INTERNAL_ERROR;
        }

        prev->next = node->next;
    }

    node->flags |= UACPI_NAMESPACE_NODE_FLAG_DANGLING;
    uacpi_namespace_node_unref(node);

    return UACPI_STATUS_OK;
}

uacpi_namespace_node *uacpi_namespace_node_find_sub_node(
    uacpi_namespace_node *parent,
    uacpi_object_name name
)
{
    uacpi_namespace_node *node;

    if (parent == UACPI_NULL)
        parent = uacpi_namespace_root();

    node = parent->child;

    while (node) {
        if (node->name.id == name.id)
            return node;

        node = node->next;
    }

    return UACPI_NULL;
}

static uacpi_object_name segment_to_name(
    const uacpi_char **string, uacpi_size *in_out_size
)
{
    uacpi_object_name out_name;
    const uacpi_char *cursor = *string;
    uacpi_size offset, bytes_left = *in_out_size;

    for (offset = 0; offset < 4; offset++) {
        if (bytes_left < 1 || *cursor == '.') {
            out_name.text[offset] = '_';
            continue;
        }

        out_name.text[offset] = *cursor++;
        bytes_left--;
    }

    *string = cursor;
    *in_out_size = bytes_left;
    return out_name;
}

uacpi_status uacpi_namespace_node_resolve(
    uacpi_namespace_node *parent, const uacpi_char *path,
    enum uacpi_should_lock should_lock,
    enum uacpi_may_search_above_parent may_search_above_parent,
    enum uacpi_permanent_only permanent_only,
    uacpi_namespace_node **out_node
)
{
    uacpi_namespace_node *cur_node = parent;
    uacpi_status ret = UACPI_STATUS_OK;
    const uacpi_char *cursor = path;
    uacpi_size bytes_left;
    uacpi_char prev_char = 0;
    uacpi_bool single_nameseg = UACPI_TRUE;

    if (cur_node == UACPI_NULL)
        cur_node = uacpi_namespace_root();

    bytes_left = uacpi_strlen(path);

    if (should_lock == UACPI_SHOULD_LOCK_YES) {
        ret = uacpi_namespace_read_lock();
        if (uacpi_unlikely_error(ret))
            return ret;
    }

    for (;;) {
        if (bytes_left == 0)
            goto out;

        switch (*cursor) {
        case '\\':
            single_nameseg = UACPI_FALSE;

            if (prev_char == '^') {
                ret = UACPI_STATUS_INVALID_ARGUMENT;
                goto out;
            }

            cur_node = uacpi_namespace_root();
            break;
        case '^':
            single_nameseg = UACPI_FALSE;

            // Tried to go behind root
            if (uacpi_unlikely(cur_node == uacpi_namespace_root())) {
                ret = UACPI_STATUS_INVALID_ARGUMENT;
                goto out;
            }

            cur_node = cur_node->parent;
            break;
        default:
            break;
        }

        prev_char = *cursor;

        switch (prev_char) {
        case '^':
        case '\\':
            cursor++;
            bytes_left--;
            break;
        default:
            break;
        }

        if (prev_char != '^')
            break;
    }

    while (bytes_left != 0) {
        uacpi_object_name nameseg;

        if (*cursor == '.') {
            cursor++;
            bytes_left--;
        }

        nameseg = segment_to_name(&cursor, &bytes_left);
        if (bytes_left != 0 && single_nameseg)
            single_nameseg = UACPI_FALSE;

        cur_node = uacpi_namespace_node_find_sub_node(cur_node, nameseg);
        if (cur_node == UACPI_NULL) {
            if (may_search_above_parent == UACPI_MAY_SEARCH_ABOVE_PARENT_NO ||
                !single_nameseg)
                goto out;

            parent = parent->parent;

            while (parent) {
                cur_node = uacpi_namespace_node_find_sub_node(parent, nameseg);
                if (cur_node != UACPI_NULL)
                    goto out;

                parent = parent->parent;
            }

            goto out;
        }
    }

out:
    if (uacpi_unlikely(ret == UACPI_STATUS_INVALID_ARGUMENT)) {
        uacpi_warn("invalid path '%s'\n", path);
        goto out_read_unlock;
    }

    if (cur_node == UACPI_NULL) {
        ret = UACPI_STATUS_NOT_FOUND;
        goto out_read_unlock;
    }

    if (uacpi_namespace_node_is_temporary(cur_node) &&
        permanent_only == UACPI_PERMANENT_ONLY_YES) {
        uacpi_warn("denying access to temporary namespace node '%.4s'\n",
                   cur_node->name.text);
        ret = UACPI_STATUS_DENIED;
        goto out_read_unlock;
    }

    if (out_node != UACPI_NULL)
        *out_node = cur_node;

out_read_unlock:
    if (should_lock == UACPI_SHOULD_LOCK_YES)
        uacpi_namespace_read_unlock();
    return ret;
}

uacpi_status uacpi_namespace_node_find(
    uacpi_namespace_node *parent, const uacpi_char *path,
    uacpi_namespace_node **out_node
)
{
    return uacpi_namespace_node_resolve(
        parent, path, UACPI_SHOULD_LOCK_YES, UACPI_MAY_SEARCH_ABOVE_PARENT_NO,
        UACPI_PERMANENT_ONLY_YES, out_node
    );
}

uacpi_status uacpi_namespace_node_resolve_from_aml_namepath(
    uacpi_namespace_node *scope,
    const uacpi_char *path,
    uacpi_namespace_node **out_node
)
{
    return uacpi_namespace_node_resolve(
        scope, path, UACPI_SHOULD_LOCK_YES, UACPI_MAY_SEARCH_ABOVE_PARENT_YES,
        UACPI_PERMANENT_ONLY_YES, out_node
    );
}

uacpi_object *uacpi_namespace_node_get_object(const uacpi_namespace_node *node)
{
    if (node == UACPI_NULL || node->object == UACPI_NULL)
        return UACPI_NULL;

    return uacpi_unwrap_internal_reference(node->object);
}

uacpi_object *uacpi_namespace_node_get_object_typed(
    const uacpi_namespace_node *node, uacpi_object_type_bits type_mask
)
{
    uacpi_object *obj;

    obj = uacpi_namespace_node_get_object(node);
    if (uacpi_unlikely(obj == UACPI_NULL))
        return obj;

    if (!uacpi_object_is_one_of(obj, type_mask))
        return UACPI_NULL;

    return obj;
}

uacpi_status uacpi_namespace_node_acquire_object_typed(
    const uacpi_namespace_node *node, uacpi_object_type_bits type_mask,
    uacpi_object **out_obj
)
{
    uacpi_status ret;
    uacpi_object *obj;

    ret = uacpi_namespace_read_lock();
    if (uacpi_unlikely_error(ret))
        return ret;

    obj = uacpi_namespace_node_get_object(node);

    if (uacpi_unlikely(obj == UACPI_NULL) ||
        !uacpi_object_is_one_of(obj, type_mask)) {
        ret = UACPI_STATUS_INVALID_ARGUMENT;
        goto out;
    }

    uacpi_object_ref(obj);
    *out_obj = obj;

out:
    uacpi_namespace_read_unlock();
    return ret;
}

uacpi_status uacpi_namespace_node_acquire_object(
    const uacpi_namespace_node *node, uacpi_object **out_obj
)
{
    return uacpi_namespace_node_acquire_object_typed(
        node, UACPI_OBJECT_ANY_BIT, out_obj
    );
}

enum action {
    ACTION_REACQUIRE,
    ACTION_PUT,
};

static uacpi_status object_mutate_refcount(
    uacpi_object *obj, void (*cb)(uacpi_object*)
)
{
   uacpi_status ret = UACPI_STATUS_OK;

    if (uacpi_likely(!uacpi_object_is(obj, UACPI_OBJECT_REFERENCE))) {
        cb(obj);
        return ret;
    }

    /*
     * Reference objects must be (un)referenced under at least a read lock, as
     * this requires walking down the entire reference chain and dropping each
     * object ref-count by 1. This might race with the interpreter and
     * object_replace_child in case an object in the chain is CopyObject'ed
     * into.
     */
    ret = uacpi_namespace_read_lock();
    if (uacpi_unlikely_error(ret))
        return ret;

    cb(obj);

    uacpi_namespace_read_unlock();
    return ret;
}

uacpi_status uacpi_namespace_node_reacquire_object(
    uacpi_object *obj
)
{
    return object_mutate_refcount(obj, uacpi_object_ref);
}

uacpi_status uacpi_namespace_node_release_object(uacpi_object *obj)
{
    return object_mutate_refcount(obj, uacpi_object_unref);
}

uacpi_object_name uacpi_namespace_node_name(const uacpi_namespace_node *node)
{
    return node->name;
}

uacpi_status uacpi_namespace_node_type_unlocked(
    const uacpi_namespace_node *node, uacpi_object_type *out_type
)
{
    uacpi_object *obj;

    if (uacpi_unlikely(node == UACPI_NULL))
        return UACPI_STATUS_INVALID_ARGUMENT;

    obj = uacpi_namespace_node_get_object(node);
    if (uacpi_unlikely(obj == UACPI_NULL))
        return UACPI_STATUS_NOT_FOUND;

    *out_type = obj->type;
    return UACPI_STATUS_OK;
}

uacpi_status uacpi_namespace_node_type(
    const uacpi_namespace_node *node, uacpi_object_type *out_type
)
{
    uacpi_status ret;

    ret = uacpi_namespace_read_lock();
    if (uacpi_unlikely_error(ret))
        return ret;

    ret = uacpi_namespace_node_type_unlocked(node, out_type);

    uacpi_namespace_read_unlock();
    return ret;
}

uacpi_status uacpi_namespace_node_is_one_of_unlocked(
    const uacpi_namespace_node *node, uacpi_object_type_bits type_mask, uacpi_bool *out
)
{
    uacpi_object *obj;

    if (uacpi_unlikely(node == UACPI_NULL))
        return UACPI_STATUS_INVALID_ARGUMENT;

    obj = uacpi_namespace_node_get_object(node);
    if (uacpi_unlikely(obj == UACPI_NULL))
        return UACPI_STATUS_NOT_FOUND;

    *out = uacpi_object_is_one_of(obj, type_mask);

    return UACPI_STATUS_OK;
}

uacpi_status uacpi_namespace_node_is_one_of(
    const uacpi_namespace_node *node, uacpi_object_type_bits type_mask,
    uacpi_bool *out
)
{
    uacpi_status ret;

    ret = uacpi_namespace_read_lock();
    if (uacpi_unlikely_error(ret))
        return ret;

    ret = uacpi_namespace_node_is_one_of_unlocked(node,type_mask, out);

    uacpi_namespace_read_unlock();
    return ret;
}

uacpi_status uacpi_namespace_node_is(
    const uacpi_namespace_node *node, uacpi_object_type type, uacpi_bool *out
)
{
    return uacpi_namespace_node_is_one_of(
        node, 1u << type, out
    );
}

uacpi_status uacpi_namespace_do_for_each_child(
    uacpi_namespace_node *node, uacpi_iteration_callback descending_callback,
    uacpi_iteration_callback ascending_callback,
    uacpi_object_type_bits type_mask, uacpi_u32 max_depth,
    enum uacpi_should_lock should_lock,
    enum uacpi_permanent_only permanent_only, void *user
)
{
    uacpi_status ret = UACPI_STATUS_OK;
    uacpi_iteration_decision decision;
    uacpi_iteration_callback cb;
    uacpi_bool walking_up = UACPI_FALSE, matches = UACPI_FALSE;
    uacpi_u32 depth = 1;

    UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_SUBSYSTEM_INITIALIZED);

    if (uacpi_unlikely(descending_callback == UACPI_NULL &&
                       ascending_callback == UACPI_NULL))
        return UACPI_STATUS_INVALID_ARGUMENT;

    if (uacpi_unlikely(node == UACPI_NULL || max_depth == 0))
        return UACPI_STATUS_INVALID_ARGUMENT;

    if (should_lock == UACPI_SHOULD_LOCK_YES) {
        ret = uacpi_namespace_read_lock();
        if (uacpi_unlikely_error(ret))
            return ret;
    }

    if (node->child == UACPI_NULL)
        goto out;

    node = node->child;

    while (depth) {
        uacpi_namespace_node_is_one_of_unlocked(node, type_mask, &matches);
        if (!matches) {
            decision = UACPI_ITERATION_DECISION_CONTINUE;
            goto do_next;
        }

        if (permanent_only == UACPI_PERMANENT_ONLY_YES &&
            uacpi_namespace_node_is_temporary(node)) {
            decision = UACPI_ITERATION_DECISION_NEXT_PEER;
            goto do_next;
        }

        cb = walking_up ? ascending_callback : descending_callback;
        if (cb != UACPI_NULL) {
            if (should_lock == UACPI_SHOULD_LOCK_YES) {
                ret = uacpi_namespace_read_unlock();
                if (uacpi_unlikely_error(ret))
                    return ret;
            }

            decision = cb(user, node, depth);
            if (decision == UACPI_ITERATION_DECISION_BREAK)
                return ret;

            if (should_lock == UACPI_SHOULD_LOCK_YES) {
                ret = uacpi_namespace_read_lock();
                if (uacpi_unlikely_error(ret))
                    return ret;
            }
        } else {
            decision = UACPI_ITERATION_DECISION_CONTINUE;
        }

    do_next:
        if (walking_up) {
            if (node->next) {
                node = node->next;
                walking_up = UACPI_FALSE;
                continue;
            }

            depth--;
            node = node->parent;
            continue;
        }

        switch (decision) {
        case UACPI_ITERATION_DECISION_CONTINUE:
            if ((depth != max_depth) && (node->child != UACPI_NULL)) {
                node = node->child;
                depth++;
                continue;
            }
            UACPI_FALLTHROUGH;
        case UACPI_ITERATION_DECISION_NEXT_PEER:
            walking_up = UACPI_TRUE;
            continue;
        default:
            ret = UACPI_STATUS_INVALID_ARGUMENT;
            goto out;
        }
    }

out:
    if (should_lock == UACPI_SHOULD_LOCK_YES)
        uacpi_namespace_read_unlock();
    return ret;
}

uacpi_status uacpi_namespace_for_each_child_simple(
    uacpi_namespace_node *parent, uacpi_iteration_callback callback, void *user
)
{
    return uacpi_namespace_do_for_each_child(
        parent, callback, UACPI_NULL, UACPI_OBJECT_ANY_BIT, UACPI_MAX_DEPTH_ANY,
        UACPI_SHOULD_LOCK_YES, UACPI_PERMANENT_ONLY_YES, user
    );
}

uacpi_status uacpi_namespace_for_each_child(
    uacpi_namespace_node *parent, uacpi_iteration_callback descending_callback,
    uacpi_iteration_callback ascending_callback,
    uacpi_object_type_bits type_mask, uacpi_u32 max_depth, void *user
)
{
    return uacpi_namespace_do_for_each_child(
        parent, descending_callback, ascending_callback, type_mask, max_depth,
        UACPI_SHOULD_LOCK_YES, UACPI_PERMANENT_ONLY_YES, user
    );
}

uacpi_status uacpi_namespace_node_next_typed(
    uacpi_namespace_node *parent, uacpi_namespace_node **iter,
    uacpi_object_type_bits type_mask
)
{
    uacpi_status ret;
    uacpi_bool is_one_of;
    uacpi_namespace_node *node;

    UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_SUBSYSTEM_INITIALIZED);

    if (uacpi_unlikely(parent == UACPI_NULL && *iter == UACPI_NULL))
        return UACPI_STATUS_INVALID_ARGUMENT;

    ret = uacpi_namespace_read_lock();
    if (uacpi_unlikely_error(ret))
        return ret;

    node = *iter;
    if (node == UACPI_NULL)
        node = parent->child;
    else
        node = node->next;

    for (; node != UACPI_NULL; node = node->next) {
        if (uacpi_namespace_node_is_temporary(node))
            continue;

        ret = uacpi_namespace_node_is_one_of_unlocked(
            node, type_mask, &is_one_of
        );
        if (uacpi_unlikely_error(ret))
            break;
        if (is_one_of)
            break;
    }

    uacpi_namespace_read_unlock();
    if (node == UACPI_NULL)
        return UACPI_STATUS_NOT_FOUND;

    if (uacpi_likely_success(ret))
        *iter = node;
    return ret;
}

uacpi_status uacpi_namespace_node_next(
    uacpi_namespace_node *parent, uacpi_namespace_node **iter
)
{
    return uacpi_namespace_node_next_typed(
        parent, iter, UACPI_OBJECT_ANY_BIT
    );
}

uacpi_size uacpi_namespace_node_depth(const uacpi_namespace_node *node)
{
    uacpi_size depth = 0;

    while (node->parent) {
        depth++;
        node = node->parent;
    }

    return depth;
}

uacpi_namespace_node *uacpi_namespace_node_parent(
    uacpi_namespace_node *node
)
{
    return node->parent;
}

const uacpi_char *uacpi_namespace_node_generate_absolute_path(
    const uacpi_namespace_node *node
)
{
    uacpi_size depth, offset;
    uacpi_size bytes_needed;
    uacpi_char *path;

    depth = uacpi_namespace_node_depth(node) + 1;

    // \ only needs 1 byte, the rest is 4 bytes
    bytes_needed = 1 + (depth - 1) * sizeof(uacpi_object_name);

    // \ and the first NAME don't need a '.', every other segment does
    bytes_needed += depth > 2 ? depth - 2 : 0;

    // Null terminator
    bytes_needed += 1;

    path = uacpi_kernel_alloc(bytes_needed);
    if (uacpi_unlikely(path == UACPI_NULL))
        return path;

    path[0] = '\\';

    offset = bytes_needed - 1;
    path[offset] = '\0';

    while (node != uacpi_namespace_root()) {
        offset -= sizeof(uacpi_object_name);
        uacpi_memcpy(&path[offset], node->name.text, sizeof(uacpi_object_name));

        node = node->parent;
        if (node != uacpi_namespace_root())
            path[--offset] = '.';
    }

    return path;
}

void uacpi_free_absolute_path(const uacpi_char *path)
{
    uacpi_free_dynamic_string(path);
}

#endif // !UACPI_BAREBONES_MODE
