/*
	coreaudio: audio output on MacOS X

	copyright ?-2006 by the mpg123 project - free software under the terms of the GPL 2
	see COPYING and AUTHORS files in distribution or http://mpg123.org
	initially written by Guillaume Outters
	modified by Nicholas J Humfrey to use SFIFO code
	modified by Taihei Monma to use AudioUnit and AudioConverter APIs
*/


#include "mpg123app.h"

#include <AudioUnit/AudioComponent.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
#include <errno.h>

/* Including the sfifo code locally, to avoid module linkage issues. */
#define SFIFO_STATIC
#include "sfifo.c"

#include "debug.h"

#define FIFO_DURATION		(0.5f)		/* Duration of the ring buffer in seconds */


typedef struct mpg123_coreaudio
{
	AudioConverterRef converter;
	AudioUnit outputUnit;
	int open;
	char play;
	int channels;
	int bps;
	int last_buffer;
	int play_done;
	int decode_done;

	/* Convertion buffer */
	unsigned char * buffer;
	size_t buffer_size;

	/* Ring buffer */
	sfifo_t fifo;

} mpg123_coreaudio_t;



static OSStatus playProc(AudioConverterRef inAudioConverter,
						 UInt32 *ioNumberDataPackets,
                         AudioBufferList *outOutputData,
                         AudioStreamPacketDescription **outDataPacketDescription,
                         void* inClientData)
{
	mpg123_coreaudio_t *ca = (mpg123_coreaudio_t *)inClientData;
	long n;


	if(ca->last_buffer) {
		ca->play_done = 1;
		return noErr;
	}

	for(n = 0; n < outOutputData->mNumberBuffers; n++)
	{
		unsigned int wanted = *ioNumberDataPackets * ca->channels * ca->bps;
		unsigned char *dest;
		unsigned int read;
		if(ca->buffer_size < wanted) {
			debug1("Allocating %d byte sample conversion buffer", wanted);
			ca->buffer = realloc( ca->buffer, wanted);
			ca->buffer_size = wanted;
		}
		dest = ca->buffer;

		/* Only play if we have data left */
		if ( sfifo_used( &ca->fifo ) < (int)wanted ) {
			if(!ca->decode_done) {
				warning("Didn't have any audio data in callback (buffer underflow)");
				return -1;
			}
			wanted = sfifo_used( &ca->fifo );
			ca->last_buffer = 1;
		}

		/* Read audio from FIFO to SDL's buffer */
		read = sfifo_read( &ca->fifo, dest, wanted );

		if (wanted!=read)
			warning2("Error reading from the ring buffer (wanted=%u, read=%u).\n", wanted, read);

		outOutputData->mBuffers[n].mDataByteSize = read;
		outOutputData->mBuffers[n].mData = dest;
	}

	return noErr;
}

static OSStatus convertProc(void *inRefCon, AudioUnitRenderActionFlags *inActionFlags,
                            const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
                            UInt32 inNumFrames, AudioBufferList *ioData)
{
	AudioStreamPacketDescription* outPacketDescription = NULL;
	mpg123_coreaudio_t* ca = (mpg123_coreaudio_t*)inRefCon;
	OSStatus err= noErr;

	err = AudioConverterFillComplexBuffer(ca->converter, playProc, inRefCon, &inNumFrames, ioData, outPacketDescription);

	return err;
}

