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

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

enum {
    SOUND_ALIAS_FATAL_DROP = 1,
    SOUND_ALIAS_MAX_CSV_COLUMNS = 256
};

static const char *const soundAlias_csvParseColumnNameTable
    [CODUO_SOUND_ALIAS_PARSE_COLUMN_SLOT_COUNT] = {
        NULL,         "name",        "sequence", "file",
        "subtitle",  "vol_min",     "vol_max",  "pitch_min",
        "pitch_max", "dist_min",    "dist_max", "channel",
        "type",      "loop",        "probability",
        "loadspec",  "masterslave", "lod_min",  "lod_max"
};

int32_t
Com_LoadSoundAliasCsvRows(const char *sourceName, const char *csvPath,
                          int32_t count,
                          qboolean defaultLoadspec)
{
    void *fileBuffer;
    char *parseCursor;
    char *token;
    int32_t columnMap[SOUND_ALIAS_MAX_CSV_COLUMNS];
    coduo_sound_alias_parse_seen_column_table_t seenColumns;
    coduo_sound_alias_parse_node_t parseNode;
    int32_t result;
    int columnCount;
    int column;
    qboolean hasNameColumn;
    qboolean hasFileColumn;

    if (FS_ReadFile(csvPath, &fileBuffer) < 0) {
        return count;
    }

    Com_BeginParseSession(csvPath);
    Com_SetCSV(qtrue);
    parseCursor = fileBuffer;
    columnCount = 0;

    while (1) {
        token = Com_Parse(&parseCursor);
        if (parseCursor == NULL) {
            Com_EndParseSession();
            result = count;
            return result;
        }

        if (token[0] == '\0' || token[0] == '#') {
            Com_SkipRestOfLine(&parseCursor);
            continue;
        }

        if (columnCount == 0) {
            hasNameColumn = qfalse;
            hasFileColumn = qfalse;

            do {
                columnMap[columnCount] =
                    CODUO_SOUND_ALIAS_PARSE_COLUMN_UNKNOWN;

                for (column = CODUO_SOUND_ALIAS_PARSE_COLUMN_NAME;
                     column < CODUO_SOUND_ALIAS_PARSE_COLUMN_COUNT;
                     column++) {
                    if (Q_stricmp(soundAlias_csvParseColumnNameTable[column],
                                  token) == 0) {
                        columnMap[columnCount] = column;
                        if (column == CODUO_SOUND_ALIAS_PARSE_COLUMN_NAME) {
                            hasNameColumn = qtrue;
                        } else if (column ==
                                   CODUO_SOUND_ALIAS_PARSE_COLUMN_FILE) {
                            hasFileColumn = qtrue;
                        }
                        break;
                    }
                }

                columnCount++;
                if (columnCount == SOUND_ALIAS_MAX_CSV_COLUMNS ||
                    parseCursor == NULL || parseCursor[0] == '\n') {
                    break;
                }

                token = Com_ParseOnLine(&parseCursor);
            } while (1);

            if (!hasNameColumn || !hasFileColumn) {
                Com_Error(SOUND_ALIAS_FATAL_DROP,
                          "\x15" "Sound alias file %s: missing 'name' "
                                 "and/or 'file' columns\n",
                          soundAlias_currentFile);
            }

            Com_SkipRestOfLine(&parseCursor);
            continue;
        }

        memset(seenColumns, 0, sizeof(seenColumns));
        Com_InitSoundAliasParseNode(&parseNode, defaultLoadspec);

        column = 0;
        while (1) {
            if (token[0] != '\0') {
                Com_ParseSoundAliasColumn(sourceName, token,
                                          columnMap[column], seenColumns,
                                          &parseNode);
            }

            column++;
            if (column == columnCount) {
                break;
            }

            token = Com_ParseOnLine(&parseCursor);
        }

        if (seenColumns[CODUO_SOUND_ALIAS_PARSE_COLUMN_NAME] == 0 ||
            seenColumns[CODUO_SOUND_ALIAS_PARSE_COLUMN_FILE] == 0) {
            Com_Error(SOUND_ALIAS_FATAL_DROP,
                      "\x15" "Sound alias file %s: alias entry missing name "
                             "and/or file\n",
                      soundAlias_currentFile);
        }

        if (parseNode.loadspec != 0) {
            Com_ValidateSoundAliasParseNode(&parseNode);
            Com_CloneSoundAliasParseNode(&parseNode);
            count++;
        }

        Com_SkipRestOfLine(&parseCursor);
    }
}

coduo_sound_alias_parse_node_t *
Com_SortSoundAliasParseList(coduo_sound_alias_parse_node_t *head,
                            int32_t *count)
{
    int32_t leftCount;
    int32_t rightCount;
    int32_t index;
    coduo_sound_alias_parse_node_t *rightHead;
    coduo_sound_alias_parse_node_t *leftSorted;
    coduo_sound_alias_parse_node_t *rightSorted;
    coduo_sound_alias_parse_node_t *mergedHead;
    coduo_sound_alias_parse_node_t *tailNode;
    coduo_sound_alias_parse_node_t *selectedNode;
    int compare;
    int sourceCompare;

    if (*count == 1) {
        head->next = 0;
        return head;
    }

    leftCount = *count / 2;
    rightCount = *count - leftCount;
    rightHead = head;
    for (index = 0; index < leftCount; index++) {
        rightHead = rightHead->next;
    }

    leftSorted = Com_SortSoundAliasParseList(head, &leftCount);
    rightSorted = Com_SortSoundAliasParseList(rightHead, &rightCount);
    *count = 0;
    mergedHead = 0;
    tailNode = 0;

    while (1) {
        while (leftCount != 0 && rightCount != 0) {
            compare = Q_stricmp(leftSorted->name, rightSorted->name);
            if (compare == 0) {
                compare = leftSorted->sequence - rightSorted->sequence;
                if (compare == 0) {
                    sourceCompare =
                        Q_stricmp(leftSorted->sourceFile,
                                  rightSorted->sourceFile);
                    if (sourceCompare == 0) {
                        Com_Error(SOUND_ALIAS_FATAL_DROP,
                                  "\x15" "sound alias file %s: duplicate "
                                         "alias '%s'\n",
                                  leftSorted->sourceFile, leftSorted->name);
                        compare = 0;
                    } else if (sourceCompare < 0) {
                        leftSorted = leftSorted->next;
                        leftCount--;
                        continue;
                    } else {
                        rightSorted = rightSorted->next;
                        rightCount--;
                        continue;
                    }
                }
            }

            if (compare < 0) {
                selectedNode = leftSorted;
                leftSorted = leftSorted->next;
                leftCount--;
            } else {
                selectedNode = rightSorted;
                rightSorted = rightSorted->next;
                rightCount--;
            }

            if (mergedHead == NULL) {
                mergedHead = selectedNode;
            } else {
                tailNode->next = selectedNode;
            }
            tailNode = selectedNode;

            (*count)++;
        }

        if (leftCount == 0) {
            if (mergedHead == NULL) {
                mergedHead = rightSorted;
            } else {
                tailNode->next = rightSorted;
            }
            *count += rightCount;
        } else {
            if (mergedHead == NULL) {
                mergedHead = leftSorted;
            } else {
                tailNode->next = leftSorted;
            }
            *count += leftCount;
        }

        return mergedHead;
    }
}
