/* Coreaudio Output.
 *
 * Copyright (C) 2016 Rui Batista
 *
 * This file is part of pcaudiolib.
 *
 * pcaudiolib is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * pcaudiolib is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with pcaudiolib.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"
#include "audio_priv.h"

#ifdef HAVE_COREAUDIO_H
#include <AudioToolbox/AudioToolbox.h>
#include <CoreServices/CoreServices.h>
#include "TPCircularBuffer/TPCircularBuffer+AudioBufferList.h"
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <unistd.h>


#define min(a,b) ((a) < (b) ? (a):(b))

#define COREAUDIO_BUFFER_SIZE (1024 * 64) // 4m


struct coreaudio_object
{
	struct audio_object vtable;
	AudioStreamBasicDescription format;
	AudioUnit outputUnit;
	TPCircularBuffer circularBuffer;
	bool initialized;
	bool running;

	char *device;
	char *application_name;
	char *description;
};

#define to_coreaudio_object(object) container_of(object, struct coreaudio_object, vtable)

static OSStatus graphRenderProc(void *inRefCon,
						 AudioUnitRenderActionFlags *ioActionFlags,
						 const AudioTimeStamp *inTimeStamp,
						 UInt32 inBusNumber,
						 UInt32 inNumberFrames,
						 AudioBufferList * ioData) {
	struct coreaudio_object *self = (struct coreaudio_object *)inRefCon;
	if(! self->running){
		// fill the buffer with sielence, stop was not acnoledged yet
		memset(ioData->mBuffers[0].mData,0,(self->format).mBytesPerFrame * inNumberFrames);
		ioData->mBuffers[0].mNumberChannels = 1;
		ioData->mBuffers[0].mDataByteSize = (self->format).mBytesPerFrame * inNumberFrames;
		return noErr;
 }
	
	UInt32 available = TPCircularBufferPeek(&(self->circularBuffer), NULL, &(self->format));
	if(available == 0) {
		self->running = FALSE;
		AudioOutputUnitStop(self->outputUnit);
	}
	// copy as much data as we can from the circular buffer
	TPCircularBufferDequeueBufferListFrames(&(self->circularBuffer), &inNumberFrames, ioData, NULL, &(self->format));
	return noErr;
}

int
coreaudio_object_open(struct audio_object *object,
                       enum audio_object_format format,
                       uint32_t rate,
                       uint8_t channels)
{
	struct coreaudio_object *self = to_coreaudio_object(object);
	OSStatus err = noErr;
	if (self->initialized)
		return noErr;

	memset(&(self->format), 0, sizeof(AudioStreamBasicDescription));
	(self->format).mSampleRate = rate;
	(self->format).mChannelsPerFrame = channels;
	(self->format).mFramesPerPacket = 1;
	switch (format)
	{
		case AUDIO_OBJECT_FORMAT_S16LE:
			(self->format).mFormatID = kAudioFormatLinearPCM;
			(self->format).mBitsPerChannel = 16;
			(self->format).mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
			break;
		default:
			return -1;
	}
	// guess the remaining of the AudioBasicStreamDescription structure
	UInt32 procSize = sizeof(AudioStreamBasicDescription);
	err = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &procSize, &(self->format));
	if(err != noErr) {
		goto cleanup;
	}


	// Find default output
	AudioComponentDescription outputcd = {0,};
	outputcd.componentType = kAudioUnitType_Output;
	outputcd.componentSubType = kAudioUnitSubType_DefaultOutput;
	outputcd.componentManufacturer = kAudioUnitManufacturer_Apple;
	
	// Create a component
	AudioComponent outputComponent = AudioComponentFindNext(NULL, &outputcd);
	err = AudioComponentInstanceNew(outputComponent, &(self->outputUnit));
	if(err != noErr) {
		goto cleanup;
	}

	// set render callback
	AURenderCallbackStruct callbackStruct;
	callbackStruct.inputProcRefCon = self;
	callbackStruct.inputProc = graphRenderProc;
	size_t propSize = sizeof(AURenderCallbackStruct);
	err = AudioUnitSetProperty(self->outputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Output, 0, &callbackStruct, propSize);
	if(err != noErr) {
		goto cleanup;
	}
	
	// set output unit input format
	propSize = sizeof(AudioStreamBasicDescription);
	err = AudioUnitSetProperty(self->outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &(self->format), propSize);
	if(err != noErr) {
		goto cleanup;
	}

	err = AudioUnitInitialize(self->outputUnit);
	if(err != noErr) {
		goto cleanup;
	}
	// create a circular buffer to produce and consume audio
	TPCircularBufferInit(&(self->circularBuffer), COREAUDIO_BUFFER_SIZE);

	self->initialized = TRUE;
	return noErr;
cleanup:
	if(self->outputUnit) {
		AudioUnitUninitialize(self->outputUnit);
		AudioComponentInstanceDispose(self->outputUnit);
	}
	return err;
}

void
coreaudio_object_close(struct audio_object *object)
{
	struct coreaudio_object *self = to_coreaudio_object(object);
	if(self->initialized) {
		AudioUnitUninitialize(self->outputUnit);
		AudioComponentInstanceDispose(self->outputUnit);
		TPCircularBufferCleanup(&(self->circularBuffer));
		self->initialized = FALSE;
	}

}

void
coreaudio_object_destroy(struct audio_object *object)
{
	struct coreaudio_object *self = to_coreaudio_object(object);

}

int
coreaudio_object_drain(struct audio_object *object)
{
	struct coreaudio_object *self = to_coreaudio_object(object);
	if (!self->initialized)
		return noErr;
	while(self->running)
		usleep(10000);
	return noErr;
}

int
coreaudio_object_flush(struct audio_object *object)
{
	struct coreaudio_object *self = to_coreaudio_object(object);
	if (!self->initialized)
		return noErr;

	OSStatus err = AudioOutputUnitStop(self->outputUnit);

	TPCircularBufferClear(&(self->circularBuffer));
	self->running = FALSE;

	return err;
}

int
coreaudio_object_write(struct audio_object *object,
                        const void *data,
                        size_t bytes)
{
	struct coreaudio_object *self = to_coreaudio_object(object);
	if (!self->initialized)
		return noErr;
	bool runningAtStart = self->running;
	if(!runningAtStart) {
		TPCircularBufferClear(&(self->circularBuffer));
	}
	char *dataPtr = (char*)data;

	// copy data to our circular buffer
	// Poll while we don't have space in the buffer.
	AudioBufferList *bufferList;
	UInt32 available;
	while(bytes > 0) {
		while((!runningAtStart || self->running) && (available = TPCircularBufferGetAvailableSpace(&(self->circularBuffer), &(self->format))) == 0)
			usleep(10000);

		// check we didn't stop
		if(runningAtStart &&  (!self->running))
			return noErr;

		bufferList = TPCircularBufferPrepareEmptyAudioBufferListWithAudioFormat(&(self->circularBuffer), &(self->format), available, NULL);

		// we are writing mono/interleaved data so we have one buffer
		UInt32 bytesToWrite = min(bytes, bufferList->mBuffers[0].mDataByteSize);
		bufferList->mBuffers[0].mNumberChannels = (self->format).mChannelsPerFrame;
		bufferList->mBuffers[0].mDataByteSize = bytesToWrite;
		memcpy(bufferList->mBuffers[0].mData, dataPtr, bytesToWrite);
		TPCircularBufferProduceAudioBufferList(&(self->circularBuffer), NULL);
		dataPtr += bytesToWrite;
		bytes -= bytesToWrite;
		if(! runningAtStart) {
			OSStatus err = AudioOutputUnitStart(self->outputUnit);
			if(err == noErr) {
			self->running = TRUE;
			runningAtStart = TRUE;
		} else {
			return err;
		}
	}
	}
	return noErr;
}

const char *
coreaudio_object_strerror(struct audio_object *object,
                           int error)
{
	static char res[40];
	sprintf(res, "Runtime error %x", error);
	return res;
}

static bool
coreaudio_is_available(const char *device,
                        const char *application_name,
                        const char *description)
{
	return true;
}

struct audio_object *
create_coreaudio_object(const char *device,
                         const char *application_name,
                         const char *description)
{
	if (!coreaudio_is_available(device, application_name, description))
		return NULL;

	struct coreaudio_object *self = malloc(sizeof(struct coreaudio_object));
	if (!self)
		return NULL;

	self->vtable.open = coreaudio_object_open;
	self->vtable.close = coreaudio_object_close;
	self->vtable.destroy = coreaudio_object_destroy;
	self->vtable.write = coreaudio_object_write;
	self->vtable.drain = coreaudio_object_drain;
	self->vtable.flush = coreaudio_object_flush;
	self->vtable.strerror = coreaudio_object_strerror;

	return &self->vtable;
}

#else

struct audio_object *
create_coreaudio_object(const char *device,
                         const char *application_name,
                         const char *description)
{
	return NULL;
}

#endif
