#include <stdint.h>

#include "../core_memory/core_memory_private.h"
#include "coduo_engine_structs.h"
#include "dobj_private.h"

enum {
    DOBJ_PRIMARY_OCCUPANCY_BIT = 1,
    DOBJ_SECONDARY_OCCUPANCY_BIT = 2,
    DOBJ_ANY_OCCUPANCY_BITS = 3
};

/* NOT_FROM_ORIGINAL_SOURCE: local factoring for packed DObj occupancy bits. */
static uint8_t coduomp_dobj_occupancy_shift(int32_t slot)
{
    return (uint8_t)((slot & 3) * CODUO_DOBJ_OCCUPANCY_BITS_PER_RECORD);
}

/* NOT_FROM_ORIGINAL_SOURCE: local factoring for packed DObj occupancy bits. */
static uint8_t coduomp_dobj_occupancy_mask(int32_t slot, uint8_t bits)
{
    return (uint8_t)(bits << coduomp_dobj_occupancy_shift(slot));
}

/* NOT_FROM_ORIGINAL_SOURCE: local factoring for packed DObj occupancy bits. */
static qboolean coduomp_dobj_slot_occupied(int32_t slot)
{
    return (dobjOccupancy[slot >> 2] &
            coduomp_dobj_occupancy_mask(slot, DOBJ_ANY_OCCUPANCY_BITS)) != 0;
}

/* NOT_FROM_ORIGINAL_SOURCE: local DObj pool slot accessor. */
static DObj *coduomp_dobj_record_for_slot(int32_t slot)
{
    return &dobjPool[slot];
}

DObj *DObjGetPrimary(int32_t key)
{
    int16_t slot;

    slot = (int16_t)dobjPrimaryLookup[key];
    if (slot == CODUO_DOBJ_SLOT_INDEX_NULL) {
        return NULL;
    }

    return coduomp_dobj_record_for_slot(slot);
}

/* NOT_FROM_ORIGINAL_SOURCE: local factoring for packed DObj occupancy bits. */
static void coduomp_dobj_set_occupancy(int32_t slot, uint8_t bits)
{
    dobjOccupancy[slot >> 2] =
        (uint8_t)(dobjOccupancy[slot >> 2] |
                  coduomp_dobj_occupancy_mask(slot, bits));
}

/* NOT_FROM_ORIGINAL_SOURCE: local factoring for packed DObj occupancy bits. */
static void coduomp_dobj_clear_occupancy(int32_t slot, uint8_t bits)
{
    dobjOccupancy[slot >> 2] =
        (uint8_t)(dobjOccupancy[slot >> 2] &
                  ~coduomp_dobj_occupancy_mask(slot, bits));
}

DObj *DObjGetForEntity(int32_t entityNum)
{
    int16_t slot;

    slot = (int16_t)dobjSecondaryLookup[entityNum];
    if (slot == 0) {
        return 0;
    }

    return coduomp_dobj_record_for_slot(slot);
}

int32_t DObjAllocSlot(void)
{
    int32_t slot;

    slot = dobjLastSearchSlot;
    while (1) {
        ++slot;
        if (slot > CODUO_DOBJ_POOL_COUNT - 1) {
            break;
        }
        if (!coduomp_dobj_slot_occupied(slot)) {
            dobjLastSearchSlot = slot;
            return slot;
        }
    }

    slot = 1;
    while (1) {
        if (dobjLastSearchSlot < slot) {
            Com_Error(1, "\x15" "No free DObjs");
            return 0;
        }
        if (!coduomp_dobj_slot_occupied(slot)) {
            dobjLastSearchSlot = slot;
            return slot;
        }
        ++slot;
    }
}

void DObjCreatePrimary(DObjModel *models,
                       uint16_t modelCount,
                       XAnimTree *runtimeTree,
                       int32_t key, uint16_t gameId)
{
    int32_t slot;

    slot = DObjAllocSlot();
    coduomp_dobj_set_occupancy(slot, DOBJ_PRIMARY_OCCUPANCY_BIT);
    dobjPrimaryLookup[key] = (uint16_t)slot;
    DObjConstructRecord(models, modelCount, runtimeTree,
                        coduomp_dobj_record_for_slot(slot),
                        gameId);
}

void DObjCreateForEntity(DObjModel *models,
                         uint16_t modelCount,
                         XAnimTree *runtimeTree,
                         int32_t entityNum,
                         uint16_t gameId)
{
    int32_t slot;

    slot = DObjAllocSlot();
    coduomp_dobj_set_occupancy(slot, DOBJ_SECONDARY_OCCUPANCY_BIT);
    dobjSecondaryLookup[entityNum] = (uint16_t)slot;
    DObjConstructRecord(models, modelCount, runtimeTree,
                        coduomp_dobj_record_for_slot(slot),
                        gameId);
}

void DObjFreePrimary(int32_t key, qboolean releaseRuntimeTree)
{
    int32_t slot;

    slot = (int16_t)dobjPrimaryLookup[key];
    if (slot == 0) {
        return;
    }

    dobjPrimaryLookup[key] = 0;
    coduomp_dobj_clear_occupancy(slot, DOBJ_PRIMARY_OCCUPANCY_BIT);
    if (!coduomp_dobj_slot_occupied(slot)) {
        DObjDestroyRecord(coduomp_dobj_record_for_slot(slot),
                          releaseRuntimeTree);
    }
}

void DObjFreeForEntity(int32_t entityNum, qboolean releaseRuntimeTree)
{
    int32_t slot;

    slot = (int16_t)dobjSecondaryLookup[entityNum];
    if (slot == 0) {
        return;
    }

    dobjSecondaryLookup[entityNum] = 0;
    coduomp_dobj_clear_occupancy(slot, DOBJ_SECONDARY_OCCUPANCY_BIT);
    if (!coduomp_dobj_slot_occupied(slot)) {
        DObjDestroyRecord(coduomp_dobj_record_for_slot(slot),
                          releaseRuntimeTree);
    }
}

void DObjMirrorEntitySlotsToPrimary(void)
{
    int32_t entityNum;
    int16_t slot;

    for (entityNum = 0; entityNum < CODUO_DOBJ_SECONDARY_LOOKUP_COUNT;
         ++entityNum) {
        DObjFreePrimary(entityNum, qfalse);
        slot = (int16_t)dobjSecondaryLookup[entityNum];
        dobjPrimaryLookup[entityNum] = (uint16_t)slot;
        if (slot != CODUO_DOBJ_SLOT_INDEX_NULL) {
            coduomp_dobj_set_occupancy(slot,
                                       DOBJ_PRIMARY_OCCUPANCY_BIT);
        }
    }
}

void DObjInitPool(void)
{
    Com_Memset(dobjOccupancy, 0, sizeof(dobjOccupancy));
    Com_Memset(dobjPrimaryLookup, 0, sizeof(dobjPrimaryLookup));
    Com_Memset(dobjSecondaryLookup, 0, sizeof(dobjSecondaryLookup));
    dobjLastSearchSlot = 1;
    dobjPoolInitialized = qtrue;
}

void DObjShutdownPool(void)
{
    if (dobjPoolInitialized != qfalse) {
        dobjPoolInitialized = qfalse;
    }
}
