// Space Taxi (JoeyLib port) -- shared types and decls.
//
// Architecture (all four JoeyLib targets):
//
//   tilemap   40x25 cells, each cell = (tileIndex, paletteSlot).
//             jlTilePaste blits the tile bank into the stage one cell
//             at a time at level boot, then it's static until the
//             scene changes. No per-frame redraw of the tilemap.
//
//   taxi      Single jlSpriteT with multiple cels (thrust frames,
//             facing variants). Save-under + restore-under each frame
//             so we don't repaint the tilemap behind it.
//
//   passenger Up to 2 simultaneous sprites: one waiting on a pad,
//             one already in the cab (or none). Same save/restore
//             discipline as the taxi.
//
//   audio     One ~3-voice event stream rendered per platform:
//             SB+PSG-synth (DOS), PT 4-voice (Amiga), YM2149 (ST),
//             Ensoniq DOC channels (IIgs). The dispatch lives in
//             stAudio.c with one entry point per voice command.
//
//   input     Joystick port 2 conventionally on the C64: direction =
//             the four cardinals (thrust), FIRE = the LANDING GEAR
//             toggle while airborne ($63DD: press edge EORs $7197
//             bit 0 with an SFX; $6462 requires gear down to land;
//             $6190 strips left/right thrust while gear is down).
//             Keyboard fallback: arrows + space for hosts without
//             joysticks.
//
// Level data lives in a small custom .dat per level (see
// assets/levels/format.md). Tile bitmaps and sprite cels are PNG
// authored externally and baked to native .tbk / .spr blobs at
// build time via tools/assetbake/assetbake.py.

#ifndef SPACETAXI_H
#define SPACETAXI_H

#include <joey/joey.h>

#define ST_TILEMAP_W        40u
#define ST_TILEMAP_H        25u
#define ST_TILE_PIXELS      8u

// The tilemap stores raw C64 screen-RAM character codes. The empty
// (non-colliding) playfield cell is the space character, $20. Every
// other code is non-blank background -- on the C64 it latches a sprite-
// background collision ($D01F). romToLevel.py uses BG=0x20 as the empty
// marker when extracting screen RAM.
#define ST_TILE_EMPTY       0x20u

// Display field is 320x200 (JoeyLib's SURFACE_WIDTH x SURFACE_HEIGHT).
// 40 tiles x 8 px = 320, 25 tiles x 8 px = 200. The bottom 3 rows are
// the HUD band (score / lives / level / current-fare strip). The top
// 22 rows are the playfield where the taxi moves.
// Full C64 screen height. The original title uses all 25 rows
// (frame at 0, 12, 24 + content). For gameplay the bottom 3 rows
// are HUD territory -- HUD draws over the tilemap there.
#define ST_PLAYFIELD_ROWS   25u
#define ST_HUD_ROW          22u
#define ST_HUD_ROW_COUNT    3u

// Maximum stuff. Tuned for fitting the smallest target (IIgs):
//   10 pads is the highest count in any canonical Space Taxi level
//     (D and M each have 10 pads; everything else <= 9)
//   Exactly ONE fare exists at a time. Verified: the C64 spawns a new
//     fare only while its state machine (gDeathStage) is idle, so
//     waiting-vs-carrying is the single passenger's `onboard` flag -- NOT
//     two passengers. (A prior "waiting + carrying = 2 sprites" note was
//     a misconception / false second source of truth.)
#define ST_MAX_PADS         10u
#define ST_MAX_PASSENGERS   1u

// Passenger destination sentinel: "UP PLEASE!" -- the fare wants to be
// flown UP through the top-wall transporter opening rather than to a
// landing pad. This is the C64's $0B destination ($6CB8), used on
// single-pad screens (and when all pads are consumed). Any real pad
// index is 0..ST_MAX_PADS-1, so 0xFE can't collide.
#define ST_DEST_TRANSPORTER 0xFEu

// The transporter opening is a fixed 4-column gap ($67 tiles) in the top
// wall, at columns 18..21 in every canonical Space Taxi screen (verified
// across the extracted tilemaps). Flying the taxi up through it exits the
// screen.
#define ST_TRANSPORTER_COL_LO 18u
#define ST_TRANSPORTER_COL_HI 22u   // exclusive
#define ST_TILE_TRANSPORTER   0x67u

