#include "q_string_compare.h"

#include <stddef.h>
#include <stdint.h>

enum {
    Q_STRING_WHOLE_COMPARE_COUNT = 99999
};

/* Original-machine-code sources:
 *
 * Windows i686 (the four implementations are instruction-identical):
 *   CoDUOMP.exe           0x0044f830 / 0x0044f890 / 0x0044f8d0
 *   uo_game_mp_x86.dll    0x20057e40 / 0x20057ea0 / 0x20057ee0
 *   uo_cgame_mp_x86.dll   0x3004e620 / 0x3004e680 / 0x3004e6c0
 *   uo_ui_mp_x86.dll      0x40006650 / 0x400066b0 / 0x400066f0
 * Linux i386:
 *   coduo_lnxded          0x08086946 / 0x080869eb / 0x08086a5a
 *   game.mp.uo.i386.so    0x00093451 / 0x00093506 / 0x00093575
 *
 * Each row lists Q_stricmpn, Q_strncmp, then Q_stricmp. The Linux compiler
 * spells the lowercase tests as calls to Q_islower; those calls implement the
 * same inclusive ASCII 'a'..'z' range used inline by the Windows bodies.
 *
 * The supporting PowerPC PEF copies in the Mac engine, game, cgame, and UI
 * have byte-identical Q_strncmp and Q_stricmp bodies. Their Q_stricmpn bodies
 * are also identical except for the two relative branches to each image's
 * local Q_islower. File offsets are, respectively:
 *   engine 0xe68d0 / 0xe6850 / 0xe67d0
 *   game   0x199f0 / 0x19970 / 0x198f0
 *   cgame  0x17d00 / 0x17c80 / 0x17c00
 *   UI     0x1f870 / 0x1f7f0 / 0x1f770
 */

int32_t Q_stricmpn(const char *left, const char *right, int32_t count)
{
    uint32_t remaining = (uint32_t)count;

    for (;;) {
        int32_t leftCharacter = (int32_t)(signed char)*left++;
        int32_t rightCharacter = (int32_t)(signed char)*right++;
        uint32_t countBefore = remaining;

        /* ORIGINAL_BINARY_BUG: every original loads both input bytes before
         * testing the old count. A zero count therefore still reads one byte,
         * and exhausting a positive count on non-NUL data causes one extra
         * pair of reads. Preserve that ordering; see
         * original-bugs/ui-mp-bounded-string-compare-extra-read.md. */
        remaining -= UINT32_C(1);
        if (countBefore == 0) {
            return 0;
        }

        if (leftCharacter != rightCharacter) {
            if (leftCharacter >= 'a' && leftCharacter <= 'z') {
                leftCharacter -= 'a' - 'A';
            }
            if (rightCharacter >= 'a' && rightCharacter <= 'z') {
                rightCharacter -= 'a' - 'A';
            }
            if (leftCharacter != rightCharacter) {
                return leftCharacter < rightCharacter ? -1 : 1;
            }
        }

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

int32_t Q_strncmp(const char *left, const char *right, int32_t count)
{
    uint32_t remaining = (uint32_t)count;

    for (;;) {
        const int32_t leftCharacter = (int32_t)(signed char)*left++;
        const int32_t rightCharacter = (int32_t)(signed char)*right++;
        const uint32_t countBefore = remaining;

        /* ORIGINAL_BINARY_BUG: the original case-sensitive sibling has the
         * same pre-count input loads as Q_stricmpn. */
        remaining -= UINT32_C(1);
        if (countBefore == 0) {
            return 0;
        }

        if (leftCharacter != rightCharacter) {
            return leftCharacter < rightCharacter ? -1 : 1;
        }
        if (leftCharacter == '\0') {
            return 0;
        }
    }
}

int32_t Q_stricmp(const char *left, const char *right)
{
    if (left == NULL || right == NULL) {
        return -1;
    }

    return Q_stricmpn(left, right, Q_STRING_WHOLE_COMPARE_COUNT);
}
