#include <stdint.h>

#include "../core_memory/core_memory_private.h"
#include "../model_assets/xmodel_asset_private.h"
#include "../scripting/script_memory_private.h"
#include "coduo_engine_structs.h"
#include "dobj_private.h"

typedef struct coduo_dobj_surface_ref_s {
    int16_t childIndex;
    int16_t surfaceIndex;
} coduo_dobj_surface_ref_t;

enum {
    DOBJ_PART_BIT_WORD_SHIFT = 5,
    DOBJ_PART_BIT_WORD_MASK = 31,
    DOBJ_PART_BIT_WORD_BITS = 32
};

static const char dobj_defaultSurfaceName[] = "DEFAULT";

qboolean
DObjMarkRotTransIndex(DObj *state,
                      const uint32_t *partBits,
                      int32_t boneIndex)
{
    const uint8_t *partBytes = (const uint8_t *)partBits;
    int32_t byteIndex;
    int32_t wordIndex;
    uint8_t clientBit;
    uint32_t bit;
    coduo_xanim_eval_storage_t *storage;

    byteIndex = boneIndex >> 3;
    wordIndex = boneIndex >> 5;
    clientBit = (uint8_t)(1 << (boneIndex & 7));
    bit = 1U << (boneIndex & 31);

    if ((partBytes[byteIndex] & clientBit) == 0) {
        return 0;
    }

    storage = state->storage;
    if ((storage->blockedPartBits[wordIndex] & bit) != 0) {
        return 0;
    }

    storage->evaluatedPartBits[wordIndex] |= bit;

    return 1;
}

qboolean
DObjMarkControlRotTransIndex(DObj *state,
                             const uint32_t *partBits,
                             int32_t boneIndex)
{
    const uint8_t *partBytes = (const uint8_t *)partBits;
    int32_t byteIndex;
    int32_t wordIndex;
    uint8_t clientBit;
    uint32_t bit;
    coduo_xanim_eval_storage_t *storage;

    byteIndex = boneIndex >> 3;
    wordIndex = boneIndex >> 5;
    clientBit = (uint8_t)(1 << (boneIndex & 7));
    bit = 1U << (boneIndex & 31);

    if ((partBytes[byteIndex] & clientBit) == 0) {
        return 0;
    }

    storage = state->storage;
    if ((storage->blockedPartBits[wordIndex] & bit) != 0) {
        return 0;
    }

    storage->controlPartBits[wordIndex] |= bit;
    storage->evaluatedPartBits[wordIndex] |= bit;

    return 1;
}

int32_t
DObjGetLodForDist(DObj *state,
                  int32_t childIndex,
                  float dist)
{
    return XModelGetLodForDist(state->childRefs[childIndex], dist);
}

qboolean DObjHasContents(DObj *state,
                               int32_t contentMask)
{
    int32_t childIndex;

    childIndex = 0;
    while (childIndex < state->childCount) {
        if ((XModelGetContents(state->childRefs[childIndex]) & contentMask) !=
            0) {
            return 1;
        }
        ++childIndex;
    }

    return 0;
}

int32_t
DObjFindPartIndex(DObj *state,
                  uint16_t partName)
{
    int32_t childPartBase;
    int32_t childIndex;

    childPartBase = 0;
    childIndex = 0;
    while (childIndex < state->childCount) {
        XModel *model = state->childRefs[childIndex];
        int32_t modelPartIndex =
            XModelGetBoneIndex(model, partName);

        if (modelPartIndex >= 0) {
            return modelPartIndex + childPartBase;
        }

        childPartBase += XModelNumBones(model);

        ++childIndex;
    }

    return -1;
}

int32_t DObjGetBoneIndex(DObj *state,
                                         const char *tagName)
{
    uint16_t partName;

    partName = SL_FindLowercaseString(tagName);
    if (partName == 0) {
        return -1;
    }

    return DObjFindPartIndex(state, partName);
}

const char *DObjGetBoneName(DObj *state,
                            int32_t boneIndex)
{
    int32_t childPartBase = 0;

    for (int32_t childIndex = 0; childIndex < state->childCount;
         ++childIndex) {
        XModel *model = state->childRefs[childIndex];
        int32_t partNameCount =
            XModelNumBones(model);

        if (boneIndex - childPartBase < partNameCount) {
            const uint16_t *partNames =
                XModelBoneNames(model);
            return SL_ConvertToString(
                partNames[boneIndex - childPartBase]);
        }

        childPartBase += partNameCount;
    }

    return NULL;
}

qboolean DObjHasDefaultModel(DObj *state)
{
    for (int32_t childIndex = state->childCount - 1; childIndex >= 0;
         --childIndex) {
        if (XModelBad(state->childRefs[childIndex]) != qfalse) {
            return qtrue;
        }
    }

    return qfalse;
}

