#include "moonlight.hpp"

#define MAX_CHANNEL_COUNT 2
#define FRAME_SIZE 240

#define CIRCULAR_BUFFER_SIZE 32

// This code uses volatiles for synchronization between the producer and consumer side. This is
// only safe because this code executes under very specific conditions, namely that the framework
// ensures AudioPlayerSampleCallback and AudDecDecodeAndPlaySample are each only active on one thread
// at a time.

static short s_CircularBuffer[CIRCULAR_BUFFER_SIZE][FRAME_SIZE * MAX_CHANNEL_COUNT];
static int s_ReadIndex = 0;
static int s_WriteIndex = 0;

static void AudioPlayerSampleCallback(void* samples, uint32_t buffer_size, void* data) {
    // It should only ask us for complete buffers
    assert(buffer_size == FRAME_SIZE * MAX_CHANNEL_COUNT * sizeof(short));
        
    // If the indexes aren't equal, we have a sample
    if (s_WriteIndex != s_ReadIndex) {
        memcpy(samples, s_CircularBuffer[s_ReadIndex], buffer_size);
        
        // Use a full memory barrier to ensure the circular buffer is read before incrementing the index
        __sync_synchronize();
        
        // This can race with the reader in the AudDecDecodeAndPlaySample function. This is
        // not a problem because at worst, it just won't see that we've consumed this sample yet.
        s_ReadIndex = (s_ReadIndex + 1) % CIRCULAR_BUFFER_SIZE;
    }
    else {
        memset(samples, 0, buffer_size);
    }
}

int MoonlightInstance::AudDecInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int flags) {
    int rc;
    
    g_Instance->m_OpusDecoder = opus_multistream_decoder_create(opusConfig->sampleRate,
                                                                opusConfig->channelCount,
                                                                opusConfig->streams,
                                                                opusConfig->coupledStreams,
                                                                opusConfig->mapping,
                                                                &rc);
    
    g_Instance->m_AudioPlayer = pp::Audio(g_Instance, pp::AudioConfig(g_Instance, PP_AUDIOSAMPLERATE_48000, FRAME_SIZE),
                                          AudioPlayerSampleCallback, NULL);
    
    // Start playback now
    g_Instance->m_AudioPlayer.StartPlayback();
    
    return 0;
}

void MoonlightInstance::AudDecCleanup(void) {
    // Stop playback
    g_Instance->m_AudioPlayer.StopPlayback();
    
    if (g_Instance->m_OpusDecoder) {
        opus_multistream_decoder_destroy(g_Instance->m_OpusDecoder);
    }
}

void MoonlightInstance::AudDecDecodeAndPlaySample(char* sampleData, int sampleLength) {
    int decodeLen;
        
    // Check if there is space for this sample in the buffer. Again, this can race
    // but in the worst case, we'll not see the sample callback having consumed a sample.
    if (((s_WriteIndex + 1) % CIRCULAR_BUFFER_SIZE) == s_ReadIndex) {
        return;
    }
    
    decodeLen = opus_multistream_decode(g_Instance->m_OpusDecoder, (unsigned char *)sampleData, sampleLength,
                                        s_CircularBuffer[s_WriteIndex], FRAME_SIZE, 0);
    if (decodeLen > 0) {
        // Use a full memory barrier to ensure the circular buffer is written before incrementing the index
        __sync_synchronize();
        
        // This can race with the reader in the sample callback, however this is a benign
        // race since we'll either read the original value of s_WriteIndex (which is safe,
        // we just won't consider this sample) or the new value of s_WriteIndex
        s_WriteIndex = (s_WriteIndex + 1) % CIRCULAR_BUFFER_SIZE;
    }
}

AUDIO_RENDERER_CALLBACKS MoonlightInstance::s_ArCallbacks = {
    .init = MoonlightInstance::AudDecInit,
    .cleanup = MoonlightInstance::AudDecCleanup,
    .decodeAndPlaySample = MoonlightInstance::AudDecDecodeAndPlaySample,
    .capabilities = CAPABILITY_DIRECT_SUBMIT
};
