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

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

enum {
    SOUND_ALIAS_HASH_MULTIPLIER = 31337,
    SOUND_ALIAS_HASH_BUCKET_MASK = CODUO_SOUND_ALIAS_HASH_BUCKET_COUNT - 1,
    SOUND_ALIAS_NAME_MIN_PRINTABLE = 31,
    SOUND_ALIAS_FATAL_DROP = 1
};

#define SOUND_ALIAS_SUBTITLE_PREFIX "SUBTITLE_"
#define SOUND_ALIAS_SUBTITLE_FILE "soundaliases/subtitle.st"
#define SOUND_ALIAS_SUBTITLE_READ_WARNING_FORMAT \
    "WARNING: Could not read local copy of StringEd file %s\n"
#define SOUND_ALIAS_SUBTITLE_REFERENCE_TOKEN "REFERENCE"
#define SOUND_ALIAS_SUBTITLE_BAD_SYNTAX_FORMAT \
    "StringEd file %s has bad syntax"
#define SOUND_ALIAS_SUBTITLE_LANG_ENGLISH_TOKEN "LANG_ENGLISH"

int Com_SoundAliasHash(const char *name)
{
    /*
     * The stock hash recurrence (multiply by 31337, 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.
     */
    uint32_t hash;

    hash = 0;
    while (*name != '\0') {
        hash = (uint32_t)tolower(coduo_ctype_signed_byte_arg(*name)) +
               hash * SOUND_ALIAS_HASH_MULTIPLIER;
        name++;
    }

    return (int)(hash & SOUND_ALIAS_HASH_BUCKET_MASK);
}

qboolean Com_IsValidSoundAliasName(const char *name)
{
    signed char ch;

    ch = (signed char)*name;
    if (ch <= SOUND_ALIAS_NAME_MIN_PRINTABLE ||
        (isalpha(coduo_ctype_signed_byte_arg(ch)) == 0 && ch != '_')) {
        return qfalse;
    }

    do {
        name++;
        ch = (signed char)*name;
        if (ch == '\0') {
            return qtrue;
        }
        if (ch <= SOUND_ALIAS_NAME_MIN_PRINTABLE ||
            (isalnum(coduo_ctype_signed_byte_arg(ch)) == 0 && ch != '_')) {
            return qfalse;
        }
    } while (1);
}

qboolean Com_SoundAliasSubtitleReferenceExists(const char *subtitle)
{
    void *fileBuffer;
    char *parseCursor;
    char *token;
    qboolean found;

    found = qfalse;
    if (Q_strncmp(subtitle, SOUND_ALIAS_SUBTITLE_PREFIX,
                  (int)(sizeof(SOUND_ALIAS_SUBTITLE_PREFIX) - 1)) != 0) {
        return qfalse;
    }

    if (FS_ReadFile(SOUND_ALIAS_SUBTITLE_FILE, &fileBuffer) < 0) {
        Com_Printf(SOUND_ALIAS_SUBTITLE_READ_WARNING_FORMAT,
                   SOUND_ALIAS_SUBTITLE_FILE);
        return qfalse;
    }

    Com_BeginParseSession(SOUND_ALIAS_SUBTITLE_FILE);
    parseCursor = fileBuffer;
    for (;;) {
        token = Com_Parse(&parseCursor);
        if (parseCursor == NULL) {
            break;
        }
        if (strcmp(token, SOUND_ALIAS_SUBTITLE_REFERENCE_TOKEN) == 0) {
            token = Com_ParseOnLine(&parseCursor);
            if (Q_stricmp(subtitle + sizeof(SOUND_ALIAS_SUBTITLE_PREFIX) - 1,
                          token) == 0) {
                found = qtrue;
                break;
            }
        }
        Com_SkipRestOfLine(&parseCursor);
    }

    Com_EndParseSession();
    FS_FreeFile(fileBuffer);
    return found;
}

const char *Com_SoundAliasSubtitleReferenceForText(const char *englishText)
{
    void *fileBuffer;
    char *parseCursor;
    char *token;

    if (FS_ReadFile(SOUND_ALIAS_SUBTITLE_FILE, &fileBuffer) < 0) {
        Com_Printf(SOUND_ALIAS_SUBTITLE_READ_WARNING_FORMAT,
                   SOUND_ALIAS_SUBTITLE_FILE);
        return NULL;
    }

    Com_BeginParseSession(SOUND_ALIAS_SUBTITLE_FILE);
    parseCursor = fileBuffer;
    while (parseCursor != NULL) {
        token = Com_Parse(&parseCursor);
        if (parseCursor == NULL) {
            break;
        }
        if (strcmp(token, SOUND_ALIAS_SUBTITLE_REFERENCE_TOKEN) == 0) {
            token = Com_ParseOnLine(&parseCursor);
            strcpy(soundAlias_subtitleReferenceBuffer, token);
            Com_SkipRestOfLine(&parseCursor);

            do {
                token = Com_Parse(&parseCursor);
                if (parseCursor == NULL) {
                    Com_Error(SOUND_ALIAS_FATAL_DROP,
                              SOUND_ALIAS_SUBTITLE_BAD_SYNTAX_FORMAT,
                              SOUND_ALIAS_SUBTITLE_FILE);
                }
            } while (strcmp(token, SOUND_ALIAS_SUBTITLE_LANG_ENGLISH_TOKEN) !=
                     0);

            token = Com_ParseOnLine(&parseCursor);
            if (Q_stricmp(englishText, token) == 0) {
                Com_EndParseSession();
                FS_FreeFile(fileBuffer);
                return soundAlias_subtitleReferenceBuffer;
            }
        }
        Com_SkipRestOfLine(&parseCursor);
    }

    Com_EndParseSession();
    FS_FreeFile(fileBuffer);
    return NULL;
}
