/*
 * Copyright (c) 2026, Chloe M.
 * Provided under the BSD-3 clause.
 *
 * Description:  Object manager
 * Author:       Chloe M.
 */

#include <ob/object.h>
#include <ex/pool.h>
#include <ob/cache.h>
#include <string.h>
#include <atomic.h>

ST_STATUS
ObCreateDirectory(const CHAR *Name, ST_OBJECT **Result)
{
    ST_STATUS Status;
    ST_OBJECT *DirecObj;
    OBJECT_DIRECTORY *Direc;

    if (Name == NULL || Result == NULL) {
        return STATUS_INVALID_PARAM;
    }

    Status = ObCreateObject(
        Name,
        OBJECT_TYPE_DIRECTORY,
        NULL,
        &DirecObj
    );

    if (Status != STATUS_SUCCESS) {
        return Status;
    }

    Direc = ExAllocatePoolWithTag(
        POOL_NON_PAGED,
        sizeof(*Direc),
        OBJECT_POOL_TAG
    );

    if (Direc == NULL) {
        ObReclaimObject(DirecObj);
        return STATUS_NO_MEMORY;
    }

    RtlMemSet(Direc, 0, sizeof(*Direc));
    DirecObj->Data = Direc;
    *Result = DirecObj;
    return STATUS_SUCCESS;
}

ST_STATUS
ObCreateObject(const CHAR *Name, OBJECT_TYPE Type, VOID *Data, ST_OBJECT **Result)
{
    ST_OBJECT *Object;
    USIZE NameLen;

    if (Name == NULL || Result == NULL) {
        return STATUS_INVALID_PARAM;
    }

    NameLen = RtlStrLen(Name);
    if (NameLen >= OBJECT_NAMESZ - 1) {
        return STATUS_NAME_TOO_LONG;
    }

    /*
     * Try to pull an object from the cache, if there are not
     * objects to pull then we'll allocate one.
     */
    Object = ObPopFromCache(&gObCache);
    if (Object == NULL) {
        Object = ExAllocatePoolWithTag(
            POOL_NON_PAGED,
            sizeof(*Object),
            OBJECT_POOL_TAG
        );

        if (Object == NULL)
            return STATUS_NO_MEMORY;
    }

    RtlMemSet(Object, 0, sizeof(*Object));
    RtlMemCpy(Object->Name, Name, NameLen);

    Object->Name[NameLen] = '\0';
    Object->RefCount = 1;
    Object->Type = Type;
    Object->Data = Data;
    *Result = Object;
    return STATUS_SUCCESS;
}

VOID
ObReclaimObject(ST_OBJECT *Object)
{
    if (Object == NULL) {
        return;
    }

    if (AtomicDecLong(&Object->RefCount) == 0) {
        ObReclaimToCache(&gObCache, Object);
    }
}

ST_STATUS
ObAppendDirectory(ST_OBJECT *DirecParent, ST_OBJECT *Object)
{
    OBJECT_DIRECTORY *Directory;
    ST_OBJECT *Last;

    if (DirecParent == NULL || Object == NULL) {
        return STATUS_INVALID_PARAM;
    }

    if (DirecParent->Type != OBJECT_TYPE_DIRECTORY) {
        return STATUS_NOT_DIRECTORY;
    }

    Directory = DirecParent->Data;
    if (Directory == NULL) {
        return STATUS_IO_ERROR;
    }

    /* TODO: Place lock here */
    if (Directory->First == NULL || Directory->Last == NULL) {
        Directory->First = Object;
        Directory->Last = Object;
    } else {
        Last = Directory->Last;
        Last->DirecNext = Object;
        Directory->Last = Object;
    }

    return STATUS_SUCCESS;
}

ST_STATUS
ObLookupFromTop(ST_OBJECT *Parent, const CHAR *Name, ST_OBJECT **Result)
{
    ST_OBJECT *Entry;
    OBJECT_DIRECTORY *ParentDirec;
    USIZE NameLen;

    if (Parent == NULL || Name == NULL) {
        return STATUS_INVALID_PARAM;
    }

    if (Result == NULL) {
        return STATUS_INVALID_PARAM;
    }

    /* Must be a directory */
    if (Parent->Type != OBJECT_TYPE_DIRECTORY) {
        return STATUS_NOT_DIRECTORY;
    }

    ParentDirec = Parent->Data;
    if (ParentDirec == NULL) {
        return STATUS_IO_ERROR;
    }

    Entry = ParentDirec->First;
    NameLen = RtlStrLen(Name);
    if (NameLen >= OBJECT_NAMESZ - 1) {
        return STATUS_NAME_TOO_LONG;
    }

    while (Entry != NULL) {
        if (*Entry->Name != *Name) {
            Entry = Entry->DirecNext;
            continue;
        }

        if (RtlMemCmp(Entry->Name, Name, NameLen) == 0) {
            *Result = Entry;
            return STATUS_SUCCESS;
        }

        Entry = Entry->DirecNext;
    }

    return STATUS_NOT_FOUND;
}
