// === LOGALL writes very detailed informations to vba-trace.log ===
//#define LOGALL

#ifndef NO_OAL

// for gopts
// also, wx-related
#include "wxvbam.h"

// Interface
#include "../common/SoundDriver.h"

// OpenAL
#include "openal.h"

// Internals
#include "../gba/Sound.h"
#include "../gba/Globals.h" // for 'speedup' and 'synchronize'

// Debug
#include <assert.h>
#define ASSERT_SUCCESS   assert( AL_NO_ERROR == ALFunction.alGetError() )

#ifndef LOGALL
// replace logging functions with comments
#ifdef winlog
#undef winlog
#endif
#define winlog //
#define debugState() //
#endif

struct OPENALFNTABLE;

class OpenAL : public SoundDriver
{
public:
	OpenAL();
	virtual ~OpenAL();

	static wxDynamicLibrary Lib;
	static bool LoadOAL();
	static bool GetDevices(wxArrayString &names, wxArrayString &ids);
	bool init(long sampleRate);   // initialize the sound buffer queue
	void pause();  // pause the secondary sound buffer
	void reset();  // stop and reset the secondary sound buffer
	void resume(); // play/resume the secondary sound buffer
	void write(u16 * finalWave, int length);  // write the emulated sound to a sound buffer

private:
	static OPENALFNTABLE  ALFunction;
	bool           initialized;
	bool           buffersLoaded;
	ALCdevice     *device;
	ALCcontext    *context;
	ALuint        *buffer;
	ALuint         tempBuffer;
	ALuint         source;
	int            freq;
	int			   soundBufferLen;

#ifdef LOGALL
	void           debugState();
#endif
};

OpenAL::OpenAL()
{
	initialized = false;
	buffersLoaded = false;
	device = NULL;
	context = NULL;
	buffer = (ALuint*)malloc( gopts.audio_buffers * sizeof( ALuint ) );
	memset( buffer, 0, gopts.audio_buffers * sizeof( ALuint ) );
	tempBuffer = 0;
	source = 0;
}


OpenAL::~OpenAL()
{
	if( !initialized ) return;

	ALFunction.alSourceStop( source );
	ASSERT_SUCCESS;

	ALFunction.alSourcei( source, AL_BUFFER, 0 );
	ASSERT_SUCCESS;

	ALFunction.alDeleteSources( 1, &source );
	ASSERT_SUCCESS;

	ALFunction.alDeleteBuffers( gopts.audio_buffers, buffer );
	ASSERT_SUCCESS;

	free( buffer );

	ALFunction.alcMakeContextCurrent( NULL );
	// Wine incorrectly returns ALC_INVALID_VALUE
	// and then fails the rest of these functions as well
	// so there will be a leak under Wine, but that's a bug in Wine, not
	// this code
	//ASSERT_SUCCESS;

	ALFunction.alcDestroyContext( context );
	//ASSERT_SUCCESS;

	ALFunction.alcCloseDevice( device );
	//ASSERT_SUCCESS;
	ALFunction.alGetError(); // reset error state
}

#ifdef LOGALL
void OpenAL::debugState()
{

	ALint value = 0;
	ALFunction.alGetSourcei( source, AL_SOURCE_STATE, &value );
	ASSERT_SUCCESS;

	winlog( " soundPaused = %i\n", soundPaused );
	winlog( " Source:\n" );
	winlog( "  State: " );
	switch( value )
	{
	case AL_INITIAL:
		winlog( "AL_INITIAL\n" );
		break;
	case AL_PLAYING:
		winlog( "AL_PLAYING\n" );
		break;
	case AL_PAUSED:
		winlog( "AL_PAUSED\n" );
		break;
	case AL_STOPPED:
		winlog( "AL_STOPPED\n" );
		break;
	default:
		winlog( "!unknown!\n" );
		break;
	}


	ALFunction.alGetSourcei( source, AL_BUFFERS_QUEUED, &value );
	ASSERT_SUCCESS;
	winlog( "  Buffers in queue: %i\n", value );

	ALFunction.alGetSourcei( source, AL_BUFFERS_PROCESSED, &value );
	ASSERT_SUCCESS;
	winlog( "  Buffers processed: %i\n", value );
}
#endif