// Fixed-point taxi physics: position and velocity are int16_t in
// units of 1/16 px, so the taxi can drift fractionally and the
// thrust/gravity terms are integers without losing precision over
// the whole field. (22 rows x 8 px x 16 = 2816 < 32767 so int16
// is fine.)
// Match the C64 fixed-point scale: 8-bit sub-pixel (256 sub-units per
// pixel) so the C64's accel=14 and gravity=1 are usable directly
// (14/256 px/frame initial accel; constant 1/256 px/frame gravity).
// Position needs int32_t since a 320-wide playfield * 256 sub-units
// overflows int16_t.
#define ST_SUBPIXEL_SHIFT   8
#define ST_SUBPIXEL         (1 << ST_SUBPIXEL_SHIFT)

// Taxi cels are the real C64 art (extractSprites.py): the $C0/$C1
// right-facing and $DC/$DD left-facing pairs with the $6A95 per-frame
// low-bit flicker; the engine flame is the separate direction-indexed
// sprite-2 cel set. Selection lives in stRender.c.


typedef enum {
    ST_STATE_TITLE = 0,
    ST_STATE_HIGHSCORES,    // UP on title -> "THE IMMORTAL CABBIES" ($4C1F)
    ST_STATE_INSTRUCTIONS,  // DOWN on title -> author/about screen ($556A)
    ST_STATE_OPTIONS,       // "GAME VARIATIONS" menu ($5295): pick cabbie count
    ST_STATE_DEMO,          // rolling attract demo (recorded input playback)
    ST_STATE_PLAYING,
    ST_STATE_LEVEL_DONE,
    ST_STATE_GAME_OVER
} StGameStateE;


typedef enum {
    ST_DIR_RIGHT = 0,
    ST_DIR_LEFT  = 1
} StFacingE;


typedef struct {
    uint8_t  tileX;        // landing surface left edge (tile coord)
    uint8_t  tileY;        // landing surface row (tile coord)
    uint8_t  tileW;        // pad width in tiles
    uint8_t  standX;       // passenger stand X (screen px; slot byte6-24)
} StPadT;


typedef struct {
    char         name[24];      // level display name ("UP & DOWN", etc.)
    uint8_t      tileBankId;    // which tile asset (0 = default bank)
    uint8_t      musicId;       // UNUSED in C64 Space Taxi -- the
                                // game has no per-level background
                                // music; gameplay is silent except
                                // for SFX. Title plays song 8, score
                                // screen plays song 6 or 7 (see
                                // MECHANICS.md "Sound (SID)"). Kept
                                // here as scaffolding for a possible
                                // future "level-entry jingle" event.
    uint8_t      bgColor;       // background palette slot
    uint8_t      borderColor;   // border palette slot (for HUD if used)
    uint8_t      taxiSpawnTileX;
    uint8_t      taxiSpawnTileY;
    // Per-level physics templates. Mirror the C64 templates at
    // $7D8F/$7D91 (Y/X accel) and $7D93/$7D95 (Y/X gravity). Accels
    // are unsigned magnitudes; gravities are int8 so a level can pull
    // upward (e.g. canonical level K = -7). Hand-authored levels can
    // leave them zero; loader substitutes per-level defaults below.
    uint8_t      xAccel;
    uint8_t      yAccel;
    int8_t       xGrav;
    int8_t       yGrav;
    // VIC color block from C64 $7D00-$7D08, mapped to $D020-$D028 by
    // $62F0 (scene-load). borderColor/bgColor above are $7D00/$7D01.
    // bgColor1/2/3 and spriteMc0/1 only matter in VIC multicolor mode
    // which the JoeyLib port doesn't reproduce -- they're stored so
    // the .dat format stays a faithful capture of $7D00-$7D08 but the
    // runtime ignores them. sprite0Color/sprite1Color drive the cab
    // and flame placeholder colors when no sprite asset is authored.
    uint8_t      bgColor1;      // $D022 (multicolor only, unused)
    uint8_t      bgColor2;      // $D023 (multicolor only, unused)
    uint8_t      bgColor3;      // $D024 (multicolor only, unused)
    uint8_t      spriteMc0;     // $D025 sprite multicolor 0 (unused)
    uint8_t      spriteMc1;     // $D026 sprite multicolor 1 (unused)
    uint8_t      sprite0Color;  // $D027 sprite 0 (taxi)
    uint8_t      sprite1Color;  // $D028 sprite 1 (flame)
    uint8_t      padCount;
    StPadT       pads[ST_MAX_PADS];
    // Tilemap: tile-index per cell (row-major).
    uint8_t      tilemap[ST_TILEMAP_W * ST_PLAYFIELD_ROWS];
    // Palette slot per cell -- which surface palette index a cell uses.
    uint8_t      colormap[ST_TILEMAP_W * ST_PLAYFIELD_ROWS];
} StLevelT;


