// inputHost.c - Host-cc harness for the typed-character queue
// (jlInputGetChar / jlInputCharPush in src/core/input.c). Exercises
// FIFO ordering, the character filter, drop-newest overflow, reset
// semantics, and the jlInputPoll refill path via a mocked
// jlpInputPoll.
//
// Built by scripts/check-input.sh with -DJOEYLIB_PLATFORM_BLANK so
// port.h routes jlpInputPoll/jlpJoystickReset to the mocks below.

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

#include "joey/input.h"
#include "inputInternal.h"
#include "port.h"

// Characters the mocked jlpInputPoll delivers on its next call
// (one-shot; cleared after delivery).
static const uint8_t *gMockChars = NULL;
static int            gMockCount = 0;

// Prototypes (harness-local, alphabetical).
static bool checkEmpty(const char *name);
static bool popExpect(const char *name, const uint8_t *expect, int count);
static void pushAll(const uint8_t *chars, int count);
static bool runFilter(void);
static bool runInterleave(void);
static bool runOrder(void);
static bool runOverflow(void);
static bool runPollRefill(void);
static bool runReset(void);


void jlpInputPoll(void) {
    int i;

    for (i = 0; i < gMockCount; i++) {
        jlInputCharPush(gMockChars[i]);
    }
    gMockCount = 0;
}


void jlpJoystickReset(jlJoystickE js) {
    (void)js;
}


static bool checkEmpty(const char *name) {
    int got;

    got = jlInputGetChar();
    if (got != -1) {
        printf("  %s: expected empty (-1), got %d\n", name, got);
        return false;
    }
    return true;
}


static bool popExpect(const char *name, const uint8_t *expect, int count) {
    int got;
    int i;

    for (i = 0; i < count; i++) {
        got = jlInputGetChar();
        if (got != (int)expect[i]) {
            printf("  %s: index %d: expected 0x%02X, got %d\n",
                   name, i, expect[i], got);
            return false;
        }
    }
    return checkEmpty(name);
}


static void pushAll(const uint8_t *chars, int count) {
    int i;

    for (i = 0; i < count; i++) {
        jlInputCharPush(chars[i]);
    }
}


// Control characters other than BS/TAB/CR/ESC, plus DEL and 8-bit
// values, must never surface; the documented four and the printable
// range must pass through unchanged.
static bool runFilter(void) {
    static const uint8_t rejected[] = { 0x00, 0x01, 0x07, 0x0A, 0x0C, 0x1F, 0x7F };
    static const uint8_t accepted[] = {
        JL_CHAR_BACKSPACE, JL_CHAR_TAB, JL_CHAR_RETURN, JL_CHAR_ESCAPE,
        0x20, '0', 'Z', 'z', '~', 0x7E
    };
    bool ok;

    jlInputCharReset();
    pushAll(rejected, (int)sizeof(rejected));
    ok = checkEmpty("filter-rejects");
    pushAll(accepted, (int)sizeof(accepted));
    ok &= popExpect("filter-accepts", accepted, (int)sizeof(accepted));
    printf("  filter: %s\n", ok ? "PASS" : "FAIL");
    return ok;
}


// Pops interleaved with pushes keep FIFO order across the wrap point.
static bool runInterleave(void) {
    static const uint8_t first[]  = { 'a', 'b', 'c' };
    static const uint8_t second[] = { 'd', 'e' };
    static const uint8_t rest[]   = { 'b', 'c', 'd', 'e' };
    bool ok;
    int  got;

    jlInputCharReset();
    pushAll(first, (int)sizeof(first));
    got = jlInputGetChar();
    ok  = (got == 'a');
    if (!ok) {
        printf("  interleave: expected 'a', got %d\n", got);
    }
    pushAll(second, (int)sizeof(second));
    ok &= popExpect("interleave", rest, (int)sizeof(rest));
    printf("  interleave: %s\n", ok ? "PASS" : "FAIL");
    return ok;
}


// The definition-of-done string from the RetroNet handoff must come
// back byte-for-byte.
static bool runOrder(void) {
    static const char target[] = "192.168.1.10:6510";
    bool ok;

    jlInputCharReset();
    pushAll((const uint8_t *)target, (int)(sizeof(target) - 1));
    ok = popExpect("order", (const uint8_t *)target, (int)(sizeof(target) - 1));
    printf("  order: %s\n", ok ? "PASS" : "FAIL");
    return ok;
}


// Overflow drops the NEWEST characters: exactly JL_CHAR_QUEUE_SIZE - 1
// survive, and they are the first ones pushed.
static bool runOverflow(void) {
    uint8_t burst[JL_CHAR_QUEUE_SIZE + 8];
    bool    ok;
    int     i;

    jlInputCharReset();
    for (i = 0; i < (int)sizeof(burst); i++) {
        burst[i] = (uint8_t)('!' + (i % 0x5E));
    }
    pushAll(burst, (int)sizeof(burst));
    ok = popExpect("overflow", burst, JL_CHAR_QUEUE_SIZE - 1);
    printf("  overflow: %s\n", ok ? "PASS" : "FAIL");
    return ok;
}


// The real app-facing path: jlInputPoll runs the port poll, which
// pushes translated characters; jlInputGetChar then pops them.
static bool runPollRefill(void) {
    static const uint8_t typed[] = { 'H', 'i', '!', JL_CHAR_RETURN };
    bool ok;

    jlInputCharReset();
    gMockChars = typed;
    gMockCount = (int)sizeof(typed);
    jlInputPoll();
    ok = popExpect("poll-refill", typed, (int)sizeof(typed));
    jlInputPoll();
    ok &= checkEmpty("poll-refill-once");
    printf("  poll-refill: %s\n", ok ? "PASS" : "FAIL");
    return ok;
}


// jlInputCharReset empties the queue (the jlInit path).
static bool runReset(void) {
    static const uint8_t some[] = { 'x', 'y', 'z' };
    bool ok;

    jlInputCharReset();
    pushAll(some, (int)sizeof(some));
    jlInputCharReset();
    ok = checkEmpty("reset");
    printf("  reset: %s\n", ok ? "PASS" : "FAIL");
    return ok;
}


int main(void) {
    bool ok = true;

    printf("inputHost: typed-character queue scenarios\n");
    ok &= runOrder();
    ok &= runFilter();
    ok &= runOverflow();
    ok &= runInterleave();
    ok &= runReset();
    ok &= runPollRefill();
    if (!ok) {
        printf("inputHost: FAIL\n");
        return 1;
    }
    printf("inputHost: PASS\n");
    return 0;
}