bool OpenAL::init(long sampleRate)
{
	winlog( "OpenAL::init\n" );
	assert( initialized == false );

	if( !LoadOAL() ) {
		wxLogError( _("OpenAL library could not be found on your system.  Please install the runtime from http://openal.org") );
		return false;
	}

	if( !gopts.audio_dev.empty() ) {
		device = ALFunction.alcOpenDevice( gopts.audio_dev.mb_str() );
	} else {
		device = ALFunction.alcOpenDevice( NULL );
	}
	assert( device != NULL );

	context = ALFunction.alcCreateContext( device, NULL );
	assert( context != NULL );

	ALCboolean retVal = ALFunction.alcMakeContextCurrent( context );
	assert( ALC_TRUE == retVal );

	ALFunction.alGenBuffers( gopts.audio_buffers, buffer );
	ASSERT_SUCCESS;

	ALFunction.alGenSources( 1, &source );
	ASSERT_SUCCESS;

	freq = sampleRate;

	// calculate the number of samples per frame first
	// then multiply it with the size of a sample frame (16 bit * stereo)
	soundBufferLen = ( freq / 60 ) * 4;


	initialized = true;
	return true;
}


void OpenAL::resume()
{
	if( !initialized ) return;
	winlog( "OpenAL::resume\n" );
	if( !buffersLoaded ) return;
	debugState();


	ALint sourceState = 0;
	ALFunction.alGetSourcei( source, AL_SOURCE_STATE, &sourceState );
	ASSERT_SUCCESS;
	if( sourceState != AL_PLAYING ) {
		ALFunction.alSourcePlay( source );
		ASSERT_SUCCESS;
	}
	debugState();
}


void OpenAL::pause()
{
	if( !initialized ) return;
	winlog( "OpenAL::pause\n" );
	if( !buffersLoaded ) return;
	debugState();


	ALint sourceState = 0;
	ALFunction.alGetSourcei( source, AL_SOURCE_STATE, &sourceState );
	ASSERT_SUCCESS;
	if( sourceState == AL_PLAYING ) {
		ALFunction.alSourcePause( source );
		ASSERT_SUCCESS;
	}
	debugState();
}


void OpenAL::reset()
{
	if( !initialized ) return;
	winlog( "OpenAL::reset\n" );
	if( !buffersLoaded ) return;
	debugState();

	ALint sourceState = 0;
	ALFunction.alGetSourcei( source, AL_SOURCE_STATE, &sourceState );
	ASSERT_SUCCESS;
	if( sourceState != AL_STOPPED ) {
		ALFunction.alSourceStop( source );
		ASSERT_SUCCESS;
	}
	debugState();
}


void OpenAL::write(u16 * finalWave, int length)
{
	if( !initialized ) return;
	winlog( "OpenAL::write\n" );

	debugState();

	ALint sourceState = 0;
	ALint nBuffersProcessed = 0;

	if( !buffersLoaded ) {
		// ==initial buffer filling==
		winlog( " initial buffer filling\n" );
		for( int i = 0 ; i < gopts.audio_buffers ; i++ ) {
			// Filling the buffers explicitly with silence would be cleaner,
			// but the very first sample is usually silence anyway.
			ALFunction.alBufferData( buffer[i], AL_FORMAT_STEREO16, finalWave, soundBufferLen, freq );
			ASSERT_SUCCESS;
		}

		ALFunction.alSourceQueueBuffers( source, gopts.audio_buffers, buffer );
		ASSERT_SUCCESS;

		buffersLoaded = true;
	} else {
		// ==normal buffer refreshing==
		nBuffersProcessed = 0;
		ALFunction.alGetSourcei( source, AL_BUFFERS_PROCESSED, &nBuffersProcessed );
		ASSERT_SUCCESS;

		if( nBuffersProcessed == gopts.audio_buffers ) {
			// we only want to know about it when we are emulating at full speed or faster:
			if( ( gopts.throttle >= 100 ) || ( gopts.throttle == 0 ) ) {
				if( systemVerbose & VERBOSE_SOUNDOUTPUT ) {
					static unsigned int i = 0;
					log( "OpenAL: Buffers were not refilled fast enough (i=%i)\n", i++ );
				}
			}
		}

		if( !speedup && synchronize && !gopts.throttle ) {
			// wait until at least one buffer has finished
			while( nBuffersProcessed == 0 ) {
				winlog( " waiting...\n" );
				// wait for about half the time one buffer needs to finish
				// unoptimized: ( sourceBufferLen * 1000 ) / ( freq * 2 * 2 ) * 1/2
				wxMilliSleep( soundBufferLen / ( freq >> 7 ) );
				ALFunction.alGetSourcei( source, AL_BUFFERS_PROCESSED, &nBuffersProcessed );
				ASSERT_SUCCESS;
			}
		} else {
			if( nBuffersProcessed == 0 ) return;
		}

		assert( nBuffersProcessed > 0 );

		// unqueue buffer
		tempBuffer = 0;
		ALFunction.alSourceUnqueueBuffers( source, 1, &tempBuffer );
		ASSERT_SUCCESS;

		// refill buffer
		ALFunction.alBufferData( tempBuffer, AL_FORMAT_STEREO16, finalWave, soundBufferLen, freq );
		ASSERT_SUCCESS;

		// requeue buffer
		ALFunction.alSourceQueueBuffers( source, 1, &tempBuffer );
		ASSERT_SUCCESS;
	}

	// start playing the source if necessary
	ALFunction.alGetSourcei( source, AL_SOURCE_STATE, &sourceState );
	ASSERT_SUCCESS;
	if( !soundPaused && ( sourceState != AL_PLAYING ) ) {
		ALFunction.alSourcePlay( source );
		ASSERT_SUCCESS;
	}
}

