/*
 * cblstream — потоковое чтение с диска ОДНОВРЕМЕННО с воспроизведением
 * через CBL.  Расчитан на быстрый носитель (HDD) — на floppy
 * захлёбывается (см. tests/cblwav, где поэтому весь клип грузится в
 * RAM заранее).  speech30.pcm — ~39.6 c речи, моно 8-бит unsigned,
 * 21.875 кГц.
 *
 * ЕДИНСТВЕННЫЙ случай из трёх cbl-тестов, где приложению НУЖНО своё
 * кольцо (2026-07-07): read() — ESTEX-вызов, а fill()-callback CBL
 * зовётся ИЗ ISR, где ESTEX/BIOS под запретом (см. cbl.h).  Поэтому
 * диск читается ТОЛЬКО в main (в собственное кольцо ring[]), а
 * fill_stream() лишь копирует уже готовые байты из ring в стейджинг и
 * пропихивает cbl_push_otir() — сам с диском не работает.
 */

#include <stdio.h>
#include <string.h>
#include <conio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <cbl.h>
#include <irq.h>

#define RING_SIZE 2048u
#define READ_CHUNK 1024u

static uint8_t  ring[RING_SIZE];
static uint16_t r_rd, r_wr, r_count;    /* r_count: main под IRQ_DISABLE, fill() уже в ISR */
static uint8_t  staging[128];           /* блок 8-бит моно */
static uint16_t ring_in = 0;
static uint16_t ring_border = 0;

static uint16_t ring_free(void)
{
    IRQ_DISABLE();
    uint16_t c = r_count;
    IRQ_ENABLE();
    return (uint16_t)(RING_SIZE - c);
}

/* Вызывать ТОЛЬКО из main. */
static void ring_push(const void *src, uint16_t n)
{
    const uint8_t *s = src;
    while (n) {
        uint16_t tail  = (uint16_t)(RING_SIZE - r_wr);
        uint16_t chunk = n < tail ? n : tail;
        memcpy(ring + r_wr, s, chunk);
        r_wr = (uint16_t)((r_wr + chunk) % RING_SIZE);
        IRQ_DISABLE();
        r_count += chunk;
        IRQ_ENABLE();
        s += chunk;
        n = (uint16_t)(n - chunk);
    }
}

/* Зовётся ИЗ ISR — только копия из уже готового ring, никакого ESTEX. */
static int fill_stream(uint16_t n)
{
    if (r_count < n) return 0;

    uint16_t tail = (uint16_t)(RING_SIZE - r_rd);
    if (tail >= n) {
        // memcpy(staging, ring + r_rd, n);
        cbl_push_otir(ring + r_rd, n);
        ring_in++;
    } else {
        memcpy(staging, ring + r_rd, tail);
        memcpy(staging + tail, ring, (uint16_t)(n - tail));
        cbl_push_otir(staging, n);
        ring_border++;
    }
    r_rd    = (uint16_t)((r_rd + n) % RING_SIZE);
    r_count = (uint16_t)(r_count - n);   /* уже внутри ISR — IRQ и так замаскированы */

    return 1;
}

int main(void)
{
    int fd = open("speech30.pcm", O_RDONLY);
    if (fd < 0) {
        printf("open(speech30.pcm) failed: errno=%d\n", errno);
        return 1;
    }

    static uint8_t readbuf[READ_CHUNK];
    uint32_t played = 0;
    uint8_t  eof = 0;

    /* прогреть кольцо реальными данными ДО cbl_open()/puts() — иначе
     * пауза тишины на старте (тот же приём, что в cbltest.c/cblwav.c) */
    while (!eof && ring_free() >= sizeof(readbuf)) {
        int got = read(fd, readbuf, sizeof(readbuf));
        if (got <= 0) { eof = 1; break; }
        ring_push(readbuf, (uint16_t)got);
        played += (uint32_t)got;
    }

    if (cbl_open(CBL_FREQ_21K9, CBL_FMT_MONO8, CBL_PUMP_OTIR,
                 CBL_UNDERRUN_APP, fill_stream) != 0) {
        printf("cbl_open failed: errno=%d\n", errno);
        close(fd);
        return 1;
    }

    puts("streaming speech30.pcm from disk (21.875 kHz, mono 8-bit)...");

    while (!eof) {
        if (ring_free() >= sizeof(readbuf)) {
            int got = read(fd, readbuf, sizeof(readbuf));
            if (got <= 0) { eof = 1; break; }
            ring_push(readbuf, (uint16_t)got);
            played += (uint32_t)got;
        } else {
            __asm__("halt");   /* ждём, пока ISR подъест кольцо */
        }
    }
    close(fd);
    printf("streamed %lu bytes from disk\n", played);

    /* Ждём "почти полного" опустошения, а не точного RING_SIZE: если
     * общая длина потока не кратна 128, необслуженный хвост (<128 Б,
     * тот же квирк, что и в старом кольце libc — см. cbl.h) навечно
     * оставит r_count>0, и точное ожидание никогда бы не завершилось. */
    while (ring_free() < RING_SIZE - 127u) __asm__("halt");

    printf("requests: %u, underruns: %u (expect 0 during play)\n",
           cbl_requests(), cbl_underruns());
    printf("ring: in: %u, border: %u\n",
           ring_in, ring_border);
    cbl_close();

    puts("press any key (keyboard must work)...");
    getch();
    puts("cblstream done.");
    return 0;
}