static int open_coreaudio(audio_output_t *ao)
{
	mpg123_coreaudio_t* ca = (mpg123_coreaudio_t*)ao->userptr;
	UInt32 size;
	AudioComponentDescription desc;
	AudioComponent comp;
	AudioStreamBasicDescription inFormat;
	AudioStreamBasicDescription outFormat;
	AURenderCallbackStruct  renderCallback;
	Boolean outWritable;

	/* Initialize our environment */
	ca->play = 0;
	ca->buffer = NULL;
	ca->buffer_size = 0;
	ca->last_buffer = 0;
	ca->play_done = 0;
	ca->decode_done = 0;


	/* Get the default audio output unit */
	desc.componentType = kAudioUnitType_Output;
	desc.componentSubType = kAudioUnitSubType_DefaultOutput;
	desc.componentManufacturer = kAudioUnitManufacturer_Apple;
	desc.componentFlags = 0;
	desc.componentFlagsMask = 0;
	comp = AudioComponentFindNext(NULL, &desc);
	if(comp == NULL) {
		error("FindNextComponent failed");
		return(-1);
	}

	if(AudioComponentInstanceNew(comp, &(ca->outputUnit)))  {
		error("OpenAComponent failed");
		return (-1);
	}

	if(AudioUnitInitialize(ca->outputUnit)) {
		error("AudioUnitInitialize failed");
		return (-1);
	}

	/* Specify the output PCM format */
	AudioUnitGetPropertyInfo(ca->outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &size, &outWritable);
	if(AudioUnitGetProperty(ca->outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &outFormat, &size)) {
		error("AudioUnitGetProperty(kAudioUnitProperty_StreamFormat) failed");
		return (-1);
	}

	if(AudioUnitSetProperty(ca->outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &outFormat, size)) {
		error("AudioUnitSetProperty(kAudioUnitProperty_StreamFormat) failed");
		return (-1);
	}

	/* Specify the input PCM format */
	ca->channels = ao->channels;
	inFormat.mSampleRate = ao->rate;
	inFormat.mChannelsPerFrame = ao->channels;
	inFormat.mFormatID = kAudioFormatLinearPCM;
#ifdef _BIG_ENDIAN
	inFormat.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsBigEndian;
#else
	inFormat.mFormatFlags = kLinearPCMFormatFlagIsPacked;
#endif

	switch(ao->format)
	{
		case MPG123_ENC_SIGNED_16:
			inFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
			ca->bps = 2;
			break;
		case MPG123_ENC_SIGNED_8:
			inFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
			ca->bps = 1;
			break;
		case MPG123_ENC_UNSIGNED_8:
			ca->bps = 1;
			break;
		case MPG123_ENC_SIGNED_32:
			inFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
			ca->bps = 4;
			break;
		case MPG123_ENC_FLOAT_32:
			inFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
			ca->bps = 4;
			break;
		case MPG123_ENC_FLOAT_64:
			inFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
			ca->bps = 4;
			break;
	}

	inFormat.mBitsPerChannel = ca->bps << 3;
	inFormat.mBytesPerPacket = ca->bps*inFormat.mChannelsPerFrame;
	inFormat.mFramesPerPacket = 1;
	inFormat.mBytesPerFrame = ca->bps*inFormat.mChannelsPerFrame;

	/* Add our callback - but don't start it yet */
	memset(&renderCallback, 0, sizeof(AURenderCallbackStruct));
	renderCallback.inputProc = convertProc;
	renderCallback.inputProcRefCon = ao->userptr;
	if(AudioUnitSetProperty(ca->outputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &renderCallback, sizeof(AURenderCallbackStruct))) {
		error("AudioUnitSetProperty(kAudioUnitProperty_SetRenderCallback) failed");
		return(-1);
	}


	/* Open an audio I/O stream and create converter */
	if (ao->rate > 0 && ao->channels >0 ) {
		int ringbuffer_len;

		if(AudioConverterNew(&inFormat, &outFormat, &(ca->converter))) {
			error("AudioConverterNew failed");
			return(-1);
		}
		if(ao->channels == 1) {
			SInt32 channelMap[2] = { 0, 0 };
			if(AudioConverterSetProperty(ca->converter, kAudioConverterChannelMap, sizeof(channelMap), channelMap)) {
				error("AudioConverterSetProperty(kAudioConverterChannelMap) failed");
				return(-1);
			}
		}

		/* Initialise FIFO */
		ringbuffer_len = ao->rate * FIFO_DURATION * ca->bps * ao->channels;
		debug2( "Allocating %d byte ring-buffer (%f seconds)", ringbuffer_len, (float)FIFO_DURATION);
		sfifo_init( &ca->fifo, ringbuffer_len );
	}

	return(0);
}

