// 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.

#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;
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];


#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 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)) {
                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];
}


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);
}
