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

#include "coduo_ctype_compat.h"
#include "core_runtime_private.h"

enum {
    Q_STRING_WHOLE_STRING_COMPARE_COUNT = 99999,
    Q_CP1252_RIGHT_SINGLE_QUOTE = -110,
    Q_CLEAN_CHARACTER_REPLACEMENT = '.',
    Q_STRCAT_ERROR_CODE = 0,
    COM_VA_ERROR_CODE = 1,
    COM_VA_MAX_RESULT_LENGTH = CODUO_COM_VA_SCRATCH_SIZE - 1,
    COM_VA_WRAP_LIMIT = CODUO_COM_VA_RETURN_RING_SIZE - 2,
    COM_TEMP_VECTOR_COUNT = 8,
    COM_TEMP_VECTOR_INDEX_MASK = COM_TEMP_VECTOR_COUNT - 1
};

static const char q_strcatOverflowError[] = "\x15" "Q_strcat: already overflowed";
static const char com_vaOverrunError[] =
    "\x15" "Attempted to overrun string in call to va()\n";

static coduo_com_va_scratch_t com_vaScratch;
static coduo_com_va_return_ring_t com_vaReturnRing;
static int32_t com_vaReturnRingOffset;

static vec3_t com_tempVectors[COM_TEMP_VECTOR_COUNT];
static int32_t com_tempVectorIndex;

int Q_isprint(int value)
{
    return value > 31 && value <= 126;
}

int Q_islower(int value)
{
    return value >= 'a' && value <= 'z';
}

int Q_isupper(int value)
{
    return value >= 'A' && value <= 'Z';
}

int Q_isalpha(int value)
{
    return (value >= 'a' && value <= 'z') ||
           (value >= 'A' && value <= 'Z');
}

int Q_isnumeric(int value)
{
    return value >= '0' && value <= '9';
}

int Q_isalphanumeric(int value)
{
    return Q_isalpha(value) != 0 || Q_isnumeric(value) != 0;
}

int Q_isforfilename(int value)
{
    return (Q_isalphanumeric(value) != 0 || value == '_' || value == '-') &&
           value != ' ';
}

char *Q_strrchr(const char *string, int value)
{
    const char target = (char)value;
    const char *match = NULL;
    const char *cursor;

    for (cursor = string; *cursor != '\0'; ++cursor) {
        if (*cursor == target) {
            match = cursor;
        }
    }

    if (target == '\0') {
        match = cursor;
    }

    return (char *)match;
}

void Q_strncpyz(char *dest, const char *src, int destsize)
{
    int lastIndex = destsize - 1;

    /* ORIGINAL_BINARY_BUG: stock does not reject a zero extent, so the copy
     * count becomes 0xffffffff and the terminator store targets dest[-1];
     * see original-bugs/coduomp-zero-size-string-output-underwrites.md. */
    strncpy(dest, src, (size_t)(uint32_t)lastIndex);
    dest[lastIndex] = '\0';
}

int Q_strncasecmp(const char *left, const char *right, int count)
{
    for (;;) {
        int leftChar = (signed char)*left++;
        int rightChar = (signed char)*right++;

        count--;
        if (count == -1) {
            return 0;
        }

        if (leftChar != rightChar) {
            if (Q_islower(leftChar) != 0) {
                leftChar -= 'a' - 'A';
            }
            if (Q_islower(rightChar) != 0) {
                rightChar -= 'a' - 'A';
            }
            if (leftChar != rightChar) {
                return -1;
            }
        }

        if (leftChar == '\0') {
            return 0;
        }
    }
}

int Q_strcasecmp(const char *left, const char *right)
{
    return Q_strncasecmp(left, right, Q_STRING_WHOLE_STRING_COMPARE_COUNT);
}

char *Q_strupr(char *string)
{
    char *cursor;

    for (cursor = string; *cursor != '\0'; ++cursor) {
        *cursor = (char)toupper(coduo_ctype_signed_byte_arg(*cursor));
    }

    return string;
}

void Q_strcat(char *dest, int size, const char *src)
{
    size_t used = strlen(dest);

    if (size <= (int)used) {
        Com_Error(Q_STRCAT_ERROR_CODE, q_strcatOverflowError);
    }

    Q_strncpyz(dest + used, src, size - (int)used);
}

/* NOT_FROM_ORIGINAL_SOURCE: factored color-code predicate used by string helpers. */
static qboolean engine_compat_q_is_color_string(const char *string)
{
    signed char color;

    if (string == NULL || string[0] != '^' || string[1] == '\0') {
        return qfalse;
    }

    color = (signed char)string[1];

    return color != '^' && color >= '0' && color <= '9';
}

int Q_DrawStrlen(const char *string)
{
    int length = 0;
    const char *cursor = string;

    while (*cursor != '\0') {
        if (engine_compat_q_is_color_string(cursor) != qfalse) {
            cursor += 2;
        } else {
            length++;
            cursor++;
        }
    }

    return length;
}

char *Q_CleanStr(char *string)
{
    char *out = string;
    char *in = string;

    while (*in != '\0') {
        signed char value = (signed char)*in;

        if (engine_compat_q_is_color_string(in) != qfalse) {
            in++;
        } else if (value > 31 && value != 127) {
            *out++ = (char)value;
        }
        in++;
    }

    *out = '\0';
    return string;
}

int Q_CleanCharacter(int value)
{
    signed char c = (signed char)value;

    if (c == Q_CP1252_RIGHT_SINGLE_QUOTE) {
        return '\'';
    }

    if (c < 0) {
        return Q_CLEAN_CHARACTER_REPLACEMENT;
    }

    return c;
}

char *va(const char *format, ...)
{
    int length;
    char *out;
    va_list args;

    va_start(args, format);
    length = vsnprintf(com_vaScratch, sizeof(com_vaScratch), format, args);
    va_end(args);

    com_vaScratch[COM_VA_MAX_RESULT_LENGTH] = '\0';
    if (length < 0 || length > COM_VA_MAX_RESULT_LENGTH) {
        Com_Error(COM_VA_ERROR_CODE, com_vaOverrunError);
    }

    if (com_vaReturnRingOffset + length > COM_VA_WRAP_LIMIT) {
        com_vaReturnRingOffset = 0;
    }

    out = &com_vaReturnRing[com_vaReturnRingOffset];
    memcpy(out, com_vaScratch, (size_t)length + 1U);
    com_vaReturnRingOffset += length + 1;
    return out;
}

float *tv(float x, float y, float z)
{
    float *out = com_tempVectors[com_tempVectorIndex];

    com_tempVectorIndex =
        (com_tempVectorIndex + 1) & COM_TEMP_VECTOR_INDEX_MASK;
    out[0] = x;
    out[1] = y;
    out[2] = z;

    return out;
}
