////////////////////////////////////////////////////////////////////////////////
// CONFIDENTIAL and PROPRIETARY software of Magewell Electronics Co., Ltd.
// Copyright (c) 2011-2019 Magewell Electronics Co., Ltd. (Nanjing)
// All rights reserved.
// This copyright notice MUST be reproduced on all authorized copies.
////////////////////////////////////////////////////////////////////////////////

#include <AudioUnit/AudioUnit.h>
#include <queue>
#include <pthread.h>

#include "MWAudioRender.h"

static const int maxBufferDuration = 100/*ms*/;
static const AudioUnitElement SPEAKER = 0;
static OSStatus audioRenderCallback(void *inRefCon,
                                    AudioUnitRenderActionFlags *ioActionFlags,
                                    const AudioTimeStamp *inTimeStamp,
                                    UInt32 inBusNumber,
                                    UInt32 inNumberFrames,
                                    AudioBufferList *ioData);

struct AudioData {
    uint8_t *pcmData;
    int  pcmSize;
    int  offset;
};

struct AudioPlaybackAudioUnit {
    uint32_t sampleRate;
    uint32_t channels;
    uint32_t bitsPerSample;
    
    AudioUnit outputAudioUnit;
    bool audioUnitRun;
    
    std::queue<AudioData*> buffer;
    int bufferSize;
    pthread_mutex_t bufferLock;
    
    void *param;
};

int MWAudioRenderCreate(uint32_t sampleRate, uint32_t channels, uint32_t bitsPerSample, void **p_handle) {
    if (p_handle == NULL) {
        return -1;
    }
    
    AudioPlaybackAudioUnit *playback = (AudioPlaybackAudioUnit*)calloc(1, sizeof(AudioPlaybackAudioUnit));
    
    playback->sampleRate = sampleRate;
    playback->channels = channels;
    playback->bitsPerSample = bitsPerSample;
    
    playback->audioUnitRun = false;
    
    pthread_mutex_init(&playback->bufferLock, NULL);
    playback->bufferSize = 0;
    
    playback->param = NULL;
    
    *p_handle = playback;
    
    return 0;
}

void MWAudioRenderDestroy(void *handle) {
    if (handle == NULL) {
        return;
    }
    
    AudioPlaybackAudioUnit *playback = (AudioPlaybackAudioUnit*)handle;
    
    pthread_mutex_destroy(&playback->bufferLock);
    free(playback);
    playback = NULL;
}

int MWAudioRenderStart(void *handle) {
    if (handle == NULL) {
        return -1;
    }
    
    AudioPlaybackAudioUnit *playback = (AudioPlaybackAudioUnit*)handle;
    
    AudioComponentDescription desc = {
        .componentType = kAudioUnitType_Output,
        .componentSubType = kAudioUnitSubType_HALOutput,
        .componentManufacturer = kAudioUnitManufacturer_Apple,
        .componentFlags = 0,
        .componentFlagsMask = 0
    };
    AudioComponent comp = AudioComponentFindNext(NULL, &desc);
    AudioComponentInstanceNew(comp, &playback->outputAudioUnit);
    
    AURenderCallbackStruct renderCallback = {audioRenderCallback, playback};
    OSStatus ret = AudioUnitSetProperty(playback->outputAudioUnit,
                                        kAudioUnitProperty_SetRenderCallback,
                                        kAudioUnitScope_Global,
                                        SPEAKER,
                                        &renderCallback,
                                        sizeof(renderCallback));
    
    ret = AudioUnitInitialize(playback->outputAudioUnit);
    
    AudioStreamBasicDescription outDesc;
    UInt32 propertySize = sizeof(outDesc);
    ret = AudioUnitGetProperty(playback->outputAudioUnit,
                               kAudioUnitProperty_StreamFormat,
                               kAudioUnitScope_Input,
                               SPEAKER,
                               &outDesc,
                               &propertySize);
                                
    outDesc.mSampleRate = playback->sampleRate;
    outDesc.mFormatID = kAudioFormatLinearPCM;
    outDesc.mFormatFlags = kAudioFormatFlagIsSignedInteger;
    outDesc.mBitsPerChannel = playback->bitsPerSample;
    outDesc.mChannelsPerFrame = playback->channels;
    outDesc.mBytesPerFrame = playback->channels * (outDesc.mBitsPerChannel >> 3);
    outDesc.mBytesPerPacket = outDesc.mBytesPerFrame;
    ret = AudioUnitSetProperty(playback->outputAudioUnit,
                               kAudioUnitProperty_StreamFormat,
                               kAudioUnitScope_Input,
                               SPEAKER,
                               &outDesc,
                               sizeof(outDesc));
    
    AudioStreamBasicDescription deviceDesc;
    propertySize = sizeof(deviceDesc);
    ret = AudioUnitGetProperty(playback->outputAudioUnit,
                               kAudioUnitProperty_StreamFormat,
                               kAudioUnitScope_Input,
                               SPEAKER,
                               &deviceDesc,
                               &propertySize);
    uint32_t blankFill = maxBufferDuration / 2 * (playback->sampleRate * playback->channels * playback->bitsPerSample/8)/1000;
    uint8_t *blankData = (uint8_t*)calloc(1, blankFill);
    if (blankData) {
        MWAudioRenderPut(handle, blankData, blankFill);
        free(blankData);
    }
    
    ret = AudioOutputUnitStart(playback->outputAudioUnit);
    playback->audioUnitRun = true;
    
    return 0;
}

