#include <stdint.h>

#include "coduo_engine_structs.h"
#include "coduo_int32_bits.h"
#include "dobj_private.h"

enum {
    DOBJ_CHILD_SKIP_SHIFT_MASK = 0x1f
};

void DObjBumpSkelCacheKey(void)
{
    ++dobj_skelCacheKey;
    if (dobj_skelCacheKey == 0) {
        ++dobj_skelCacheKey;
    }
}

qboolean
DObjChildSkipped(DObj *state,
                 uint8_t childIndex)
{
    return (qboolean)(coduo_int32_sar_bits(
                          state->childSkipMask,
                          childIndex & DOBJ_CHILD_SKIP_SHIFT_MASK) &
                      1U);
}

int32_t
DObjEvalStorageSize(DObj *state)
{
    return (int32_t)(
        sizeof(coduo_xanim_eval_storage_t) +
        state->partCount * (sizeof(DObjAnimMat) * 3));
}

DObjAnimMat *
DObjEvalStorageSeedOutput(DObj *state)
{
    coduo_xanim_eval_storage_t *storage = state->storage;

    return &storage->partSpans[state->partCount].evalParts.slots[0];
}

qboolean
DObjRefreshEvalStorageKey(DObj *state,
                          int32_t key)
{
    if (state->skelCacheKey == key) {
        return state->storage != NULL;
    }

    state->skelCacheKey = key;
    state->unknownState10 = 0;
    state->storage = 0;

    return 0;
}

qboolean
DObjEvalStorageIsCurrent(DObj *state,
                         int32_t key)
{
    qboolean current = 0;

    if (state->skelCacheKey == key && state->storage != NULL) {
        current = 1;
    }

    return current;
}

void DObjBindEvalStorage(DObj *state,
                         coduo_xanim_eval_storage_t *storage)
{
    int32_t wordIndex;

    state->storage = storage;

    for (wordIndex = 0; wordIndex < CODUO_XANIM_PART_BITSET_WORD_COUNT;
         ++wordIndex) {
        storage->evaluatedPartBits[wordIndex] = 0;
        storage->controlPartBits[wordIndex] = 0;
        storage->blockedPartBits[wordIndex] = 0;
    }
}

uint8_t
DObjGetChildCount(DObj *state)
{
    return state->childCount;
}

coduo_xanim_eval_child_ref_t
DObjGetChildRef(DObj *state,
                int32_t childIndex)
{
    return state->childRefs[childIndex];
}

XAnimTree *DObjGetTree(DObj *state)
{
    return state->runtimeTree;
}

int32_t DObjNumBones(DObj *state)
{
    return state->partCount;
}

qboolean DObjSkelIsBoneUpToDate(DObj *state,
                                int32_t boneIndex)
{
    const coduo_xanim_eval_storage_t *storage = state->storage;
    const uint8_t *blockedPartBytes = (const uint8_t *)storage->blockedPartBits;
    uint8_t partBit = (uint8_t)(1U << (boneIndex & 7));

    return (blockedPartBytes[boneIndex >> 3] & partBit) != 0 ? qtrue : qfalse;
}

qboolean DObjSkelAreBonesUpToDate(DObj *state,
                                  const uint32_t *partBits)
{
    const coduo_xanim_eval_storage_t *storage = state->storage;

    for (int32_t wordIndex = 0;
         wordIndex < CODUO_XANIM_PART_BITSET_WORD_COUNT; ++wordIndex) {
        if ((~storage->blockedPartBits[wordIndex] & partBits[wordIndex]) !=
            0) {
            return qfalse;
        }
    }

    return qtrue;
}