SoundDriver *newOpenAL()
{
	winlog( "newOpenAL\n" );
	return new OpenAL();
}

// no more use of copyrighted OpenAL code just to load the stupid library
// this is for compatibility with MFC version
// positive:  make an OpenAL-capable binary which does not require OpenAL
// negative:  openal lib may not be in library path
//            openal lib name is OS-dependent, and may even change based
//            on where it was installed from
// On UNIX, it would probably be better to just hard link with libopenal

OPENALFNTABLE OpenAL::ALFunction = {NULL};
wxDynamicLibrary OpenAL::Lib;

bool OpenAL::LoadOAL()
{
	if(!Lib.IsLoaded() &&
#ifdef __WXMSW__
	   // on win32, it's openal32.dll
	   !Lib.Load(wxT("openal32")) &&
#else
#ifdef __WXMAC__
	   // on macosx, it's just plain OpenAL
	   !Lib.Load(wxT("OpenAL"), wxDL_NOW|wxDL_VERBATIM) &&
#endif
#endif
	   // on linux, it's libopenal.so
	   // try standard name on all platforms
	   !Lib.Load(wxDynamicLibrary::CanonicalizeName(wxT("openal"))))
		return false;
#define loadfn(t, n) do { \
	if(!(ALFunction.n = (t)Lib.GetSymbol(wxT(#n)))) \
		return false; \
} while(0)
	//loadfn(LPALENABLE, alEnable);
	//loadfn(LPALDISABLE, alDisable);
	//loadfn(LPALISENABLED, alIsEnabled);
	//loadfn(LPALGETSTRING, alGetString);
	//loadfn(LPALGETBOOLEANV, alGetBooleanv);
	//loadfn(LPALGETINTEGERV, alGetIntegerv);
	//loadfn(LPALGETFLOATV, alGetFloatv);
	//loadfn(LPALGETDOUBLEV, alGetDoublev);
	//loadfn(LPALGETBOOLEAN, alGetBoolean);
	//loadfn(LPALGETINTEGER, alGetInteger);
	//loadfn(LPALGETFLOAT, alGetFloat);
	//loadfn(LPALGETDOUBLE, alGetDouble);
	loadfn(LPALGETERROR, alGetError);
	//loadfn(LPALISEXTENSIONPRESENT, alIsExtensionPresent);
	//loadfn(LPALGETPROCADDRESS, alGetProcAddress);
	//loadfn(LPALGETENUMVALUE, alGetEnumValue);
	//loadfn(LPALLISTENERF, alListenerf);
	//loadfn(LPALLISTENER3F, alListener3f);
	//loadfn(LPALLISTENERFV, alListenerfv);
	//loadfn(LPALLISTENERI, alListeneri);
	//loadfn(LPALLISTENER3I, alListener3i);
	//loadfn(LPALLISTENERIV, alListeneriv);
	//loadfn(LPALGETLISTENERF, alGetListenerf);
	//loadfn(LPALGETLISTENER3F, alGetListener3f);
	//loadfn(LPALGETLISTENERFV, alGetListenerfv);
	//loadfn(LPALGETLISTENERI, alGetListeneri);
	//loadfn(LPALGETLISTENER3I, alGetListener3i);
	//loadfn(LPALGETLISTENERIV, alGetListeneriv);
	loadfn(LPALGENSOURCES, alGenSources);
	loadfn(LPALDELETESOURCES, alDeleteSources);
	//loadfn(LPALISSOURCE, alIsSource);
	//loadfn(LPALSOURCEF, alSourcef);
	//loadfn(LPALSOURCE3F, alSource3f);
	//loadfn(LPALSOURCEFV, alSourcefv);
	loadfn(LPALSOURCEI, alSourcei);
	//loadfn(LPALSOURCE3I, alSource3i);
	//loadfn(LPALSOURCEIV, alSourceiv);
	//loadfn(LPALGETSOURCEF, alGetSourcef);
	//loadfn(LPALGETSOURCE3F, alGetSource3f);
	//loadfn(LPALGETSOURCEFV, alGetSourcefv);
	loadfn(LPALGETSOURCEI, alGetSourcei);
	//loadfn(LPALGETSOURCE3I, alGetSource3i);
	//loadfn(LPALGETSOURCEIV, alGetSourceiv);
	//loadfn(LPALSOURCEPLAYV, alSourcePlayv);
	//loadfn(LPALSOURCESTOPV, alSourceStopv);
	//loadfn(LPALSOURCEREWINDV, alSourceRewindv);
	//loadfn(LPALSOURCEPAUSEV, alSourcePausev);
	loadfn(LPALSOURCEPLAY, alSourcePlay);
	loadfn(LPALSOURCESTOP, alSourceStop);
	//loadfn(LPALSOURCEREWIND, alSourceRewind);
	loadfn(LPALSOURCEPAUSE, alSourcePause);
	loadfn(LPALSOURCEQUEUEBUFFERS, alSourceQueueBuffers);
	loadfn(LPALSOURCEUNQUEUEBUFFERS, alSourceUnqueueBuffers);
	loadfn(LPALGENBUFFERS, alGenBuffers);
	loadfn(LPALDELETEBUFFERS, alDeleteBuffers);
	//loadfn(LPALISBUFFER, alIsBuffer);
	loadfn(LPALBUFFERDATA, alBufferData);
	//loadfn(LPALBUFFERF, alBufferf);
	//loadfn(LPALBUFFER3F, alBuffer3f);
	//loadfn(LPALBUFFERFV, alBufferfv);
	//loadfn(LPALBUFFERI, alBufferi);
	//loadfn(LPALBUFFER3I, alBuffer3i);
	//loadfn(LPALBUFFERIV, alBufferiv);
	//loadfn(LPALGETBUFFERF, alGetBufferf);
	//loadfn(LPALGETBUFFER3F, alGetBuffer3f);
	//loadfn(LPALGETBUFFERFV, alGetBufferfv);
	//loadfn(LPALGETBUFFERI, alGetBufferi);
	//loadfn(LPALGETBUFFER3I, alGetBuffer3i);
	//loadfn(LPALGETBUFFERIV, alGetBufferiv);
	//loadfn(LPALDOPPLERFACTOR, alDopplerFactor);
	//loadfn(LPALDOPPLERVELOCITY, alDopplerVelocity);
	//loadfn(LPALSPEEDOFSOUND, alSpeedOfSound);
	//loadfn(LPALDISTANCEMODEL, alDistanceModel);

	loadfn(LPALCCREATECONTEXT, alcCreateContext);
	loadfn(LPALCMAKECONTEXTCURRENT, alcMakeContextCurrent);
	//loadfn(LPALCPROCESSCONTEXT, alcProcessContext);
	//loadfn(LPALCSUSPENDCONTEXT, alcSuspendContext);
	loadfn(LPALCDESTROYCONTEXT, alcDestroyContext);
	//loadfn(LPALCGETCURRENTCONTEXT, alcGetCurrentContext);
	//loadfn(LPALCGETCONTEXTSDEVICE, alcGetContextsDevice);
	loadfn(LPALCOPENDEVICE, alcOpenDevice);
	loadfn(LPALCCLOSEDEVICE, alcCloseDevice);
	//loadfn(LPALCGETERROR, alcGetError);
	loadfn(LPALCISEXTENSIONPRESENT, alcIsExtensionPresent);
	//loadfn(LPALCGETPROCADDRESS, alcGetProcAddress);
	//loadfn(LPALCGETENUMVALUE, alcGetEnumValue);
	loadfn(LPALCGETSTRING, alcGetString);
	//loadfn(LPALCGETINTEGERV, alcGetIntegerv);
	//loadfn(LPALCCAPTUREOPENDEVICE, alcCaptureOpenDevice);
	//loadfn(LPALCCAPTURECLOSEDEVICE, alcCaptureCloseDevice);
	//loadfn(LPALCCAPTURESTART, alcCaptureStart);
	//loadfn(LPALCCAPTURESTOP, alcCaptureStop);
	//loadfn(LPALCCAPTURESAMPLES, alcCaptureSamples);
	return true;
}

bool GetOALDevices(wxArrayString &names, wxArrayString &ids)
{
	return OpenAL::GetDevices(names, ids);
}

bool OpenAL::GetDevices(wxArrayString &names, wxArrayString &ids)
{
	if(!OpenAL::LoadOAL())
		return false;
#ifdef ALC_DEVICE_SPECIFIER
	if(ALFunction.alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT") == AL_FALSE)
		// this extension isn't critical to OpenAL operating
		return true;
	const char *devs = ALFunction.alcGetString(NULL, ALC_DEVICE_SPECIFIER);
	while(*devs) {
		names.push_back(wxString(devs, wxConvLibc));
		ids.push_back(names[names.size() - 1]);
		devs += strlen(devs) + 1;
	}
#else
	// should work anyway, but must always use default driver
	return true;
#endif
}

#endif
