#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>

#include "../core_runtime/core_runtime_private.h"
#include "sound_alias_private.h"

#define SOUND_ALIAS_STRING_SELECTOR (-1.0f)
#define SOUND_ALIAS_PICK_SELECTOR 10.0f
#define SOUND_ALIAS_WEIGHT_RANDOM_SCALE (-2147483648.0f)
#define SOUND_ALIAS_REPICK_MIN_MATCHES 3
#define SOUND_ALIAS_INDEX_NONE 0
/* Multiplicative inverse of the 19 dwords in one stock 76-byte record. */
#define SOUND_ALIAS_STRIDE_WORD_INVERSE UINT32_C(0x286bca1b)
#define I386_SIGN_BIT UINT32_C(0x80000000)
#define I386_SAR2_SIGN_FILL UINT32_C(0xc0000000)

qboolean Com_SoundAliasMatchesSelector(const coduo_sound_alias_t *alias,
                                       float selector)
{
    if (alias->selectorMin < 0.0f) {
        return 1;
    }

    if (selector < 0.0f) {
        if (alias->selectorMin <= 0.0f) {
            return 1;
        }
    } else if (alias->selectorMin <= selector && selector < alias->selectorMax) {
        return 1;
    }

    return 0;
}

coduo_sound_alias_t *Com_FindSoundAlias(const char *name,
                                        int32_t locale,
                                        float selector)
{
    coduo_sound_alias_t *alias;
    int bucket;

    if (name == NULL) {
        return NULL;
    }

    bucket = Com_SoundAliasHash(name);
    for (alias = soundAlias_hashTable[locale][bucket]; alias != NULL;
         alias = alias->hashNext) {
        if (Q_stricmp(name, alias->name) == 0 &&
            Com_SoundAliasMatchesSelector(alias, selector)) {
            return alias;
        }
    }

    return NULL;
}

const char *Com_SoundAliasString(const char *name,
                                 int32_t locale)
{
    const coduo_sound_alias_t *alias;

    alias =
        Com_FindSoundAlias(name, locale, SOUND_ALIAS_STRING_SELECTOR);
    if (alias == NULL) {
        return NULL;
    }

    return alias->name;
}

/*
 * Stock i386 Com_PickSoundAlias reads only name and locale, while the syscall
 * dispatcher also supplies the game ABI's unused alias-output word. The
 * variadic spelling represents that caller-cleaned cdecl contract without
 * leaving incompatible declarations across translation units; the extra
 * argument remains intentionally unobserved on every host.
 */
coduo_sound_alias_t *Com_PickSoundAlias(const char *name,
                                        int32_t locale, ...)
{
    coduo_sound_alias_t *firstAlias;
    coduo_sound_alias_t *selectedAlias;
    coduo_sound_alias_t *cursor;
    coduo_sound_alias_t *tableBase;
    int32_t matchingCount;
    int32_t remainingMatches;
    int32_t maxPickSequence;
    float totalWeight;

    firstAlias =
        Com_FindSoundAlias(name, locale, SOUND_ALIAS_PICK_SELECTOR);
    if (firstAlias == NULL) {
        return NULL;
    }

    selectedAlias = firstAlias;
    totalWeight = firstAlias->selectionWeight;
    matchingCount = 1;
    maxPickSequence = firstAlias->pickSequence;
    tableBase = soundAlias_tablePointers[locale];

    for (cursor = firstAlias; cursor != tableBase;) {
        --cursor;
        if (cursor->name != firstAlias->name) {
            break;
        }

        if (Com_SoundAliasMatchesSelector(cursor,
                                          SOUND_ALIAS_PICK_SELECTOR)) {
            ++matchingCount;
            totalWeight += cursor->selectionWeight;
            /* Stock 0x806e759 keeps rand() fild-exact (no float cast). */
            if (cursor->selectionWeight *
                    SOUND_ALIAS_WEIGHT_RANDOM_SCALE >
                rand() * totalWeight) {
                selectedAlias = cursor;
            }

            if (maxPickSequence < cursor->pickSequence) {
                maxPickSequence = cursor->pickSequence;
            }
        }
    }

    if (matchingCount >= SOUND_ALIAS_REPICK_MIN_MATCHES &&
        maxPickSequence == selectedAlias->pickSequence) {
        totalWeight = 0.0f;
        cursor = firstAlias;

        for (remainingMatches = matchingCount; remainingMatches != 0;
             --remainingMatches) {
            if (!Com_SoundAliasMatchesSelector(
                    cursor, SOUND_ALIAS_PICK_SELECTOR)) {
                ++remainingMatches;
            } else if (maxPickSequence != cursor->pickSequence) {
                totalWeight += cursor->selectionWeight;
                /* Stock 0x806e807 keeps rand() fild-exact (no float cast). */
                if (cursor->selectionWeight *
                        SOUND_ALIAS_WEIGHT_RANDOM_SCALE >
                    rand() * totalWeight) {
                    selectedAlias = cursor;
                }
            }

            --cursor;
        }
    }

    /* ORIGINAL_BINARY_BUG: the signed anti-repeat sequence rolls from
     * INT32_MAX to INT32_MIN; preserved per
     * original-bugs/engine-sound-alias-pick-sequence-rollover.md. */
    selectedAlias->pickSequence = maxPickSequence + 1;
    return selectedAlias;
}

