// Joystick demo: shows position and button state for both joystick
// ports as live, redraw-on-change visualizations. Each stick gets a
// frame with a dot indicating the current axis values, plus two
// button indicators below. A small status patch shows whether each
// port reports a connected stick. Press ESC to quit.
//
// Like the keys demo, the loop only redraws cells that changed since
// last frame so the per-cell rect-present cost stays small.

#include <stdio.h>

#include <joey/joey.h>

#define COLOR_BACKGROUND   0
#define COLOR_FRAME        1
#define COLOR_DOT_OFF      2
#define COLOR_DOT_ON       3
#define COLOR_BUTTON_OFF   4
#define COLOR_BUTTON_ON    5
#define COLOR_CONNECTED    6
#define COLOR_DISCONNECTED 7

#define FRAME_SIZE         80
#define FRAME_INSET        2
#define DOT_SIZE           8
#define DOT_TRAVEL         ((FRAME_SIZE - DOT_SIZE) / 2)

#define BUTTON_W           24
#define BUTTON_H           16
#define BUTTON_GAP         4

#define STATUS_W           80
#define STATUS_H           4

#define STICK_TOP          16
#define BUTTON_TOP         (STICK_TOP + FRAME_SIZE + 6)
#define STATUS_TOP         (BUTTON_TOP + BUTTON_H + 6)

#define STICK0_LEFT        24
#define STICK1_LEFT        216

static void buildPalette(jlSurfaceT *screen);
static void drawFrame(jlSurfaceT *screen, int16_t left);
static void drawAndPresent(jlSurfaceT *screen, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color);
static int16_t dotXFor(int16_t left, int8_t ax);
static int16_t dotYFor(int8_t ay);
static int16_t buttonXFor(int16_t left, int idx);
static void initialPaint(jlSurfaceT *screen);
static void updateStick(jlSurfaceT *screen, jlJoystickE js, int16_t left);

typedef struct {
    int16_t  dotX;
    int16_t  dotY;
    bool     btn[JOY_BUTTON_COUNT];
    bool     connected;
    bool     valid;
} StickViewT;

static StickViewT gView[JOYSTICK_COUNT];


static void buildPalette(jlSurfaceT *screen) {
    uint16_t colors[SURFACE_COLORS_PER_PALETTE];
    uint16_t i;

    for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
        colors[i] = 0x0000;
    }
    colors[COLOR_BACKGROUND]   = 0x0000;
    colors[COLOR_FRAME]        = 0x0444;
    colors[COLOR_DOT_OFF]      = 0x0666;
    colors[COLOR_DOT_ON]       = 0x00F0;  // green
    colors[COLOR_BUTTON_OFF]   = 0x0333;
    colors[COLOR_BUTTON_ON]    = 0x0FF0;  // yellow
    colors[COLOR_CONNECTED]    = 0x00F0;
    colors[COLOR_DISCONNECTED] = 0x0F00;  // red

    jlPaletteSet(screen, 0, colors);
}


static void drawAndPresent(jlSurfaceT *screen, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color) {
    /* jlFillRect marks the rect dirty; jlStagePresent flushes only that
     * dirty band. */
    jlFillRect(screen, x, y, (uint16_t)w, (uint16_t)h, color);
    jlStagePresent();
}


// Draw the static stick frame (just an outlined square -- four edges
// with the inset filled background).
static void drawFrame(jlSurfaceT *screen, int16_t left) {
    int16_t top;

    top = STICK_TOP;
    jlFillRect(screen, left, top, FRAME_SIZE, FRAME_SIZE, COLOR_FRAME);
    jlFillRect(screen,
        (int16_t)(left + FRAME_INSET),
        (int16_t)(top  + FRAME_INSET),
        (uint16_t)(FRAME_SIZE - 2 * FRAME_INSET),
        (uint16_t)(FRAME_SIZE - 2 * FRAME_INSET),
        COLOR_BACKGROUND);
}


static int16_t dotXFor(int16_t left, int8_t ax) {
    int32_t cx;
    int32_t off;

    cx  = left + FRAME_SIZE / 2 - DOT_SIZE / 2;
    off = ((int32_t)ax * DOT_TRAVEL) / JOYSTICK_AXIS_MAX;
    return (int16_t)(cx + off);
}


static int16_t dotYFor(int8_t ay) {
    int32_t cy;
    int32_t off;

    cy  = STICK_TOP + FRAME_SIZE / 2 - DOT_SIZE / 2;
    off = ((int32_t)ay * DOT_TRAVEL) / JOYSTICK_AXIS_MAX;
    return (int16_t)(cy + off);
}


