#include <stdint.h>
#include <string.h>
#include <strings.h>

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

enum {
    SOUND_ALIAS_LOADSPEC_PATH_SIZE = 64,
    SOUND_ALIAS_SOURCE_NAME_SIZE = 64,
    SOUND_ALIAS_MAPS_PREFIX_COMPARE_CHARS = 5,
    SOUND_ALIAS_MAP_SOURCE_SKIP_CHARS = 8,
    SOUND_ALIAS_BSP_EXTENSION_CHARS = 4,
    SOUND_ALIAS_HASH_SLICE_BYTES =
        CODUO_SOUND_ALIAS_HASH_BUCKET_COUNT *
        (int)sizeof(coduo_sound_alias_t *),
    SOUND_ALIAS_CHECKSUM_MULTIPLIER = 31337,
    SOUND_ALIAS_LOCALE_COMMON = 1,
    SOUND_ALIAS_LOCALE_LOCALIZED = 2
};

void Com_BuildSoundAliasTables(int32_t count,
                               int32_t locale)
{
    coduo_sound_alias_parse_node_t *node;
    coduo_sound_alias_parse_node_t *scan;
    coduo_sound_alias_parse_node_t *duplicateFileNode;
    coduo_sound_alias_t *table;
    char *stringBase;
    char *stringCursor;
    char *currentName;
    char *file;
    char *subtitle;
    size_t length;
    int32_t recordIndex;
    int stringBytes;
    int sharedBytes;
    uint32_t checksum;
    int byteIndex;

    soundAlias_parseListHead =
        Com_SortSoundAliasParseList(soundAlias_parseListHead, &count);

    stringBytes = 0;
    sharedBytes = 0;
    currentName = NULL;
    for (node = soundAlias_parseListHead; node != NULL; node = node->next) {
        length = strlen(node->name) + 1;
        if (currentName == NULL || Q_stricmp(currentName, node->name) != 0) {
            stringBytes += (int)length;
            currentName = node->name;
        } else {
            sharedBytes += (int)length;
        }

        if (node->subtitle[0] != '\0') {
            stringBytes += (int)strlen(node->subtitle) + 1;
        }

        length = strlen(node->file) + 1;
        for (scan = soundAlias_parseListHead; scan != node;
             scan = scan->next) {
            if (Q_stricmp(scan->file, node->file) == 0) {
                sharedBytes += (int)length;
                node->duplicateFileNode = scan;
                goto next_node;
            }
        }

        node->duplicateFileNode = NULL;
        stringBytes += (int)length;

next_node:
        ;
    }

    /*
     * 0x0806de38/0x0806de47: bare fild feeds the divide with no float-width
     * store; the byte counts stay integer-exact into the 80-bit quotient, so
     * no (float) cast on the int operand. The only rounding is the double
     * vararg store (fstp QWORD).
     */
    Com_DPrintf(
        "Sound alias strings use %.1f KB; %.1f KB saved by string sharing\n",
        (double)(stringBytes / 1024.0f),
        (double)(sharedBytes / 1024.0f));

    table = Z_Malloc((size_t)count * sizeof(*table) + (size_t)stringBytes);
    stringBase = (char *)&table[count];
    stringCursor = stringBase;
    currentName = NULL;
    recordIndex = 0;

    for (node = soundAlias_parseListHead; node != NULL; node = node->next) {
        if (currentName == NULL || Q_stricmp(currentName, node->name) != 0) {
            currentName = stringCursor;
            strcpy(stringCursor, node->name);
            stringCursor += strlen(stringCursor) + 1;
        }

        if (node->subtitle[0] == '\0') {
            subtitle = NULL;
        } else {
            subtitle = stringCursor;
            strcpy(stringCursor, node->subtitle);
            stringCursor += strlen(stringCursor) + 1;
        }

        if (node->duplicateFileNode == NULL) {
            file = stringCursor;
            strcpy(stringCursor, node->file);
            stringCursor += strlen(stringCursor) + 1;
        } else {
            duplicateFileNode = node->duplicateFileNode;
            file = (char *)duplicateFileNode->compactFile;
        }
        node->compactFile = file;

        Com_FinalizeSoundAliasRecord(node, &table[recordIndex], currentName,
                                     file, subtitle, locale);
        recordIndex++;
    }

    soundAlias_tablePointers[locale] = table;
    soundAlias_counts[locale] = count;
    /*
     * The checksum recurrence (imul reg,reg,0x7a69 then add) relies on 32-bit
     * two's-complement wraparound. Accumulate in uint32_t so the overflow is
     * defined; the low 32 bits are bit-identical to the signed imul/add the
     * original computes, and the store back into the int32_t table preserves
     * the bit pattern.
     */
    checksum = (uint32_t)count * (uint32_t)stringBytes;
    soundAlias_checksums[locale] = (int32_t)checksum;
    for (byteIndex = 0; byteIndex < stringBytes; byteIndex++) {
        checksum = (uint32_t)soundAlias_checksums[locale] *
                   SOUND_ALIAS_CHECKSUM_MULTIPLIER;
        checksum += (uint32_t)(signed char)stringBase[byteIndex];
        soundAlias_checksums[locale] = (int32_t)checksum;
    }
}