typedef struct {
    // 16-bit-fixed-point position (8 bits sub-pixel + 8 bits pixel),
    // but stored in int32_t so a 320-wide playfield fits without
    // wrap. `x >> ST_SUBPIXEL_SHIFT` is the pixel column.
    int32_t      x;
    int32_t      y;
    int16_t      vx;           // velocity accumulator (sub-pixel/frame)
    int16_t      vy;
    StFacingE    facing;
    bool         thrusting;    // any directional input held this frame
    int8_t       thrustDx;     // -1 left, 0 neutral, +1 right
    int8_t       thrustDy;     // -1 up,   0 neutral, +1 down
    // Held-direction bit mask, mirroring $716A's encoding (the index
    // into the $6DB0 flame cel table): 1=UP 2=DOWN 4=LEFT 8=RIGHT.
    // With gear down the left/right bits are stripped before this is
    // built, exactly like $6190 AND #$13 feeding $60D6.
    uint8_t      dirMask;
    // Landing gear ($7197 bit 0). Toggled by a FIRE press edge while
    // airborne, retracted on takeoff ($65B0), REQUIRED down to land
    // ($6462) -- gear-up contact with a pad is a crash.
    bool         gearDown;
    bool         fireHeld;     // previous-frame fire state (edge detect)
    bool         landed;       // sitting on a pad
    uint8_t      onPad;        // pad index if landed (0xFF if none)
    // Death-animation countdown. >0 means crashed -- engine keeps the
    // cab integrating under gravity (no explosion visual; the C64
    // just lets the cab fall, see VERIFIED.md "Stage 1 $665F death
    // branch"). Reaches 0 -> respawn (or game-over). Mirrors the
    // C64's $6B24/$6B4C phase-2/3 timing.
    uint8_t      crashTicks;
    // Crash phase split ($6A72 fall vs $6B2A impact walk): false while
    // the debris tumbles, true once it hits the floor.
    bool         crashImpacted;
    // Transporter shrink-warp progress (0 = not warping; 1..64 = the
    // takeoff/shrink animation $5C91/$5CB9). During the warp the taxi
    // recedes up through the opening; the screen advances when it ends.
    uint8_t      warpFrame;
} StTaxiT;


// Passenger lifecycle phases (see the field comments in StPassengerT
// for the C64 sequences each one mirrors).
typedef enum {
    ST_PASS_BEAM_IN = 0,    // materialize at standX ($CB..$C7)
    ST_PASS_WAIT,           // stand + wave ($66D6 cycle)
    ST_PASS_WALK_TO_CAB,    // walk to the captured taxi X
    ST_PASS_BOARD_OUT,      // shrink into the cab ($C7..$CB)
    ST_PASS_RIDING,         // aboard, invisible
    ST_PASS_DROP_IN,        // delivery: materialize beside the cab
    ST_PASS_WALK_TO_PAD,    // walk to the destination standX
    ST_PASS_DROP_OUT        // shrink out -- fare complete
} StPassPhaseE;