static int get_formats_coreaudio(audio_output_t *ao)
{
	return MPG123_ENC_SIGNED_16|MPG123_ENC_SIGNED_8|MPG123_ENC_UNSIGNED_8|MPG123_ENC_SIGNED_32|MPG123_ENC_FLOAT_32|MPG123_ENC_FLOAT_64;
}

static int write_coreaudio(audio_output_t *ao, unsigned char *buf, int len)
{
	mpg123_coreaudio_t* ca = (mpg123_coreaudio_t*)ao->userptr;
	int written;

	/* If there is no room, then sleep for half the length of the FIFO */
	while (sfifo_space( &ca->fifo ) < len ) {
		usleep( (FIFO_DURATION/2) * 1000000 );
	}

	/* Store converted audio in ring buffer */
	written = sfifo_write( &ca->fifo, (char*)buf, len);
	if (written != len) {
		warning( "Failed to write audio to ring buffer" );
		return -1;
	}

	/* Start playback now that we have something to play */
	if(!ca->play)
	{
		if(AudioOutputUnitStart(ca->outputUnit)) {
			error("AudioOutputUnitStart failed");
			return(-1);
		}
		ca->play = 1;
	}

	return len;
}

static int close_coreaudio(audio_output_t *ao)
{
	mpg123_coreaudio_t* ca = (mpg123_coreaudio_t*)ao->userptr;

	if (ca) {
		ca->decode_done = 1;
		while(!ca->play_done && ca->play) usleep(10000);

		/* No matter the error code, we want to close it (by brute force if necessary) */
		AudioConverterDispose(ca->converter);
		AudioOutputUnitStop(ca->outputUnit);
		AudioUnitUninitialize(ca->outputUnit);
		AudioComponentInstanceDispose(ca->outputUnit);

	    /* Free the ring buffer */
		sfifo_close( &ca->fifo );

		/* Free the conversion buffer */
		if (ca->buffer) {
			free( ca->buffer );
			ca->buffer = NULL;
		}

	}

	return 0;
}

static void flush_coreaudio(audio_output_t *ao)
{
	mpg123_coreaudio_t* ca = (mpg123_coreaudio_t*)ao->userptr;

	/* Stop playback */
	if(AudioOutputUnitStop(ca->outputUnit)) {
		error("AudioOutputUnitStop failed");
	}
	ca->play=0;

	/* Empty out the ring buffer */
	sfifo_flush( &ca->fifo );
}

static int deinit_coreaudio(audio_output_t* ao)
{
	/* Free up memory */
	if (ao->userptr) {
		free( ao->userptr );
		ao->userptr = NULL;
	}

	/* Success */
	return 0;
}

static int init_coreaudio(audio_output_t* ao)
{
	if (ao==NULL) return -1;

	/* Set callbacks */
	ao->open = open_coreaudio;
	ao->flush = flush_coreaudio;
	ao->write = write_coreaudio;
	ao->get_formats = get_formats_coreaudio;
	ao->close = close_coreaudio;
	ao->deinit = deinit_coreaudio;

	/* Allocate memory for data structure */
	ao->userptr = malloc( sizeof( mpg123_coreaudio_t ) );
	if (ao->userptr==NULL) {
		error("failed to malloc memory for 'mpg123_coreaudio_t'");
		return -1;
	}
	memset( ao->userptr, 0, sizeof(mpg123_coreaudio_t) );

	/* Success */
	return 0;
}



/*
	Module information data structure
*/
mpg123_module_t mpg123_output_module_info = {
	/* api_version */	MPG123_MODULE_API_VERSION,
	/* name */			"coreaudio",
	/* description */	"Output audio using Mac OS X's CoreAudio.",
	/* revision */		"$Rev:$",
	/* handle */		NULL,

	/* init_output */	init_coreaudio,
};
