// Public input API. Maintains current/previous state buffers for the
// keyboard (gKeyState/gKeyPrev), mouse buttons (gMouseButtonState/
// gMouseButtonPrev), and joystick buttons (gJoyButtonState/
// gJoyButtonPrev). jlInputPoll snapshots the previous state, then
// asks the port HAL to refresh the current state; the predicates
// derive held/edge values from those two buffers.
//
// Mouse position and joystick axes are plain current state with no
// edge predicates -- games that want deltas track the previous values
// themselves.
//
// The typed-character queue (gCharQueue) is a separate path for text
// entry: ports push OS/firmware-translated ASCII via jlInputCharPush
// during jlpInputPoll, and apps pop with jlInputGetChar. The queue is
// a classic ring with one empty slot (head == tail means empty), so
// no element count is stored and push/pop touch a single index each.

#include <string.h>

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


// See inputInternal.h for why these are uint8_t and not bool.
uint8_t gKeyState        [KEY_COUNT];
uint8_t gKeyPrev         [KEY_COUNT];

int16_t gMouseX          = 0;
bool    gMouseAttached   = false;   // set by jlpInputInit where the HAL confirms a mouse
int16_t gMouseY          = 0;
uint8_t gMouseButtonState[MOUSE_BUTTON_COUNT];
uint8_t gMouseButtonPrev [MOUSE_BUTTON_COUNT];

uint8_t gJoyConnected  [JOYSTICK_COUNT];
int8_t  gJoyAxisX      [JOYSTICK_COUNT];
int8_t  gJoyAxisY      [JOYSTICK_COUNT];
uint8_t gJoyButtonState[JOYSTICK_COUNT][JOY_BUTTON_COUNT];
uint8_t gJoyButtonPrev [JOYSTICK_COUNT][JOY_BUTTON_COUNT];
uint8_t gJoyDeadZone   [JOYSTICK_COUNT];

uint8_t gCharQueue     [JL_CHAR_QUEUE_SIZE];
uint8_t gCharQueueHead = 0;
uint8_t gCharQueueTail = 0;

// Build-time check: the ring-index wrap masks with the size minus one,
// which only wraps correctly for a power-of-two size.
typedef int joey_charqueue_pow2_check[((JL_CHAR_QUEUE_SIZE & (JL_CHAR_QUEUE_SIZE - 1)) == 0) ? 1 : -1];


#ifdef JOEYLIB_PLATFORM_IIGS
extern void iigsInputSnapshot(void);
// Build-time checks: iigsInputSnapshot's asm hard-codes KEY_COUNT=60
// and the small button counts, and walks every array one byte per
// element. If a future change adds/removes keys or buttons the asm
// must be updated; if anyone re-types the arrays back to bool the
// per-element size could grow past one byte and the asm reads
// the wrong bytes. Either condition declares a zero-size array
// below, which is a compile error.
typedef int joey_keycount_check  [(KEY_COUNT == 60) ? 1 : -1];
typedef int joey_mousebtn_check  [(MOUSE_BUTTON_COUNT == 4) ? 1 : -1];
typedef int joey_joybtn_check    [(JOYSTICK_COUNT * JOY_BUTTON_COUNT == 4) ? 1 : -1];
typedef int joey_keystate_size_check [(sizeof(gKeyState)         == KEY_COUNT)             ? 1 : -1];
typedef int joey_mousebtn_size_check [(sizeof(gMouseButtonState) == MOUSE_BUTTON_COUNT)    ? 1 : -1];
typedef int joey_joybtn_size_check   [(sizeof(gJoyButtonState)   == JOYSTICK_COUNT * JOY_BUTTON_COUNT) ? 1 : -1];
#endif

void jlInputCharPush(uint8_t ch) {
    uint8_t head;
    uint8_t next;

    if (ch > 0x7E) {
        return;
    }
    if (ch < 0x20 &&
        ch != JL_CHAR_BACKSPACE && ch != JL_CHAR_TAB &&
        ch != JL_CHAR_RETURN    && ch != JL_CHAR_ESCAPE) {
        return;
    }
    head = gCharQueueHead;
    next = (uint8_t)((head + 1u) & (JL_CHAR_QUEUE_SIZE - 1u));
    if (next == gCharQueueTail) {
        return;
    }
    gCharQueue[head] = ch;
    gCharQueueHead   = next;
}


void jlInputCharReset(void) {
    gCharQueueHead = 0;
    gCharQueueTail = 0;
}


int jlInputGetChar(void) {
    uint8_t tail;
    int     ch;

    tail = gCharQueueTail;
    if (tail == gCharQueueHead) {
        return -1;
    }
    ch             = gCharQueue[tail];
    gCharQueueTail = (uint8_t)((tail + 1u) & (JL_CHAR_QUEUE_SIZE - 1u));
    return ch;
}


void jlInputPoll(void) {
#ifdef JOEYLIB_PLATFORM_IIGS
    // One asm pass for: TTL decrement + key snapshot + mouse/joy
    // button snapshots. Replaces 3 memcpys + the C TTL loop
    // that used to live in jlpInputPoll. ~0.6 ms saved per frame.
    iigsInputSnapshot();
#else
    memcpy(gKeyPrev,         gKeyState,         sizeof(gKeyState));
    memcpy(gMouseButtonPrev, gMouseButtonState, sizeof(gMouseButtonState));
    memcpy(gJoyButtonPrev,   gJoyButtonState,   sizeof(gJoyButtonState));
#endif
    jlpInputPoll();
}


