/*
	openal.c: audio output on OpenAL

	copyright 1995-2009 by the mpg123 project - free software under the terms of the LGPL 2.1
	see COPYING and AUTHORS files in distribution or http://mpg123.org
	initially written by Taihei Monma
*/


#include "mpg123app.h"

#ifdef OPENAL_SUBDIR_OPENAL
	#include <OpenAL/al.h>
	#include <OpenAL/alc.h>
#elif defined(OPENAL_SUBDIR_AL)
	#include <AL/al.h>
	#include <AL/alc.h>
#else
	#include <al.h>
	#include <alc.h>
#endif
#include <errno.h>
#include <unistd.h>

#include "debug.h"

#define NUM_BUFFERS 16

#ifndef AL_FORMAT_MONO_FLOAT32
#define AL_FORMAT_MONO_FLOAT32 0x10010
#endif
#ifndef AL_FORMAT_STEREO_FLOAT32
#define AL_FORMAT_STEREO_FLOAT32 0x10011
#endif

typedef struct
{
	ALCdevice *device;
	ALCcontext *context;
	ALuint source, buffer;
	ALenum format;
	ALsizei rate;
} mpg123_openal_t;


static int open_openal(audio_output_t *ao)
{
	mpg123_openal_t* al = (mpg123_openal_t*)ao->userptr;

	al->device = alcOpenDevice(NULL);
	al->context = alcCreateContext(al->device, NULL);
	alcMakeContextCurrent(al->context);
	alGenSources(1, &al->source);

	al->rate = ao->rate;
	if(ao->format == MPG123_ENC_SIGNED_16 && ao->channels == 2) al->format = AL_FORMAT_STEREO16;
	else if(ao->format == MPG123_ENC_SIGNED_16 && ao->channels == 1) al->format = AL_FORMAT_MONO16;
	else if(ao->format == MPG123_ENC_UNSIGNED_8 && ao->channels == 2) al->format = AL_FORMAT_STEREO8;
	else if(ao->format == MPG123_ENC_UNSIGNED_8 && ao->channels == 1) al->format = AL_FORMAT_MONO8;
	else if(ao->format == MPG123_ENC_FLOAT_32 && ao->channels == 2) al->format = AL_FORMAT_STEREO_FLOAT32;
	else if(ao->format == MPG123_ENC_FLOAT_32 && ao->channels == 1) al->format = AL_FORMAT_MONO_FLOAT32;
	
	return 0;
}

static int get_formats_openal(audio_output_t *ao)
{
	return MPG123_ENC_SIGNED_16|MPG123_ENC_UNSIGNED_8|((alIsExtensionPresent((ALchar*)"AL_EXT_float32") == AL_TRUE) ? MPG123_ENC_FLOAT_32 : 0);
}

static int write_openal(audio_output_t *ao, unsigned char *buf, int len)
{
	ALint state, n;
	mpg123_openal_t* al = (mpg123_openal_t*)ao->userptr;

	alGetSourcei(al->source, AL_BUFFERS_QUEUED, &n);
	if(n < NUM_BUFFERS)
	{
		alGenBuffers(1, &al->buffer);
	}
	else
	{
		alGetSourcei(al->source, AL_SOURCE_STATE, &state);
		if(state != AL_PLAYING)
		{
			alSourcePlay(al->source);
		}
		while(alGetSourcei(al->source, AL_BUFFERS_PROCESSED, &n), n == 0)
		{	
			usleep(10000);
		}
		alSourceUnqueueBuffers(al->source, 1, &al->buffer);
	}
	
	alBufferData(al->buffer, al->format, buf, len, al->rate);
	alSourceQueueBuffers(al->source, 1, &al->buffer);
	
	return len;
}

static int close_openal(audio_output_t *ao)
{
	ALint state, n;
	mpg123_openal_t* al = (mpg123_openal_t*)ao->userptr;

	if (al)
	{
		/* wait until all buffers are consumed */
		while(alGetSourcei(al->source, AL_SOURCE_STATE, &state), state == AL_PLAYING)
		{
			usleep(10000);
		}
		/* free all processed buffers */
		while(alGetSourcei(al->source, AL_BUFFERS_PROCESSED, &n), n > 0)
		{
			alSourceUnqueueBuffers(al->source, 1, &al->buffer);
			alDeleteBuffers(1, &al->buffer);
		}
		alDeleteSources(1, &al->source);
		alcMakeContextCurrent(NULL);
		alcDestroyContext(al->context);
		alcCloseDevice(al->device);
	}
	
	return 0;
}

static void flush_openal(audio_output_t *ao)
{
	ALint n;
	mpg123_openal_t* al = (mpg123_openal_t*)ao->userptr;

	if (al)
	{
		/* stop playing and flush all buffers */
		alSourceStop(al->source);
		while(alGetSourcei(al->source, AL_BUFFERS_PROCESSED, &n), n > 0)
		{
			alSourceUnqueueBuffers(al->source, 1, &al->buffer);
			alDeleteBuffers(1, &al->buffer);
		}
	}
}

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

	/* Success */
	return 0;
}

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

	/* Set callbacks */
	ao->open = open_openal;
	ao->flush = flush_openal;
	ao->write = write_openal;
	ao->get_formats = get_formats_openal;
	ao->close = close_openal;
	ao->deinit = deinit_openal;

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

	/* Success */
	return 0;
}


/* 
	Module information data structure
*/
mpg123_module_t mpg123_output_module_info = {
	/* api_version */	MPG123_MODULE_API_VERSION,
	/* name */			"openal",
	/* description */	"Output audio using OpenAL.",
	/* revision */		"$Rev:$",
	/* handle */		NULL,
	
	/* init_output */	init_openal,
};


