// Generic portable-C defaults for Category B platform SERVICES (timing, and --
// in later steps -- lifecycle/present/input/audio). Most are inherently
// platform-specific, so the "generic" is a no-op/zero stub that lets a blank/
// template port link; every real port overrides via JL_HAS_<OP>. A few have a
// genuine portable default (millisElapsed derives from the frame counter).
//
// See genericDraw.c for the jlp<Op> dispatch model. Always compiled in via the
// src/generic/*.c wildcard.

#include <stdlib.h>

#include "surfaceInternal.h"
#include "port.h"


// Display refresh rate in Hz. No universal value (IIgs 50/60, Amiga/ST 50,
// DOS 70); every real port overrides. The stub keeps a blank port linkable.
uint16_t jlpGenericFrameHz(void) {
    return 60u;
}


// Monotonic frame/refresh counter. Each port reads its own hardware (VBL bar,
// tick counter, retrace edge); the stub returns 0 for a blank port.
uint16_t jlpGenericFrameCount(void) {
    return 0u;
}


// Block until the next vertical blank. No portable implementation; a blank port
// gets a no-op (it simply won't pace to the display).
void jlpGenericWaitVBL(void) {
}


// Approximate elapsed milliseconds from the frame counter and refresh rate.
// This is the IIgs/Amiga path; ports with a real hardware timer (DOS uclock,
// ST 200 Hz tick) override via JL_HAS_MILLIS_ELAPSED.
//
// Milliseconds accumulate incrementally from uint16_t frame DELTAS, so the
// wrapping 16-bit hardware counter never runs the clock backward: the clock
// is monotonic out to the natural uint32_t limit (~49.7 days) PROVIDED this
// is called at least once every 65535 frames (~18 min at 60Hz, ~22 min at
// 50Hz) -- any per-frame caller satisfies that trivially. The fractional-
// frame remainder is carried across calls, so there is no long-term drift.
// The 50/60Hz fast paths avoid the generic 32-bit divide (hundreds of
// cycles on the 65816). A change in jlpFrameHz() re-bases the clock to 0
// (the ms-per-frame basis and remainder units change; no real port switches
// refresh rate at runtime). Statics are updated via explicit local
// read-modify-write (ORCA-C lowers ++/+= on file-scope data to inc abs, a
// DBR trap on the 65816).
uint32_t jlpGenericMillisElapsed(void) {
    static uint16_t gLastFrame = 0u;
    static uint16_t gLastHz    = 0u;
    static uint32_t gMillis    = 0u;
    static uint16_t gRemainder = 0u;

    uint16_t now;
    uint16_t hz;
    uint16_t delta;
    uint16_t rem;
    uint32_t ms;
    uint32_t scaled;

    now = jlpFrameCount();
    hz  = jlpFrameHz();
    if (hz != gLastHz) {
        gLastHz    = hz;
        gLastFrame = now;
        gMillis    = 0u;
        gRemainder = 0u;
        return 0u;
    }
    delta = (uint16_t)(now - gLastFrame);
    if (delta == 0u) {
        return gMillis;
    }
    ms  = gMillis;
    rem = gRemainder;
    if (hz == 50u) {
        // Exact: 20 ms per frame, no fractional remainder.
        ms = ms + (uint32_t)delta * 20u;
    } else if (hz == 60u) {
        // Exact: 50/3 ms per frame, mod-3 remainder carried across calls.
        scaled = (uint32_t)delta * 50u + (uint32_t)rem;
        ms     = ms + scaled / 3u;
        rem    = (uint16_t)(scaled % 3u);
    } else {
        // General fallback: 1000/hz ms per frame, mod-hz remainder carried.
        scaled = (uint32_t)delta * 1000u + (uint32_t)rem;
        ms     = ms + scaled / (uint32_t)hz;
        rem    = (uint16_t)(scaled % (uint32_t)hz);
    }
    gLastFrame = now;
    gMillis    = ms;
    gRemainder = rem;
    return ms;
}


// --- Audio services (platform-only stub generics; every real port overrides) ---

bool jlpGenericAudioInit(void) {
    return false;
}

void jlpGenericAudioShutdown(void) {
}

void jlpGenericAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
    (void)data;
    (void)length;
    (void)loop;
}

void jlpGenericAudioStopMod(void) {
}

bool jlpGenericAudioIsPlayingMod(void) {
    return false;
}

void jlpGenericAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz) {
    (void)slot;
    (void)sample;
    (void)length;
    (void)rateHz;
}

void jlpGenericAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz) {
    (void)slot;
    (void)fill;
    (void)ctx;
    (void)rateHz;
}

void jlpGenericAudioStopSfx(uint8_t slot) {
    (void)slot;
}

void jlpGenericAudioTone(uint16_t freqHz) {
    (void)freqHz;
}

void jlpGenericAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
    (void)voice;
    (void)freqHz;
    (void)atten;
}

void jlpGenericAudioNoise(uint8_t pitch, uint8_t atten) {
    (void)pitch;
    (void)atten;
}

bool jlpGenericAudioTickRegister(jlAudioTickFnT fn, uint16_t hz) {
    (void)fn;
    (void)hz;
    return false;
}

void jlpGenericAudioCriticalEnter(void) {
}

void jlpGenericAudioCriticalExit(void) {
}

void jlpGenericAudioFrameTick(void) {
}


// --- Lifecycle / present / input services (platform-only stub generics) ---

bool jlpGenericInit(const jlConfigT *config) {
    (void)config;
    return true;
}

void jlpGenericShutdown(void) {
}

void jlpGenericPresent(const jlSurfaceT *src) {
    (void)src;
}

void jlpGenericInputInit(void) {
}

void jlpGenericInputShutdown(void) {
}

void jlpGenericInputPoll(void) {
}

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


// --- Large allocation (malloc/free default; IIgs overrides via Memory Manager) ---
void *jlpGenericBigAlloc(uint32_t bytes) {
    return malloc((size_t)bytes);
}


void jlpGenericBigFree(void *p) {
    free(p);
}