void Com_LoadSoundAliasSet(const char *sourceName,
                           int32_t locale)
{
    char loadspecPath[SOUND_ALIAS_LOADSPEC_PATH_SIZE];
    char normalizedName[SOUND_ALIAS_SOURCE_NAME_SIZE];
    char **aliasFiles;
    size_t length;
    int32_t loadspecCount;
    int fileCount;
    int fileIndex;
    int32_t loadedCount;

    loadedCount = 0;
    if (locale == SOUND_ALIAS_LOCALE_COMMON &&
        strcasecmp(soundAlias_localizedSourceName, sourceName) == 0) {
        soundAlias_tablePointers[SOUND_ALIAS_LOCALE_COMMON] =
            soundAlias_tablePointers[SOUND_ALIAS_LOCALE_LOCALIZED];
        soundAlias_counts[SOUND_ALIAS_LOCALE_COMMON] =
            soundAlias_counts[SOUND_ALIAS_LOCALE_LOCALIZED];
        memcpy(soundAlias_hashTable[SOUND_ALIAS_LOCALE_COMMON],
               soundAlias_hashTable[SOUND_ALIAS_LOCALE_LOCALIZED],
               SOUND_ALIAS_HASH_SLICE_BYTES);
    } else {
        /* ORIGINAL_BINARY_BUG: the fixed normalization/path destinations use
         * unchecked source-name copies, an eight-byte maps/ skip after only a
         * five-byte prefix test, and a length-4 suffix pointer; preserved per
         * original-bugs/coduomp-sound-alias-source-name-bounds.md. */
        if (strncasecmp(sourceName, "maps/",
                        SOUND_ALIAS_MAPS_PREFIX_COMPARE_CHARS) == 0) {
            strcpy(normalizedName,
                   sourceName + SOUND_ALIAS_MAP_SOURCE_SKIP_CHARS);
            length = strlen(normalizedName);
            if (strcasecmp(
                    normalizedName + length - SOUND_ALIAS_BSP_EXTENSION_CHARS,
                    ".bsp") == 0) {
                length -= SOUND_ALIAS_BSP_EXTENSION_CHARS;
                normalizedName[length] = '\0';
            }
        } else {
            strcpy(normalizedName, sourceName);
        }

        strlwr(normalizedName);
        if (locale == SOUND_ALIAS_LOCALE_COMMON ||
            locale == SOUND_ALIAS_LOCALE_LOCALIZED) {
            strcpy(loadspecPath, "soundloadspecs/mp/");
            strcat(loadspecPath, normalizedName);
            strcat(loadspecPath, ".csv");
            aliasFiles =
                Com_LoadSoundAliasLoadspecList(loadspecPath, &loadspecCount);
            fileCount = loadspecCount;
        } else {
            aliasFiles = FS_ListFiles("soundaliases", "csv", &fileCount);
        }

        if (fileCount == 0) {
            Com_Printf(
                "WARNING: can't find any sound alias files "
                "(soundaliases/*.csv)\n");
            return;
        }

        Hunk_SetLowTempMark();
        for (fileIndex = 0; fileIndex < fileCount; fileIndex++) {
            soundAlias_currentFile = aliasFiles[fileIndex];
            loadedCount = Com_LoadSoundAliasCsvRows(
                normalizedName, va("soundaliases/%s", soundAlias_currentFile),
                loadedCount, locale);
        }

        if (loadedCount != 0) {
            Com_BuildSoundAliasTables(loadedCount, locale);
        }

        Hunk_ClearToLowTempMark();
        if (locale == SOUND_ALIAS_LOCALE_COMMON ||
            locale == SOUND_ALIAS_LOCALE_LOCALIZED) {
            Com_FreeSoundAliasStringList(aliasFiles);
        } else {
            FS_FreeFileList(aliasFiles);
        }

        if (locale == SOUND_ALIAS_LOCALE_LOCALIZED) {
            strcpy(soundAlias_localizedSourceName, sourceName);
        }
    }

    soundAlias_loadedFlags[locale] = 1;
}

void Com_UnloadSoundAliasSet(int32_t locale)
{
    if (soundAlias_loadedFlags[locale] != 0) {
        if (locale == SOUND_ALIAS_LOCALE_LOCALIZED) {
            soundAlias_localizedSourceName[0] = '\0';
        }

        if (soundAlias_tablePointers[locale] != NULL) {
            if (locale != SOUND_ALIAS_LOCALE_COMMON ||
                soundAlias_tablePointers[SOUND_ALIAS_LOCALE_LOCALIZED] == NULL) {
                Z_Free(soundAlias_tablePointers[locale]);
            }

            soundAlias_tablePointers[locale] = NULL;
            soundAlias_counts[locale] = 0;
            memset(soundAlias_hashTable[locale], 0,
                   SOUND_ALIAS_HASH_SLICE_BYTES);
        }

        soundAlias_loadedFlags[locale] = 0;
    }
}
