//
//  AudioRenderer.mm
//  HighwayDash
//
//  Created by John Ryland on 18/02/2016.
//  Copyright © 2016 John Ryland. All rights reserved.
//

#import "AudioRenderer.h"
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>


struct AudioRenderer::Pimpl
{
    // private implementation details here
    AudioComponentInstance  m_audioUnit;
    int                     m_channels;
    int                     m_bytesPerChannel;
    int                     m_sampleRate;
    //AudioBuffer           m_buffer;
};


AudioRenderer::AudioRenderer()
    : m_pimpl(std::unique_ptr<Pimpl>(new Pimpl()))
{
}


AudioRenderer::~AudioRenderer()
{
    AudioUnitUninitialize(m_pimpl->m_audioUnit);
}


static void checkStatus(int status)
{
    if (status)
        printf("Audio Error %d\n", status);
}


// Callback when the audioUnit needs to fill buffers with audio data
static OSStatus playbackCallback(void *inRefCon,
                                 AudioUnitRenderActionFlags *ioActionFlags,
                                 const AudioTimeStamp *inTimeStamp,
                                 UInt32 inBusNumber,
                                 UInt32 inNumberFrames,
                                 AudioBufferList *ioData) {
    // TODO: check what happens for stereo or other cases
    for (int i=0; i < ioData->mNumberBuffers; i++)
    {
        AudioBuffer buffer = ioData->mBuffers[i];
        AudioRenderer* _this = static_cast<AudioRenderer*>(inRefCon);
        _this->renderSamples(buffer.mData, inNumberFrames * 2); // _this->m_pimpl->m_channels);
    }
    return noErr;
}


void AudioRenderer::initialize(SampleRate a_rate, BitsPerChannel a_bitsPerChannel, Channels a_channels)
{
    OSStatus status;

    // Save parameters
    m_pimpl->m_bytesPerChannel = a_bitsPerChannel / 8;
    m_pimpl->m_channels = a_channels;
    m_pimpl->m_sampleRate = a_rate;
    
    // Describe audio component
    AudioComponentDescription desc;
    desc.componentType = kAudioUnitType_Output;
    desc.componentSubType = kAudioUnitSubType_RemoteIO;
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;
    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    
    // Get component
    AudioComponent audioComponent = AudioComponentFindNext(NULL, &desc);
    
    // Get audio units
    status = AudioComponentInstanceNew(audioComponent, &m_pimpl->m_audioUnit);
    checkStatus(status);
    
    const int kOutputBus = 0;
    
    // Enable IO for playback
    UInt32 flag = 1;
    status = AudioUnitSetProperty(m_pimpl->m_audioUnit,
                                  kAudioOutputUnitProperty_EnableIO,
                                  kAudioUnitScope_Output,
                                  kOutputBus,
                                  &flag,
                                  sizeof(flag));
    checkStatus(status);
    
    // Describe format
    AudioStreamBasicDescription audioFormat;
    audioFormat.mSampleRate			= float(a_rate);
    audioFormat.mFormatID			= kAudioFormatLinearPCM;
    audioFormat.mFormatFlags		= kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
    audioFormat.mFramesPerPacket	= 1;
    audioFormat.mChannelsPerFrame	= a_channels;
    audioFormat.mBitsPerChannel		= a_bitsPerChannel;
    audioFormat.mBytesPerPacket		= m_pimpl->m_bytesPerChannel * a_channels;
    audioFormat.mBytesPerFrame		= m_pimpl->m_bytesPerChannel * a_channels;
    
    // Apply format
    status = AudioUnitSetProperty(m_pimpl->m_audioUnit,
                                  kAudioUnitProperty_StreamFormat,
                                  kAudioUnitScope_Input,
                                  kOutputBus,
                                  &audioFormat,
                                  sizeof(audioFormat));
    checkStatus(status);
    
    // Set output callback
    AURenderCallbackStruct callbackStruct;
    callbackStruct.inputProc = playbackCallback;
    callbackStruct.inputProcRefCon = this;
    status = AudioUnitSetProperty(m_pimpl->m_audioUnit,
                                  kAudioUnitProperty_SetRenderCallback,
                                  kAudioUnitScope_Global,
                                  kOutputBus,
                                  &callbackStruct,
                                  sizeof(callbackStruct));
    checkStatus(status);
    
    // Initialise
    status = AudioUnitInitialize(m_pimpl->m_audioUnit);
    checkStatus(status);
}


void AudioRenderer::start()
{
    checkStatus(AudioOutputUnitStart(m_pimpl->m_audioUnit));
}



void AudioRenderer::stop()
{
    checkStatus(AudioOutputUnitStop(m_pimpl->m_audioUnit));
}
    



    

