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

#include "../core_command/cmd_private.h"
#include "../core_cvar/cvar_private.h"
#include "../core_memory/core_memory_private.h"
#include "coduo_ctype_compat.h"
#include "core_runtime_private.h"

enum {
    SYS_TTY_HISTORY_NEWEST_INDEX = 0,
    SYS_TTY_HISTORY_FIRST_NAVIGABLE_INDEX = 0,
    SYS_TTY_HISTORY_LAST_INDEX = CODUO_SYS_TTY_HISTORY_COUNT - 1,
    SYS_TTY_LINE_DEFAULT_WIDTH = CODUO_SYS_TTY_LINE_TEXT_SIZE,
    SYS_TTY_COMPLETION_BUFFER_SIZE = 1024
};

static const char sys_ttyCompletionArgSpace[] = " ";
static const char sys_ttyCompletionQuote[] = "\"";
static const char sys_ttyCompletionCommandFormat[] = "\\%s";
static const char sys_ttyCompletionListHeader[] = "]%s\n";

static const char *sys_ttyCompletionPrefix;
static char sys_ttyCompletionMatch[SYS_TTY_COMPLETION_BUFFER_SIZE];
static int32_t sys_ttyCompletionMatchCount;
static coduo_sys_tty_line_t *sys_ttyCompletionLine;

typedef void (*sys_tty_completion_callback_t)(const char *name);

void Sys_TTYResetLine(coduo_sys_tty_line_t *line)
{
    memset(line->text, 0, sizeof(line->text));
    line->cursor = 0;
    line->scroll = 0;
    line->widthInChars = SYS_TTY_LINE_DEFAULT_WIDTH;
}

void Sys_TTYStoreHistoryLine(coduo_sys_tty_line_t *line)
{
    int index;

    for (index = SYS_TTY_HISTORY_LAST_INDEX;
         index > SYS_TTY_HISTORY_NEWEST_INDEX; index--) {
        sys_ttyHistory[index] = sys_ttyHistory[index - 1];
    }

    sys_ttyHistory[SYS_TTY_HISTORY_NEWEST_INDEX] = *line;

    if (sys_ttyHistoryCount < CODUO_SYS_TTY_HISTORY_COUNT) {
        sys_ttyHistoryCount++;
    }

    sys_ttyHistoryCursor = SYS_TTY_HISTORY_RESET_CURSOR;
}

coduo_sys_tty_line_t *Sys_TTYPreviousHistoryLine(void)
{
    int32_t nextCursor;

    nextCursor = sys_ttyHistoryCursor + 1;
    if (nextCursor < sys_ttyHistoryCount) {
        sys_ttyHistoryCursor++;
        return &sys_ttyHistory[sys_ttyHistoryCursor];
    }

    return NULL;
}

coduo_sys_tty_line_t *Sys_TTYNextHistoryLine(void)
{
    if (sys_ttyHistoryCursor >= SYS_TTY_HISTORY_FIRST_NAVIGABLE_INDEX) {
        sys_ttyHistoryCursor--;
    }

    if (sys_ttyHistoryCursor == SYS_TTY_HISTORY_RESET_CURSOR) {
        return NULL;
    }

    return &sys_ttyHistory[sys_ttyHistoryCursor];
}

void Sys_TTYTrackCompletionMatch(const char *name)
{
    size_t prefixLength = strlen(sys_ttyCompletionPrefix);

    if (Q_stricmpn(name, sys_ttyCompletionPrefix, (int)prefixLength) == 0) {
        sys_ttyCompletionMatchCount++;
        if (sys_ttyCompletionMatchCount == 1) {
            Q_strncpyz(sys_ttyCompletionMatch, name,
                       sizeof(sys_ttyCompletionMatch));
            return;
        }

        /* ORIGINAL_BINARY_BUG: the stock loop is bounded only by the later
         * candidate's NUL.  It neither truncates a longer saved match when the
         * candidate is a strict prefix nor caps the index to the 1,024-byte
         * saved buffer; see original-bugs/coduomp-field-completion-prefix-order.md
         * and original-bugs/engine-tty-completion-match-overflow.md. */
        for (int32_t index = 0; name[index] != '\0'; ++index) {
            if (tolower(coduo_ctype_signed_byte_arg(
                    sys_ttyCompletionMatch[index])) !=
                tolower(coduo_ctype_signed_byte_arg(name[index]))) {
                sys_ttyCompletionMatch[index] = '\0';
            }
        }
    }
}

