// Audio demo: starts the baked chip-tracker song (demoSong.song ->
// demoSong.h via tools/songbake) on entry, triggers a short digital
// SFX every time SPACE is tapped, and exits on ESC. M switches to the
// .MOD loaded from the DATA folder on the disk image (see
// make/<plat>.mk for each platform's packaging step); T switches back
// to the tracker song; P pauses/resumes tracker playback in place.
// The two layers share output hardware on the ST/Amiga, so the demo
// always stops one before starting the other.
//
// On platforms where the audio HAL is still a stub, jlAudioInit
// returns false, every audio call is a quiet no-op, and the demo runs
// as a silent input loop -- you can confirm the build links and the
// input plumbing still works without hearing anything.

#include <stdio.h>
#include <stdlib.h>

#include <joey/joey.h>

#include "demoSong.h"

// Each platform encodes the MOD differently. The IIgs build converts
// test.mod -> test.ntp via joeymod at build time and ships the .NTP;
// every other platform ships the raw .MOD. test.sfx is the same raw
// PCM blob everywhere. The tracker song needs no per-platform form at
// all -- the baked JYM1 bytes are identical on every port.
#if defined(JOEYLIB_PLATFORM_IIGS)
#  define TEST_MOD_PATH  "DATA/TEST.NTP"
#else
#  define TEST_MOD_PATH  "DATA/test.mod"
#endif
#define TEST_SFX_PATH    "DATA/test.sfx"

// Slot 3: the tracker's three voices ride SFX slots 0-2 on the IIgs,
// so the demo's one-shot SFX stays out of their way.
#define SFX_SLOT      3
#define SFX_RATE_HZ   8000

#define COLOR_BG      0
#define COLOR_HINT    1
#define COLOR_BAR     2

#define BAR_X         16
#define BAR_Y         88
#define BAR_W         (SURFACE_WIDTH - 32)
#define BAR_H         16


// Read an entire file into a freshly allocated buffer. Caller must
// free(*outBytes) once the audio engine has consumed the data.
// Returns false if the file is missing, empty, or larger than maxLen.
static bool loadFile(const char *path, uint32_t maxLen, uint8_t **outBytes, uint32_t *outLen) {
    FILE    *fp;
    long     fileSize;
    uint8_t *buf;
    size_t   readBytes;

    fp = fopen(path, "rb");
    if (fp == NULL) {
        return false;
    }
    if (fseek(fp, 0L, SEEK_END) != 0) {
        fclose(fp);
        return false;
    }
    fileSize = ftell(fp);
    if (fileSize <= 0 || (uint32_t)fileSize > maxLen) {
        fclose(fp);
        return false;
    }
    if (fseek(fp, 0L, SEEK_SET) != 0) {
        fclose(fp);
        return false;
    }
    buf = (uint8_t *)malloc((size_t)fileSize);
    if (buf == NULL) {
        fclose(fp);
        return false;
    }
    readBytes = fread(buf, 1, (size_t)fileSize, fp);
    fclose(fp);
    if (readBytes != (size_t)fileSize) {
        free(buf);
        return false;
    }
    *outBytes = buf;
    *outLen   = (uint32_t)fileSize;
    return true;
}


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_BG]   = 0x0000;
    colors[COLOR_HINT] = 0x0444;
    colors[COLOR_BAR]  = 0x00F0;

    jlPaletteSet(screen, 0, colors);
}


// Visual feedback for "audio is running, but you cannot tell on a
// stub HAL": pulse a horizontal bar between the hint color and the
// active color whenever SFX has fired this frame.
static void initialPaint(jlSurfaceT *screen, bool audioOk) {
    jlSurfaceClear(screen, COLOR_BG);
    jlFillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H,
             audioOk ? COLOR_HINT : COLOR_BG);
    jlStagePresent();
}


int main(void) {
    jlConfigT  config;
    jlSurfaceT    *screen;
    bool         audioOk;
    bool         musicPaused;
    int16_t      flashFrames;
    uint8_t     *modBytes;
    uint32_t     modLen;
    uint8_t     *sfxBytes;
    uint32_t     sfxLen;

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

    modBytes = NULL;
    sfxBytes = NULL;
    modLen   = 0;
    sfxLen   = 0;

    audioOk = jlAudioInit();
    if (audioOk) {
        // Keep the MOD bytes resident so M can (re)start it later;
        // jlAudioPlayMod copies them at play time.
        (void)loadFile(TEST_MOD_PATH, 64UL * 1024UL, &modBytes, &modLen);
        (void)loadFile(TEST_SFX_PATH, 64UL * 1024UL, &sfxBytes, &sfxLen);
    }
    jlMusicPlay(gDemoSong, (uint32_t)sizeof(gDemoSong), true);

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

    musicPaused = false;
    flashFrames = 0;
    for (;;) {
        jlWaitVBL();
        jlInputPoll();
        jlAudioFrameTick();
        if (jlKeyPressed(KEY_ESCAPE)) {
            break;
        }
        if (jlKeyPressed(KEY_SPACE) && sfxBytes != NULL) {
            jlAudioPlaySfx(SFX_SLOT, sfxBytes, sfxLen, SFX_RATE_HZ);
            flashFrames = 8;
        }
        if (jlKeyPressed(KEY_M) && modBytes != NULL) {
            jlMusicStop();
            jlAudioPlayMod(modBytes, modLen, true);
        }
        if (jlKeyPressed(KEY_T)) {
            jlAudioStopMod();
            jlMusicPlay(gDemoSong, (uint32_t)sizeof(gDemoSong), true);
        }
        if (jlKeyPressed(KEY_P)) {
            if (musicPaused) {
                jlMusicResume();
            } else {
                jlMusicPause();
            }
            musicPaused = !musicPaused;
        }

        if (flashFrames > 0) {
            jlFillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H, COLOR_BAR);
            jlStagePresent();
            flashFrames--;
            if (flashFrames == 0) {
                jlFillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H, COLOR_HINT);
                jlStagePresent();
            }
        }
    }

    jlMusicStop();
    if (audioOk) {
        jlAudioStopMod();
    }
    jlAudioShutdown();
    if (modBytes != NULL) {
        free(modBytes);
    }
    if (sfxBytes != NULL) {
        free(sfxBytes);
    }
    jlShutdown();
    return 0;
}
