#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <unistd.h>

#include <3ds.h>

// My shoddy attempt at making a streamed yet simple audio system that runs off DSP so it works in-emulator. Based off of the streaming example code from DevKitPro

// Define information about playback
#define SAMPLERATE 48000
#define SAMPLESPERBUF (SAMPLERATE/15)
#define BYTESPERSAMPLE 2

// Function called to pass new information along to the audio buffer.
void fill_buffer(void *audioBuffer,size_t offset, size_t size) {
//----------------------------------------------------------------------------

// Gives us a simple reference to the audioBuffer we need to modify.
u32 *dest = (u32*)audioBuffer;

// Opens raw file, seeks to the starting offset, and read all the data into the buff. Then close it.
FILE *file = fopen("monkeys.raw", "rb");
fseek(file, (offset)*4, SEEK_SET);
s32 buff[size];
fread(buff, 4, size, file);
fclose(file);

// Convert all s16 samples into u32s. I don't know why we need to do this, but until I figure out why, this is what we do.
for (int i=0; i<size; i++) {
	s32 sample = buff[i];

	dest[i] = (sample<<16) | (sample & 0xffff);
}

// Refill the datacache in the DSP with the updated audioBuffer.
DSP_FlushDataCache(audioBuffer,size);

}

// Main function
int main(int argc, char **argv) {
//----------------------------------------------------------------------------

	ndspWaveBuf waveBuf[2];

	// Turn on graphics drawing
	gfxInitDefault();

	// Set up console for debug
	consoleInit(GFX_TOP, NULL);

	// Create audio buffer
	u32 *audioBuffer = (u32*)linearAlloc(SAMPLESPERBUF*BYTESPERSAMPLE*2);

	bool fillBlock = false;

	// Start up DSP
	ndspInit();

	// Set DSP mode to allow for stereo output
	ndspSetOutputMode(NDSP_OUTPUT_STEREO);

	// Set interpolation between samples
	ndspChnSetInterp(0, NDSP_INTERP_LINEAR);
	// Set the sample rate for channel 0
	ndspChnSetRate(0, SAMPLERATE);
	// Set channel 0 to expect stereo audio as PCM data, signed 16-bit.
	ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);

	// Unsure yet. 12 is the number of DSP channels. Probably something to do with that.
	float mix[12];
	memset(mix, 0, sizeof(mix));
	mix[0] = 1.0;
	mix[1] = 1.0;
	ndspChnSetMix(0, mix);

	// Sets up info in the two wave buffers. Makes them large enough for the samples per buff.
	memset(waveBuf,0,sizeof(waveBuf));
	waveBuf[0].data_vaddr = &audioBuffer[0];
	waveBuf[0].nsamples = SAMPLESPERBUF;
	waveBuf[1].data_vaddr = &audioBuffer[SAMPLESPERBUF];
	waveBuf[1].nsamples = SAMPLESPERBUF;

	// Setup the timer to count how many samples in we are each cycle of buffer filling.
	size_t stream_offset = 0;

	// Fill the buffer with initial data before entering main loop
	fill_buffer(audioBuffer,stream_offset, SAMPLESPERBUF * 2);

	// Update offset
	stream_offset += SAMPLESPERBUF;

	ndspChnWaveBufAdd(0, &waveBuf[0]);
	ndspChnWaveBufAdd(0, &waveBuf[1]);

	// Idk if this helps but it lets the hardware run faster if it can.
	osSetSpeedupEnable(true);

	// Main loop
	while(aptMainLoop()) {

		gfxSwapBuffers();
		gfxFlushBuffers();
		gspWaitForVBlank();

		// Get HID data
		hidScanInput();
		u32 kDown = hidKeysDown();

		// Exit on pressed start
		if (kDown & KEY_START)
			break; // break in order to return to hbmenu

		// Check if buffer is done playing, fill buffer. Need to switch between two buffers, which is done using fillBlock, as one has to be playing while the other fills.
		if (waveBuf[fillBlock].status == NDSP_WBUF_DONE) {

			fill_buffer(waveBuf[fillBlock].data_pcm16, stream_offset, waveBuf[fillBlock].nsamples);

			ndspChnWaveBufAdd(0, &waveBuf[fillBlock]);
			stream_offset += waveBuf[fillBlock].nsamples;

			fillBlock = !fillBlock;
		}
	}
	// All exit code is below
	// End DSP
	ndspExit();

	// Idk
	linearFree(audioBuffer);

	// End graphics
	gfxExit();
	// Exit
	return 0;
}