void Sys_TTYPrintCompletionMatch(const char *name)
{
    size_t matchLength = strlen(sys_ttyCompletionMatch);

    if (Q_stricmpn(name, sys_ttyCompletionMatch, (int)matchLength) == 0) {
        Com_Printf("    %s\n", name);
    }
}

void Sys_TTYAppendCompletionArgs(void)
{
    for (int32_t arg = 1; arg < Cmd_Argc(); ++arg) {
        const char *text;
        qboolean quote = qfalse;

        Q_strcat(sys_ttyCompletionLine->text,
                 sizeof(sys_ttyCompletionLine->text),
                 sys_ttyCompletionArgSpace);
        text = Cmd_Argv(arg);

        for (const char *scan = text; *scan != '\0'; ++scan) {
            if (*scan == ' ') {
                Q_strcat(sys_ttyCompletionLine->text,
                         sizeof(sys_ttyCompletionLine->text),
                         sys_ttyCompletionQuote);
                quote = qtrue;
                break;
            }
        }

        Q_strcat(sys_ttyCompletionLine->text,
                 sizeof(sys_ttyCompletionLine->text), text);

        if (quote != qfalse) {
            Q_strcat(sys_ttyCompletionLine->text,
                     sizeof(sys_ttyCompletionLine->text),
                     sys_ttyCompletionQuote);
        }
    }
}

void Sys_TTYAppendCompletionSuffix(char *sourceText, char *prefix)
{
    char *match = strstr(sourceText, prefix);

    if (match == NULL) {
        Sys_TTYAppendCompletionArgs();
        return;
    }

    Q_strcat(sys_ttyCompletionLine->text,
             sizeof(sys_ttyCompletionLine->text), match + strlen(prefix));
}

void Sys_TTYCompleteLine(coduo_sys_tty_line_t *line)
{
    coduo_sys_tty_line_t originalLine;

    sys_ttyCompletionLine = line;
    Cmd_TokenizeString(sys_ttyCompletionLine->text);
    sys_ttyCompletionPrefix = Cmd_Argv(0);
    if (*sys_ttyCompletionPrefix == '\\' || *sys_ttyCompletionPrefix == '/') {
        sys_ttyCompletionPrefix++;
    }

    sys_ttyCompletionMatchCount = 0;
    sys_ttyCompletionMatch[0] = '\0';

    if (*sys_ttyCompletionPrefix == '\0') {
        return;
    }

    Cmd_CommandCompletion(Sys_TTYTrackCompletionMatch);
    Cvar_CommandCompletion(Sys_TTYTrackCompletionMatch);
    if (sys_ttyCompletionMatchCount == 0) {
        return;
    }

    Com_Memcpy(&originalLine, sys_ttyCompletionLine, sizeof(originalLine));
    if (sys_ttyCompletionMatchCount == 1) {
        Com_sprintf(sys_ttyCompletionLine->text,
                    sizeof(sys_ttyCompletionLine->text),
                    sys_ttyCompletionCommandFormat,
                    sys_ttyCompletionMatch);
        if (Cmd_Argc() == 1) {
            Q_strcat(sys_ttyCompletionLine->text,
                     sizeof(sys_ttyCompletionLine->text),
                     sys_ttyCompletionArgSpace);
        } else {
            Sys_TTYAppendCompletionSuffix(originalLine.text,
                                          (char *)sys_ttyCompletionPrefix);
        }
        sys_ttyCompletionLine->cursor =
            (int32_t)strlen(sys_ttyCompletionLine->text);
        return;
    }

    Com_sprintf(sys_ttyCompletionLine->text,
                sizeof(sys_ttyCompletionLine->text),
                sys_ttyCompletionCommandFormat,
                sys_ttyCompletionMatch);
    sys_ttyCompletionLine->cursor =
        (int32_t)strlen(sys_ttyCompletionLine->text);
    Sys_TTYAppendCompletionSuffix(originalLine.text,
                                  (char *)sys_ttyCompletionPrefix);
    Com_Printf(sys_ttyCompletionListHeader, sys_ttyCompletionLine->text);
    Cmd_CommandCompletion(Sys_TTYPrintCompletionMatch);
    Cvar_CommandCompletion(Sys_TTYPrintCompletionMatch);
}
