//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include <stdio.h>
#include <AudioToolbox/AudioToolbox.h>
#include "AudioUnitIO.h"
#include "AudioInputAudioUnit.h"
#include "AudioOutputAudioUnit.h"
#include "../../logging.h"
#include "../../VoIPController.h"

#define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE(msg": OSStatus=%d", (int)res); return; }
#define BUFFER_SIZE 960 // 20 ms

#define kOutputBus 0
#define kInputBus 1

using namespace tgvoip;
using namespace tgvoip::audio;

int AudioUnitIO::refCount=0;
AudioUnitIO* AudioUnitIO::sharedInstance=NULL;
bool AudioUnitIO::haveAudioSession=false;

AudioUnitIO::AudioUnitIO(){
	input=NULL;
	output=NULL;
	configured=false;
	inputEnabled=false;
	outputEnabled=false;
	inBufferList.mBuffers[0].mData=malloc(10240);
	inBufferList.mBuffers[0].mDataByteSize=10240;
	inBufferList.mNumberBuffers=1;
#ifdef TGVOIP_USE_AUDIO_SESSION
	if(haveAudioSession)
		ProcessAudioSessionAcquired();
#else
	haveAudioSession=true;
	ProcessAudioSessionAcquired();
#endif
}

AudioUnitIO::~AudioUnitIO(){
	if(runFakeIO){
		runFakeIO=false;
		join_thread(fakeIOThread);
	}
	AudioOutputUnitStop(unit);
	AudioUnitUninitialize(unit);
	AudioComponentInstanceDispose(unit);
	free(inBufferList.mBuffers[0].mData);
	haveAudioSession=false;
}

void AudioUnitIO::ProcessAudioSessionAcquired(){
	OSStatus status;
	AudioComponentDescription desc;
	AudioComponent inputComponent;
	desc.componentType = kAudioUnitType_Output;
#if TARGET_OS_IPHONE
	//desc.componentSubType = kAudioUnitSubType_RemoteIO;
	desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
#else
	desc.componentSubType = kAudioUnitSubType_HALOutput;
#endif
	desc.componentFlags = 0;
	desc.componentFlagsMask = 0;
	desc.componentManufacturer = kAudioUnitManufacturer_Apple;
	inputComponent = AudioComponentFindNext(NULL, &desc);
	status = AudioComponentInstanceNew(inputComponent, &unit);
	
	if(configured)
		ActuallyConfigure(cfgSampleRate, cfgBitsPerSample, cfgChannels);
}

AudioUnitIO* AudioUnitIO::Get(){
	if(refCount==0){
		sharedInstance=new AudioUnitIO();
	}
	refCount++;
	assert(refCount>0);
	return sharedInstance;
}

void AudioUnitIO::Release(){
	refCount--;
	assert(refCount>=0);
	if(refCount==0){
		delete sharedInstance;
		sharedInstance=NULL;
	}
}

void AudioUnitIO::AudioSessionAcquired(){
	haveAudioSession=true;
	if(sharedInstance)
		sharedInstance->ProcessAudioSessionAcquired();
}

void AudioUnitIO::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){
	if(configured)
		return;
	
#ifdef TGVOIP_USE_AUDIO_SESSION
	runFakeIO=true;
	start_thread(fakeIOThread, AudioUnitIO::StartFakeIOThread, this);
	set_thread_priority(fakeIOThread, get_thread_max_priority());
#endif
	
	if(haveAudioSession){
		ActuallyConfigure(sampleRate, bitsPerSample, channels);
	}else{
		cfgSampleRate=sampleRate;
		cfgBitsPerSample=bitsPerSample;
		cfgChannels=channels;
	}
	
	configured=true;
}