typedef struct {
    bool         active;
    bool         onboard;      // in the cab (true) or waiting at pad (false)
    uint8_t      currentPad;   // where they are if waiting
    uint8_t      destPad;
    int16_t      x;            // pixel position
    int16_t      y;
    // Passenger lifecycle, per the traced sequences: materialize
    // ($CB..$C7 grow-in), wave in place ($66D6 cycle -- he NEVER
    // walks while waiting), walk to the landed cab ($6811 captures
    // the taxi X once; $C4/$C5 leftward, $C2/$C3 rightward, +/-2 px
    // per 3-tick step), shrink into the cab ($C7..$CB), ride, and
    // the delivery mirror (materialize at the cab, walk to standX,
    // shrink out).
    StPassPhaseE phase;
    int16_t      targetX;      // walk destination (captured once)
    // Step counter within the current phase; in ST_PASS_WAIT it is
    // the 0..3 wave-cycle index, in the walk phases its low bit is
    // the stride cel alternation.
    uint8_t      waitPhase;
} StPassengerT;


typedef struct {
    StGameStateE state;
    StLevelT     level;
    StTaxiT      taxi;
    StPassengerT passengers[ST_MAX_PASSENGERS];
    uint32_t     score;
    uint8_t      lives;
    uint8_t      levelIndex;
    // Attract-demo playback state (ST_STATE_DEMO): the recorded RLE
    // input stream and its cursor, plus the crash-finished signal the
    // engine raises instead of decrementing lives ($6B57).
    const uint8_t *demoStream;
    uint16_t     demoLen;
    uint16_t     demoOff;
    uint8_t      demoTimer;
    uint8_t      demoMask;
    bool         demoEnded;
    // Player-selectable fare quota (1..4). Mirrors C64 $7213 (the
    // GAME-VARIATIONS "cabbies" count), set on the title screen via
    // LEFT/RIGHT joystick ($5310-$5328). This is the GLOBAL number of
    // fares to deliver before the game completes -- NOT per-level.
    uint8_t      fareTarget;
} StGameT;


// Public entry points (one per source file).
bool stLevelLoad(StLevelT *out, const char *path);

void stRenderInit(jlSurfaceT *stage);
void stRenderShutdown(void);
// Blit the level's static tile art into the stage. Called once per
// scene change; falls back to colored solid tiles per index-range
// when no tile bank asset is loaded.
void stRenderLevel(jlSurfaceT *stage, const StLevelT *level);
void stRenderFrame(jlSurfaceT *stage, const StGameT *game);
// Tell the renderer the current game.level contents changed. Required
// after stLevelLoad even when the StLevelT pointer is unchanged --
// stRenderLevel's dirty-cache compares pointers, not contents.
void stRenderLevelChanged(void);
void stRenderTitleAdvance(void);
bool stRenderTitleIntroDone(void);
void stRenderTitleReset(void);

// Draw an ASCII string into the stage at tile coords (bx, by) using
// the loaded font asset. No-op if the font asset failed to load.
void stRenderDrawText(jlSurfaceT *stage, uint8_t bx, uint8_t by, const char *s);

void stEngineReset(StGameT *game);
void stEngineTick(StGameT *game);
// True when the taxi has flown up into the top-wall transporter opening
// (used to trigger a screen exit).
bool stEngineInTransporter(const StGameT *game);
// Begin the transporter shrink-warp (taxi.warpFrame 0 -> 1).
void stEngineStartWarp(StGameT *game);
// Advance the shrink-warp one frame; returns true when it completes.
bool stEngineWarpTick(StGameT *game);
// Current shrink step 0..7 (0 = full size, 7 = smallest) for rendering.
uint8_t stEngineWarpStep(const StGameT *game);

void stPassengerReset(StGameT *game);
void stPassengerTick(StGameT *game);
// Called when the taxi exits up through the transporter: delivers a
// carried "UP PLEASE" fare (if any) before the screen advances.
void stPassengerTransporterExit(StGameT *game);

void stAudioInit(void);
void stAudioShutdown(void);
void stAudioFrameTick(void);     // call once per host frame; counts down SFX
void stAudioPlayMusic(uint8_t musicId);
void stAudioStopMusic(void);
void stAudioSfxThrust(bool on);
void stAudioSfxGear(void);
void stAudioSfxLand(void);
void stAudioSfxPickup(void);
void stAudioSfxDropoff(void);
void stAudioSfxCrash(void);

void stHudDraw(jlSurfaceT *stage, const StGameT *game);

#endif
