#include <proto/exec.h>
#include <proto/utility.h>
#include <clib/alib_protos.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <devices/trackdisk.h>
#include <proto/exec.h>
#include <proto/dos.h>

#define DRIVE_UNIT      1           /* DF1: */
#define TRACK_SIZE      32766       // Max out the DMA
#define CHIP_BUF_SIZE   TRACK_SIZE

#define NUM_BUFFERS 2

// Allocate CHIP memory buffer 
UBYTE *chipBuf[NUM_BUFFERS];
struct IOExtTD * diskIO[NUM_BUFFERS];
    
// Helper to encode and fire a buffer async 
void sendBuffer(int slot, LONG bytes) {
    UWORD *wordBuf = (UWORD *)chipBuf[slot];
    LONG wordCount = bytes / 2;
    for (LONG i = 0; i < wordCount; i++)
        wordBuf[i] = wordBuf[i] | 0x8080;

    diskIO[slot]->iotd_Req.io_Command = ETD_RAWWRITE;
    diskIO[slot]->iotd_Req.io_Flags   = 0;
    diskIO[slot]->iotd_Count          = 0xFFFFFFFF;
    diskIO[slot]->iotd_Req.io_Data    = chipBuf[slot];
    diskIO[slot]->iotd_Req.io_Length  = bytes;
    diskIO[slot]->iotd_Req.io_Offset  = 0;
    SendIO((struct IORequest *)diskIO[slot]);
}