static int16_t buttonXFor(int16_t left, int idx) {
    int16_t centerX;

    centerX = (int16_t)(left + FRAME_SIZE / 2);
    if (idx == 0) {
        return (int16_t)(centerX - BUTTON_W - BUTTON_GAP / 2);
    }
    return (int16_t)(centerX + BUTTON_GAP / 2);
}


static void initialPaint(jlSurfaceT *screen) {
    int16_t i;

    jlSurfaceClear(screen, COLOR_BACKGROUND);
    for (i = 0; i < JOYSTICK_COUNT; i++) {
        int16_t left;
        left = (i == 0) ? STICK0_LEFT : STICK1_LEFT;
        drawFrame(screen, left);
        gView[i].valid     = false;
        gView[i].connected = false;
    }
    jlStagePresent();
}


// Compare current joystick state against gView and redraw / present
// only the visual elements that changed.
static void updateStick(jlSurfaceT *screen, jlJoystickE js, int16_t left) {
    StickViewT *v;
    int8_t      ax;
    int8_t      ay;
    int16_t     newDotX;
    int16_t     newDotY;
    bool        newBtn[JOY_BUTTON_COUNT];
    bool        connected;
    int16_t     i;
    int16_t     bx;
    int16_t     by;

    v         = &gView[js];
    connected = jlJoystickConnected(js);
    ax        = jlJoystickX(js);
    ay        = jlJoystickY(js);
    newDotX   = dotXFor(left, ax);
    newDotY   = dotYFor(ay);
    for (i = 0; i < JOY_BUTTON_COUNT; i++) {
        newBtn[i] = jlJoyDown(js, (jlJoyButtonE)i);
    }

    if (!v->valid || v->connected != connected) {
        drawAndPresent(screen,
            (int16_t)(left + (FRAME_SIZE - STATUS_W) / 2),
            STATUS_TOP,
            STATUS_W, STATUS_H,
            connected ? COLOR_CONNECTED : COLOR_DISCONNECTED);
        v->connected = connected;
    }

    // Move the position dot: erase the old square (paint background),
    // then stamp the new one. If position didn't change, skip.
    if (!v->valid || v->dotX != newDotX || v->dotY != newDotY) {
        if (v->valid) {
            drawAndPresent(screen, v->dotX, v->dotY, DOT_SIZE, DOT_SIZE, COLOR_BACKGROUND);
        }
        drawAndPresent(screen, newDotX, newDotY, DOT_SIZE, DOT_SIZE,
            (ax != 0 || ay != 0) ? COLOR_DOT_ON : COLOR_DOT_OFF);
        v->dotX = newDotX;
        v->dotY = newDotY;
    } else if (!v->valid) {
        // First paint of a centered dot: still need to draw it once.
        drawAndPresent(screen, newDotX, newDotY, DOT_SIZE, DOT_SIZE, COLOR_DOT_OFF);
    }

    // Button squares.
    by = BUTTON_TOP;
    for (i = 0; i < JOY_BUTTON_COUNT; i++) {
        if (v->valid && v->btn[i] == newBtn[i]) {
            continue;
        }
        bx = buttonXFor(left, i);
        drawAndPresent(screen, bx, by, BUTTON_W, BUTTON_H,
            newBtn[i] ? COLOR_BUTTON_ON : COLOR_BUTTON_OFF);
        v->btn[i] = newBtn[i];
    }

    v->valid = true;
}


int main(void) {
    jlConfigT  config;
    jlSurfaceT    *screen;

    config.codegenBytes = 8 * 1024;
    config.audioBytes   = 64UL * 1024;

    if (!jlInit(&config)) {
        fprintf(stderr, "jlInit failed: %s\n", jlLastError());
        return 1;
    }

    screen = jlStageGet();
    if (screen == NULL) {
        fprintf(stderr, "jlStageGet returned NULL\n");
        jlShutdown();
        return 1;
    }

    buildPalette(screen);
    jlScbSetRange(screen, 0, SURFACE_HEIGHT - 1, 0);
    initialPaint(screen);
    jlInputPoll();

    for (;;) {
        jlInputPoll();
        if (jlKeyPressed(KEY_ESCAPE)) {
            break;
        }
        updateStick(screen, JOYSTICK_0, STICK0_LEFT);
        updateStick(screen, JOYSTICK_1, STICK1_LEFT);
    }

    jlShutdown();
    return 0;
}