void AudioUnitIO::ActuallyConfigure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){
	UInt32 flag=1;
	OSStatus status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
	CHECK_AU_ERROR(status, "Error enabling AudioUnit output");
	status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
	CHECK_AU_ERROR(status, "Error enabling AudioUnit input");
	
	flag=0;
	status=AudioUnitSetProperty(unit, kAUVoiceIOProperty_VoiceProcessingEnableAGC, kAudioUnitScope_Global, kInputBus, &flag, sizeof(flag));
	CHECK_AU_ERROR(status, "Error disabling AGC");
	
	AudioStreamBasicDescription audioFormat;
	audioFormat.mSampleRate			= sampleRate;
	audioFormat.mFormatID			= kAudioFormatLinearPCM;
	audioFormat.mFormatFlags		= kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
	audioFormat.mFramesPerPacket	= 1;
	audioFormat.mChannelsPerFrame	= channels;
	audioFormat.mBitsPerChannel		= bitsPerSample*channels;
	audioFormat.mBytesPerPacket		= bitsPerSample/8*channels;
	audioFormat.mBytesPerFrame		= bitsPerSample/8*channels;
	
	status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, sizeof(audioFormat));
	CHECK_AU_ERROR(status, "Error setting output format");
	status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &audioFormat, sizeof(audioFormat));
	CHECK_AU_ERROR(status, "Error setting input format");
	
	AURenderCallbackStruct callbackStruct;
	
	callbackStruct.inputProc = AudioUnitIO::BufferCallback;
	callbackStruct.inputProcRefCon = this;
	status = AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, sizeof(callbackStruct));
	CHECK_AU_ERROR(status, "Error setting output buffer callback");
	status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &callbackStruct, sizeof(callbackStruct));
	CHECK_AU_ERROR(status, "Error setting input buffer callback");
	
	status = AudioUnitInitialize(unit);
	CHECK_AU_ERROR(status, "Error initializing AudioUnit");
	status=AudioOutputUnitStart(unit);
	CHECK_AU_ERROR(status, "Error starting AudioUnit");
}

OSStatus AudioUnitIO::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
	((AudioUnitIO*)inRefCon)->BufferCallback(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData);
	return noErr;
}

void AudioUnitIO::BufferCallback(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 bus, UInt32 numFrames, AudioBufferList *ioData){
	runFakeIO=false;
	if(bus==kOutputBus){
		if(output && outputEnabled){
			output->HandleBufferCallback(ioData);
		}else{
			memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize);
		}
	}else if(bus==kInputBus){
		if(input && inputEnabled){
			inBufferList.mBuffers[0].mDataByteSize=10240;
			AudioUnitRender(unit, ioActionFlags, inTimeStamp, bus, numFrames, &inBufferList);
			input->HandleBufferCallback(&inBufferList);
		}
	}
}

void AudioUnitIO::AttachInput(AudioInputAudioUnit *i){
	assert(input==NULL);
	input=i;
}

void AudioUnitIO::AttachOutput(AudioOutputAudioUnit *o){
	assert(output==NULL);
	output=o;
}

void AudioUnitIO::DetachInput(){
	assert(input!=NULL);
	input=NULL;
	inputEnabled=false;
}

void AudioUnitIO::DetachOutput(){
	assert(output!=NULL);
	output=NULL;
	outputEnabled=false;
}

void AudioUnitIO::EnableInput(bool enabled){
	inputEnabled=enabled;
}

void AudioUnitIO::EnableOutput(bool enabled){
	outputEnabled=enabled;
}

void* AudioUnitIO::StartFakeIOThread(void *arg){
	((AudioUnitIO*)arg)->RunFakeIOThread();
	return NULL;
}

void AudioUnitIO::RunFakeIOThread(){
	double neededDataDuration=0;
	double prevTime=VoIPController::GetCurrentTime();
	while(runFakeIO){
		double t=VoIPController::GetCurrentTime();
		neededDataDuration+=t-prevTime;
		prevTime=t;
		while(neededDataDuration>=0.020){
			if(output && outputEnabled){
				inBufferList.mBuffers[0].mDataByteSize=960*2;
				output->HandleBufferCallback(&inBufferList);
			}
			if((output && outputEnabled) || (input && inputEnabled)){
				memset(inBufferList.mBuffers[0].mData, 0, 960*2);
			}
			if(input && inputEnabled){
				inBufferList.mBuffers[0].mDataByteSize=960*2;
				input->HandleBufferCallback(&inBufferList);
			}
			neededDataDuration-=0.020;
		}
		usleep(5000);
	}
	LOGD("FakeIO thread exiting");
}