// Entry of our code
int main(int argc, char **argv) 
{
	struct MsgPort      *diskPort = NULL;
    BPTR                 fh       = NULL;
    LONG                 err      = 0;
    LONG                 track    = 0;
    LONG                 bytesRead;
    LONG                 totalBytes = 0;    
    struct IOExtTD *diskIOSingle;

    // Initialize SysBase from absolute address 4
    SysBase = *((struct ExecBase **)4);
    
    // Open DOS library 
    DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 0);
    if (!DOSBase) return 1;

    Printf("FloppyAudio - Raw audio streamer\n");
    Printf("Opening trackdisk.device unit %ld...\n", (LONG)DRIVE_UNIT);
    

    diskPort = CreateMsgPort();
    if (!diskPort) { 
        Printf("ERROR: Failed to create message port\n");
        err = 1; goto doneit; 
    }
   
    // Open trackdisk.device unit 1 (DF1:)
    if (OpenDevice(TD_NAME, DRIVE_UNIT, (struct IORequest *)diskIOSingle, 0)) {
        Printf("ERROR: Failed to open trackdisk.device unit %ld\n", (LONG)DRIVE_UNIT);
        err = 3; goto doneit;
    }
    Printf("trackdisk.device opened OK\n");

    // Allocate both buffers and IO requests 
    for (int i = 0; i < NUM_BUFFERS; i++) {
        chipBuf[i] = AllocMem(CHIP_BUF_SIZE, MEMF_CHIP | MEMF_CLEAR);
        Printf("2");
        diskIO[i] = (struct IOExtTD *)CreateIORequest(diskPort, sizeof(struct IOExtTD));
        Printf("3");
        diskIO[i]->iotd_Req.io_Device = diskIOSingle->iotd_Req.io_Device;
        Printf("4");
        diskIO[i]->iotd_Req.io_Unit   = diskIOSingle->iotd_Req.io_Unit;
    }
    Printf("CHIP buffers allocated");

    // Open raw audio file 
    Printf("Opening audio.raw...\n");
    fh = Open("8bitpcm.raw", MODE_OLDFILE);
    if (!fh) { 
        Printf("ERROR: Failed to open audio.raw\n");
        err = 5; goto doneit; 
    }
    Printf("File opened OK, streaming...\n");

    // Load entire file into chip RAM upfront 
    LONG fileSize = 1024*1024;  // 1M of it
    UBYTE *audioData = AllocMem(fileSize, MEMF_CHIP | MEMF_CLEAR);
    Read(fh, audioData, fileSize);

    // Pre-encode all MSB sync bits 
    UWORD *wordBuf = (UWORD *)audioData;
    LONG wordCount = fileSize / 2;
    for (LONG i = 0; i < wordCount; i++)
        wordBuf[i] = wordBuf[i] | 0x0101;

    // Stream directly from chip RAM 
    UBYTE *ptr = audioData;
    UBYTE *end = audioData + fileSize;

    // Prime both buffers before the loop 
    diskIO[0]->iotd_Req.io_Command = ETD_RAWWRITE;
    diskIO[0]->iotd_Req.io_Flags   = 0;
    diskIO[0]->iotd_Count          = 0xFFFFFFFF;
    diskIO[0]->iotd_Req.io_Data    = ptr;
    diskIO[0]->iotd_Req.io_Length  = CHIP_BUF_SIZE;
    diskIO[0]->iotd_Req.io_Offset  = 0;
    SendIO((struct IORequest *)diskIO[0]);
    ptr += CHIP_BUF_SIZE;

    diskIO[1]->iotd_Req.io_Command = ETD_RAWWRITE;
    diskIO[1]->iotd_Req.io_Flags   = 0;
    diskIO[1]->iotd_Count          = 0xFFFFFFFF;
    diskIO[1]->iotd_Req.io_Data    = ptr;
    diskIO[1]->iotd_Req.io_Length  = CHIP_BUF_SIZE;
    diskIO[1]->iotd_Req.io_Offset  = 0;
    SendIO((struct IORequest *)diskIO[1]);
    ptr += CHIP_BUF_SIZE;

    // PRevent DF0 being accessed while we do this
    struct MsgPort  *dummyPort;
    struct IOExtTD  *dummyIO;

    dummyPort = CreateMsgPort();
    dummyIO   = (struct IOExtTD *)CreateIORequest(dummyPort, sizeof(struct IOExtTD));

    // Open DF0: just to hold it 
    OpenDevice(TD_NAME, 0, (struct IORequest *)dummyIO, 0);

    // CMD_STOP queues a stop - prevent *any* otther use of the floppy bus
    dummyIO->iotd_Req.io_Command = CMD_STOP;
    DoIO((struct IORequest *)dummyIO);

    int cur = 0;
    int next = 1;

    while (ptr < end) {
        LONG remaining = end - ptr;
        LONG chunkSize = (remaining > CHIP_BUF_SIZE) ? CHIP_BUF_SIZE : remaining;

        // Wait for oldest request 
        WaitIO((struct IORequest *)diskIO[cur]);

        if (diskIO[cur]->iotd_Req.io_Error) {
            Printf("ERROR: %ld\n", (LONG)diskIO[cur]->iotd_Req.io_Error);
            break;
        }

        // Requeue it immediately for the next chunk 
        diskIO[cur]->iotd_Req.io_Command = ETD_RAWWRITE;
        diskIO[cur]->iotd_Req.io_Flags   = 0;
        diskIO[cur]->iotd_Count          = 0xFFFFFFFF;
        diskIO[cur]->iotd_Req.io_Data    = ptr;
        diskIO[cur]->iotd_Req.io_Length  = chunkSize;
        diskIO[cur]->iotd_Req.io_Offset  = 0;
        SendIO((struct IORequest *)diskIO[cur]);
        ptr += chunkSize;

        cur  ^= 1;
        next ^= 1;
    }

    WaitIO((struct IORequest *)diskIO[cur]);
    WaitIO((struct IORequest *)diskIO[next]);
    FreeMem(audioData, fileSize);

    // Release DF0: 
    dummyIO->iotd_Req.io_Command = CMD_START;
    DoIO((struct IORequest *)dummyIO);

    CloseDevice((struct IORequest *)dummyIO);
    DeleteIORequest((struct IORequest *)dummyIO);
    DeleteMsgPort(dummyPort);

    Printf("\nStreaming complete. %ld tracks, %ld bytes total.\n", track, totalBytes);

doneit:
    if (fh)      Close(fh);
    if (diskIOSingle) {
        CloseDevice((struct IORequest *)diskIOSingle);
        DeleteIORequest((struct IORequest *)diskIOSingle);
    }
    if (diskPort) DeleteMsgPort(diskPort);

    if (err) Printf("Exited with error code %ld\n", err);
    return err;
}

__attribute__((used)) __attribute__((section(".text.unlikely"))) void _start(int argc, char **argv)
{
	main(argc,argv);
}