void jlWaitForAnyKey(void) {
    int16_t i;

    // Prime the previous-state snapshot so a key already held when the
    // wait starts has to be released and re-pressed (rising edge) to
    // satisfy the wait. Otherwise auto-repeat or a key still down from
    // the previous frame would exit instantly.
    jlInputPoll();
    while (1) {
        jlInputPoll();
        for (i = (int16_t)(KEY_NONE + 1); i < (int16_t)KEY_COUNT; i++) {
            if (jlKeyPressed((jlKeyE)i)) {
                // The dismissing keystroke (and anything typed during
                // the wait) was consumed by this wait, not typed at a
                // text field -- drop it from the character queue so a
                // following jlInputGetChar never sees a stray char.
                jlInputCharReset();
                return;
            }
        }
    }
}


/* All six key/mouse predicates fold the lower-bound check (`<= NONE`)
 * and upper-bound check (`>= COUNT`) into a single unsigned compare.
 * Index 0 (KEY_NONE / MOUSE_BUTTON_NONE) is a sentinel that no HAL
 * ever writes, so reading gKeyState[0] / gMouseButtonState[0] is
 * always 0 -- the predicate result is unchanged.
 *
 * DECIDED (PERF-AUDIT #40, Phase 8): these stay out-of-line functions.
 * Macro/inline forms in joey/input.h would expose the state arrays in
 * a public header and multi-evaluate args, to shave call overhead off
 * predicates already measured at 70K+ ops/sec post-clock-fix -- input
 * is nowhere near any real game's frame profile. Revisit only if a
 * profiled game shows the predicates in its frame budget. */
bool jlKeyDown(jlKeyE key) {
    if ((uint16_t)key >= (uint16_t)KEY_COUNT) {
        return false;
    }
    return gKeyState[key];
}


bool jlKeyPressed(jlKeyE key) {
    if ((uint16_t)key >= (uint16_t)KEY_COUNT) {
        return false;
    }
    return gKeyState[key] && !gKeyPrev[key];
}


bool jlKeyReleased(jlKeyE key) {
    if ((uint16_t)key >= (uint16_t)KEY_COUNT) {
        return false;
    }
    return !gKeyState[key] && gKeyPrev[key];
}


bool jlMouseDown(jlMouseButtonE button) {
    if ((uint16_t)button >= (uint16_t)MOUSE_BUTTON_COUNT) {
        return false;
    }
    return gMouseButtonState[button];
}


bool jlMousePressed(jlMouseButtonE button) {
    if ((uint16_t)button >= (uint16_t)MOUSE_BUTTON_COUNT) {
        return false;
    }
    return gMouseButtonState[button] && !gMouseButtonPrev[button];
}


bool jlMouseReleased(jlMouseButtonE button) {
    if ((uint16_t)button >= (uint16_t)MOUSE_BUTTON_COUNT) {
        return false;
    }
    return !gMouseButtonState[button] && gMouseButtonPrev[button];
}


bool jlMousePresent(void) {
    return gMouseAttached;
}


int16_t jlMouseX(void) {
    return gMouseX;
}


int16_t jlMouseY(void) {
    return gMouseY;
}


bool jlJoystickConnected(jlJoystickE js) {
    if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
        return false;
    }
    return gJoyConnected[js];
}


int8_t jlJoystickX(jlJoystickE js) {
    if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
        return 0;
    }
    return gJoyAxisX[js];
}


int8_t jlJoystickY(jlJoystickE js) {
    if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
        return 0;
    }
    return gJoyAxisY[js];
}


/* Joystick button predicates: compute the 1D byte index once and read
 * via an explicit (uint8_t *) cast so the 2D-array indexing avoids a
 * per-access multiply helper. */
bool jlJoyDown(jlJoystickE js, jlJoyButtonE button) {
    uint16_t idx;
    if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
        return false;
    }
    if ((uint16_t)button >= (uint16_t)JOY_BUTTON_COUNT) {
        return false;
    }
    idx = (uint16_t)((uint16_t)js * JOY_BUTTON_COUNT + (uint16_t)button);
    return ((const uint8_t *)gJoyButtonState)[idx] != 0;
}


bool jlJoyPressed(jlJoystickE js, jlJoyButtonE button) {
    uint16_t idx;
    if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
        return false;
    }
    if ((uint16_t)button >= (uint16_t)JOY_BUTTON_COUNT) {
        return false;
    }
    idx = (uint16_t)((uint16_t)js * JOY_BUTTON_COUNT + (uint16_t)button);
    return (((const uint8_t *)gJoyButtonState)[idx] != 0) &&
           (((const uint8_t *)gJoyButtonPrev) [idx] == 0);
}


bool jlJoyReleased(jlJoystickE js, jlJoyButtonE button) {
    uint16_t idx;
    if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
        return false;
    }
    if ((uint16_t)button >= (uint16_t)JOY_BUTTON_COUNT) {
        return false;
    }
    idx = (uint16_t)((uint16_t)js * JOY_BUTTON_COUNT + (uint16_t)button);
    return (((const uint8_t *)gJoyButtonState)[idx] == 0) &&
           (((const uint8_t *)gJoyButtonPrev) [idx] != 0);
}


void jlJoystickReset(jlJoystickE js, uint8_t deadZone) {
    if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
        return;
    }
    gJoyDeadZone[js] = deadZone;
    jlpJoystickReset(js);
}