coduo_sound_alias_t *
Com_SoundAliasFromIndex(int32_t index,
                        int32_t locale)
{
    coduo_sound_alias_t *tableBase;

    if (index < 1 || soundAlias_counts[locale] < index) {
        return NULL;
    }

    tableBase = soundAlias_tablePointers[locale];
    return &tableBase[index - 1];
}

int32_t
Com_SoundAliasIndex(const coduo_sound_alias_t *alias,
                    int32_t locale)
{
    const coduo_sound_alias_t *tableBase;
    int32_t index;
#if UINTPTR_MAX == UINT32_MAX
    uint32_t rawIndexBits;
#else
    uintptr_t aliasAddress;
    uintptr_t tableAddress;
    uintptr_t rawOffset;
    uintptr_t wideIndex;
#endif

    tableBase = soundAlias_tablePointers[locale];
#if UINTPTR_MAX == UINT32_MAX
    rawIndexBits =
        (uint32_t)(uintptr_t)alias - (uint32_t)(uintptr_t)tableBase;
    rawIndexBits += (uint32_t)sizeof(*alias);
    /* Stock i386 uses raw address arithmetic and sar before validation;
     * subtracting potentially invalid C pointers would itself be undefined. */
    rawIndexBits =
        (rawIndexBits >> 2) |
        ((rawIndexBits & I386_SIGN_BIT) != 0 ? I386_SAR2_SIGN_FILL : 0u);
    index =
        (int32_t)(rawIndexBits * SOUND_ALIAS_STRIDE_WORD_INVERSE);
#else
    /*
     * The stock inverse is specific to its 76-byte i386 record. Native
     * records widen with their pointers, so recover the same one-based record
     * identity with the host stride while retaining integer-address handling
     * for an ABI-supplied pointer.
     */
    aliasAddress = (uintptr_t)alias;
    tableAddress = (uintptr_t)tableBase;
    if (aliasAddress < tableAddress) {
        return SOUND_ALIAS_INDEX_NONE;
    }

    rawOffset = aliasAddress - tableAddress;
    if (rawOffset % sizeof(*alias) != 0) {
        return SOUND_ALIAS_INDEX_NONE;
    }

    wideIndex = rawOffset / sizeof(*alias) + 1U;
    if (wideIndex > INT32_MAX) {
        return SOUND_ALIAS_INDEX_NONE;
    }
    index = (int32_t)wideIndex;
#endif

    if (index < 1 || soundAlias_counts[locale] < index) {
        return SOUND_ALIAS_INDEX_NONE;
    }

    return index;
}

int32_t
Com_SoundAliasChecksum(int32_t locale)
{
    return soundAlias_checksums[locale];
}