void MWAudioRenderStop(void *handle) {
    if (handle == NULL) {
        return;
    }
    
    AudioPlaybackAudioUnit *playback = (AudioPlaybackAudioUnit*)handle;
    
    if (playback->audioUnitRun) {
        AudioOutputUnitStop(playback->outputAudioUnit);
        AudioUnitUninitialize(playback->outputAudioUnit);
        playback->audioUnitRun = false;
    }
    
    while (!playback->buffer.empty()) {
        AudioData *audioData = playback->buffer.front();
        free(audioData->pcmData);
        free(audioData);
        
        playback->buffer.pop();
    }
    
    std::queue<AudioData*>().swap(playback->buffer);
}

int MWAudioRenderPut(void *handle, uint8_t *frame, uint32_t frameSize) {
    AudioPlaybackAudioUnit *playback = (AudioPlaybackAudioUnit*)handle;
    
    if (playback == NULL) {
        return -1;
    }
    
    
    AudioData *audioData = (AudioData*)malloc(sizeof(AudioData));
    audioData->pcmData = (uint8_t*)malloc(frameSize);
    audioData->pcmSize = frameSize;
    audioData->offset = 0;
    memcpy(audioData->pcmData, frame, frameSize);

    pthread_mutex_lock(&playback->bufferLock);
    
    if (playback->bufferSize < (maxBufferDuration * (playback->sampleRate * playback->channels * playback->bitsPerSample/8)/1000)) { //100ms
        playback->buffer.push(audioData);
        playback->bufferSize += audioData->pcmSize;
    } else {
        free(audioData->pcmData);
        free(audioData);
        //printf("lost audio data, size: %d \n", frameSize);
    }
    
    pthread_mutex_unlock(&playback->bufferLock);
    
    return 0;
}

static OSStatus audioRenderCallback(void *inRefCon,
                                    AudioUnitRenderActionFlags *ioActionFlags,
                                    const AudioTimeStamp *inTimeStamp,
                                    UInt32 inBusNumber,
                                    UInt32 inNumberFrames,
                                    AudioBufferList *ioData) {
    AudioPlaybackAudioUnit *playback = (AudioPlaybackAudioUnit*)inRefCon;
    
    for (int i=0; i < ioData->mNumberBuffers; i++) {
        AudioBuffer buffer = ioData->mBuffers[i];
        
        pthread_mutex_lock(&playback->bufferLock);
        
        int curSize = (playback->bufferSize > buffer.mDataByteSize) ? buffer.mDataByteSize : playback->bufferSize;
        int leftSize = curSize;
        while (leftSize > 0) {
            AudioData *audioData = playback->buffer.front();
            if (audioData->pcmSize > leftSize) {
                memcpy(((uint8_t*)buffer.mData) + (buffer.mDataByteSize - leftSize), audioData->pcmData + audioData->offset, leftSize);
                
                audioData->offset += leftSize;
                audioData->pcmSize -= leftSize;
                leftSize = 0;
            } else {
                memcpy(((uint8_t*)buffer.mData) + (buffer.mDataByteSize - leftSize), audioData->pcmData + audioData->offset, audioData->pcmSize);
                leftSize -= audioData->pcmSize;
                
                playback->buffer.pop();
                free(audioData->pcmData);
                free(audioData);
            }
        }

        if (buffer.mDataByteSize > curSize) {
            memset(((uint8_t*)buffer.mData) + curSize, 0, buffer.mDataByteSize - curSize);
            //printf("fill silence audio, size: %d \n", buffer.mDataByteSize - curSize);
        }
        
        playback->bufferSize -= curSize;
        
        pthread_mutex_unlock(&playback->bufferLock);
    }
    
    return noErr;
}