int32_t
DObjGetNumSurfaces(DObj *state,
                   const int32_t *lodIndices)
{
    int32_t surfaceCount = 0;

    for (int32_t childIndex = state->childCount - 1; childIndex >= 0;
         --childIndex) {
        if (lodIndices[childIndex] < 0) {
            continue;
        }

        const XModel *model =
            (const XModel *)state->childRefs[childIndex];
        const XModelLodInfo *lod =
            &model->info->lodRecords[lodIndices[childIndex]];
        if (lod->surfs == NULL) {
            continue;
        }

        const XModelSurfsData *surfs = lod->surfs->surfs;
        surfaceCount += surfs->surfaceCount;
    }

    return surfaceCount;
}

XSurface *DObjGetSurface(
    DObj *state, int32_t childIndex,
    int32_t surfaceIndex, const int32_t *lodIndices)
{
    XSurface **surfaces;

    XModelGetSurfaces(state->childRefs[childIndex], &surfaces,
                      lodIndices[childIndex]);
    return surfaces[surfaceIndex];
}

const char *DObjGetSurfaceName(DObj *state,
                               int32_t childIndex,
                               int32_t surfaceIndex,
                               const int32_t *lodIndices)
{
    const XModel *model =
        (const XModel *)state->childRefs[childIndex];
    const XModelLodInfo *lod =
        &model->info->lodRecords[lodIndices[childIndex]];
    uint16_t name = lod->surfaceNameTable[surfaceIndex];

    if (name == 0) {
        return dobj_defaultSurfaceName;
    }

    return SL_ConvertToString(name);
}

void DObjGetSurfaces(DObj *state,
                     coduo_dobj_surface_ref_t *surfaceRefs,
                     uint32_t *partBits,
                     const int32_t *lodIndices)
{
    DObj *record = state;

    Com_Memset(partBits, 0, CODUO_XANIM_PART_BITSET_SIZE);

    for (int32_t childIndex = 0; childIndex < state->childCount;
         ++childIndex) {
        if (lodIndices[childIndex] < 0) {
            continue;
        }

        const XModel *model =
            (const XModel *)state->childRefs[childIndex];
        const XModelLodInfo *lod =
            &model->info->lodRecords[lodIndices[childIndex]];
        if (lod->surfs == NULL) {
            continue;
        }

        const XModelSurfsData *surfs = lod->surfs->surfs;
        XSurface **surfaces = surfs->surfaces;
        int16_t surfaceCount = surfs->surfaceCount;

        int32_t childPartCount =
            XModelNumBones(state->childRefs[childIndex]);
        /* ORIGINAL_BINARY_BUG: 0x080c6eee..0x080c6ef7 sign-extends the
         * authored bone count before deriving the last mask word.  With a
         * non-word-aligned child base, a negative count reaches the
         * unconditional shifted first/final-word reads below.  Preserved;
         * see original-bugs/coduomp-xmodel-loader-unbounded-input.md. */
        int32_t lastChildWord =
            (childPartCount - 1) >> DOBJ_PART_BIT_WORD_SHIFT;
        uint8_t childPartBase = record->childPartBaseIndices[childIndex];
        int32_t baseWord = childPartBase >> DOBJ_PART_BIT_WORD_SHIFT;
        int32_t baseBit = childPartBase & DOBJ_PART_BIT_WORD_MASK;

        for (int32_t surfaceIndex = 0; surfaceIndex < surfaceCount;
             ++surfaceIndex) {
            const uint32_t *surfaceBits =
                (const uint32_t *)surfaces[surfaceIndex]->boneUsage;

            surfaceRefs->childIndex = (int16_t)childIndex;
            surfaceRefs->surfaceIndex = (int16_t)surfaceIndex;
            ++surfaceRefs;

            if (baseBit == 0) {
                for (int32_t wordIndex = 0; wordIndex <= lastChildWord;
                     ++wordIndex) {
                    partBits[baseWord + wordIndex] |= surfaceBits[wordIndex];
                }
                continue;
            }

            int32_t rightShift = DOBJ_PART_BIT_WORD_BITS - baseBit;
            partBits[baseWord] |= surfaceBits[0] << baseBit;

            for (int32_t wordIndex = 0; wordIndex < lastChildWord;
                 ++wordIndex) {
                partBits[baseWord + wordIndex + 1] |=
                    (surfaceBits[wordIndex + 1] << baseBit) |
                    (surfaceBits[wordIndex] >> rightShift);
            }

            /* ORIGINAL_BINARY_BUG: 0x080c7010..0x080c7040 always ORs the
             * final carry word.  A model beginning in the last global word
             * can therefore address partBits[4] beyond this four-word mask.
             * Preserved; see original-bugs/
             * coduomp-dobj-surfaces-carry-word-overflow.md. */
            partBits[baseWord + lastChildWord + 1] |=
                surfaceBits[lastChildWord] >> rightShift;
        }
    }
}
