#include <stdint.h>

#include "coduo_x87emu.h" /* defines EMULATE_X87; x87 shim when it is 1 */
#include "coduo_native_x87.h"
#include "../scripting/script_memory_private.h"
#include "../scripting/script_variable_private.h"
#include "coduo_engine_structs.h"
#include "xanim_private.h"

enum {
    XANIM_POOL_SENTINEL_INDEX = 0,
    XANIM_PRIMARY_STATE_INDEX = 0,
    XANIM_SECONDARY_STATE_INDEX = 1,
    XANIM_INITIAL_POOL_USED_COUNT = 1,
    XANIM_NO_SCRIPT_VARIABLE_HANDLE = 0,
    XANIM_END_NOTIFY_STRING_USER = 0,
    XANIM_END_NOTIFY_STRING_TYPE = 3
};

void XAnimBeginLoadFiles(void)
{
    xanim_rootTreeHandle = Scr_AllocArray();
}

void XAnimLoadPendingFiles(void *(*alloc)(size_t size))
{
    uint16_t child;

    for (child = FindNextSibling(xanim_rootTreeHandle); child != 0;
         child = FindNextSibling(child)) {
        uint16_t nameHandle = GetVariableName(child);
        const char *animName =
            SL_ConvertToString(nameHandle);

        XAnimLoadFile(animName, alloc);
    }

    RemoveRefToObject(xanim_rootTreeHandle);
    xanim_rootTreeHandle = XANIM_NO_SCRIPT_VARIABLE_HANDLE;
}

void XAnimInit(void)
{
    XAnimInfo *sentinel;

    for (int32_t index = 0; index < CODUO_XANIM_POOL_NODE_COUNT; ++index) {
        xanim_pool[index].freePrev =
            (uint16_t)(
                (index + CODUO_XANIM_POOL_NODE_COUNT - 1) %
                CODUO_XANIM_POOL_NODE_COUNT);
        xanim_pool[index].freeNext =
            (uint16_t)(
                (index + 1) % CODUO_XANIM_POOL_NODE_COUNT);
    }

    sentinel = &xanim_pool[XANIM_POOL_SENTINEL_INDEX];
    sentinel->states[XANIM_PRIMARY_STATE_INDEX].time = 0.0f;
    sentinel->states[XANIM_PRIMARY_STATE_INDEX].oldTime = 0.0f;
    sentinel->states[XANIM_SECONDARY_STATE_INDEX].time = 0.0f;
    sentinel->states[XANIM_SECONDARY_STATE_INDEX].oldTime = 0.0f;
    sentinel->states[XANIM_PRIMARY_STATE_INDEX].cycleCount = 0;
    sentinel->states[XANIM_PRIMARY_STATE_INDEX].oldCycleCount = 0;
    sentinel->states[XANIM_SECONDARY_STATE_INDEX].cycleCount = 0;
    sentinel->states[XANIM_SECONDARY_STATE_INDEX].oldCycleCount = 0;

    xanim_endNotifyHandle =
        SL_GetString_("end", XANIM_END_NOTIFY_STRING_USER,
                            XANIM_END_NOTIFY_STRING_TYPE);
    xanim_poolUsedCount = XANIM_INITIAL_POOL_USED_COUNT;
    xanim_poolHighWaterCount = XANIM_INITIAL_POOL_USED_COUNT;
}

void XAnimShutdown(void)
{
    if (xanim_endNotifyHandle != XANIM_NO_SCRIPT_VARIABLE_HANDLE) {
        SL_RemoveRefToString(xanim_endNotifyHandle);
        xanim_endNotifyHandle = XANIM_NO_SCRIPT_VARIABLE_HANDLE;
    }
}

int32_t XAnimFindByteKey(float frameFrac, const uint8_t *keys,
                         int32_t keyCount, int32_t targetKey)
{
    int32_t low;
    int32_t index;
    int32_t scan;

    low = 0;
    /* 0x80b9921..0x80b9939: bare fild feeds fmul and the product feeds
     * truncating fistp directly. */
#if EMULATE_X87
    index = x87f_store_i32_trunc(
        x87f_mul(x87f_load_i32(keyCount), x87f_load_f32(frameFrac)));
#elif defined(__x86_64__)
    index = CODUO_X87_TRUNCATE_I32(
        (long double)keyCount * (long double)frameFrac);
#else
    index = (int32_t)(keyCount * frameFrac);
#endif
    if (targetKey < keys[index]) {
        do {
            do {
                keyCount = index;
                index = (low + keyCount) / 2;
            } while (targetKey < keys[index]);
            if (targetKey < keys[index + 1]) {
                return index;
            }
            low = index + 1;
            index = keyCount - 1;
        } while (targetKey < keys[index]);
    } else if (keys[index + 1] <= targetKey) {
        scan = index + 1;
        do {
            while (low = scan, scan = (low + keyCount) / 2,
                   keys[scan] <= targetKey) {
                if (targetKey < keys[scan + 1]) {
                    return scan;
                }
                scan++;
            }
            keyCount = scan;
            scan = low + 1;
        } while (keys[low + 1] <= targetKey);
        index = low;
    }

    return index;
}

int32_t XAnimFindShortKey(float frameFrac, const uint16_t *keys,
                          int32_t keyCount, int32_t targetKey)
{
    int32_t low;
    int32_t index;
    int32_t scan;

    low = 0;
    /* 0x80b9a3f..0x80b9a57: same bare-fild/product/fistp sequence. */
#if EMULATE_X87
    index = x87f_store_i32_trunc(
        x87f_mul(x87f_load_i32(keyCount), x87f_load_f32(frameFrac)));
#elif defined(__x86_64__)
    index = CODUO_X87_TRUNCATE_I32(
        (long double)keyCount * (long double)frameFrac);
#else
    index = (int32_t)(keyCount * frameFrac);
#endif
    if (targetKey < keys[index]) {
        do {
            do {
                keyCount = index;
                index = (low + keyCount) / 2;
            } while (targetKey < keys[index]);
            if (targetKey < keys[index + 1]) {
                return index;
            }
            low = index + 1;
            index = keyCount - 1;
        } while (targetKey < keys[index]);
    } else if (keys[index + 1] <= targetKey) {
        scan = index + 1;
        do {
            while (low = scan, scan = (low + keyCount) / 2,
                   keys[scan] <= targetKey) {
                if (targetKey < keys[scan + 1]) {
                    return scan;
                }
                scan++;
            }
            keyCount = scan;
            scan = low + 1;
        } while (keys[low + 1] <= targetKey);
        index = low;
    }

    return index;
}

void XAnimClearData(DObjAnimMat *part)
{
    for (int32_t partIndex = 0; partIndex < xanim_evalPartCount; ++partIndex) {
        if (((xanim_evalSkipBits[partIndex >> 5] >> (partIndex & 31)) & 1U) ==
            0) {
            part->primaryLane0 = 0.0f;
            part->primaryLane1 = 0.0f;
            part->primaryLane2 = 0.0f;
            part->primaryLane3 = 0.0f;
            part->accumulatedWeight = 0.0f;
            part->secondaryLane2 = 0.0f;
            part->secondaryLane1 = 0.0f;
            part->secondaryLane0 = 0.0f;
        }

        part++;
    }
}
