//--------------------------------------
// audioStreamSource.cc
// implementation of streaming audio source
//
// Kurtis Seebaldt
//--------------------------------------

#include "audio/wavStreamSource.h"

#define BUFFERSIZE 32768

typedef struct
{
	ALubyte		riff[4];		// 'RIFF'
	ALsizei		riffSize;
	ALubyte		wave[4];		// 'WAVE'
	ALubyte		fmt[4];			// 'fmt '
	ALuint		fmtSize;
	ALushort	Format;
	ALushort	Channels;
	ALuint		SamplesPerSec;
	ALuint		BytesPerSec;
	ALushort	BlockAlign;
	ALushort	BitsPerSample;
	ALubyte		data[4];		// 'data'
	ALuint		dataSize;
} WAVE_Struct;

/// WAV File-header
struct WAVFileHdr
{
  ALubyte  id[4];
  ALsizei  size;
  ALubyte  type[4];
};

//// WAV Fmt-header
struct WAVFmtHdr
{
  ALushort format;
  ALushort channels;
  ALuint   samplesPerSec;
  ALuint   bytesPerSec;
  ALushort blockAlign;
  ALushort bitsPerSample;
};

/// WAV FmtEx-header
struct WAVFmtExHdr
{
  ALushort size;
  ALushort samplesPerBlock;
};

/// WAV Smpl-header
struct WAVSmplHdr
{
  ALuint   manufacturer;
  ALuint   product;
  ALuint   samplePeriod;
  ALuint   note;
  ALuint   fineTune;
  ALuint   SMPTEFormat;
  ALuint   SMPTEOffest;
  ALuint   loops;
  ALuint   samplerData;
  struct
  {
    ALuint identifier;
    ALuint type;
    ALuint start;
    ALuint end;
    ALuint fraction;
    ALuint count;
  }      loop[1];
};

/// WAV Chunk-header
struct WAVChunkHdr
{
  ALubyte  id[4];
  ALuint   size;
};



WavStreamSource::WavStreamSource(const char *filename)  {
   stream = NULL;
   bIsValid = false;
   bBuffersAllocated = false;
   mBufferList[0] = 0;
   clear();

   mFilename = filename;
   mPosition = Point3F(0.f,0.f,0.f);
}

WavStreamSource::~WavStreamSource() {
	if(bReady && bIsValid)
		freeStream();
}

void WavStreamSource::clear()
{
	if(stream)
		freeStream();

	mHandle           = NULL_AUDIOHANDLE;
	mSource			  = NULL;

	if(mBufferList[0] != 0)
		alDeleteBuffers(NUMBUFFERS, mBufferList);
	for(int i = 0; i < NUMBUFFERS; i++)
		mBufferList[i] = 0;

	dMemset(&mDescription, 0, sizeof(Audio::Description));
	mEnvironment = 0;
	mPosition.set(0.f,0.f,0.f);
	mDirection.set(0.f,1.f,0.f);
	mPitch = 1.f;
	mScore = 0.f;
	mCullTime = 0;

	bReady = false;
	bFinishedPlaying = false;
	bIsValid = false;
	bBuffersAllocated = false;
}

bool WavStreamSource::initStream() {
   WAVChunkHdr chunkHdr;
   WAVFmtExHdr fmtExHdr;
   WAVFileHdr  fileHdr;
   WAVSmplHdr  smplHdr;
   WAVFmtHdr   fmtHdr;

   WAVE_Struct wave;

   ALint			error;

   bFinished = false;

   char   data[BUFFERSIZE];

   alSourceStop(mSource);
   alSourcei(mSource, AL_BUFFER, 0);

	stream = ResourceManager->openStream(mFilename);
	if(stream != NULL) {
	   stream->read(4, &fileHdr.id[0]);
	   stream->read(&fileHdr.size);
	   stream->read(4, &fileHdr.type[0]);

	   stream->read(4, &chunkHdr.id[0]);
	   stream->read(&chunkHdr.size);

	   // WAV Format header
		stream->read(&fmtHdr.format);
		stream->read(&fmtHdr.channels);
		stream->read(&fmtHdr.samplesPerSec);
		stream->read(&fmtHdr.bytesPerSec);
		stream->read(&fmtHdr.blockAlign);
		stream->read(&fmtHdr.bitsPerSample);

		format=(fmtHdr.channels==1?
		   (fmtHdr.bitsPerSample==8?AL_FORMAT_MONO8:AL_FORMAT_MONO16):
		   (fmtHdr.bitsPerSample==8?AL_FORMAT_STEREO8:AL_FORMAT_STEREO16));
		freq=fmtHdr.samplesPerSec;

		 stream->read(4, &chunkHdr.id[0]);
		 stream->read(&chunkHdr.size);

		DataSize = chunkHdr.size;
		DataLeft = DataSize;
		dataStart = stream->getPosition();

		// Clear Error Code
		alGetError();

		alGenBuffers(NUMBUFFERS, mBufferList);
		if ((error = alGetError()) != AL_NO_ERROR) {
			return false;
		}

		bBuffersAllocated = true;

		int numBuffers = 0;
		for(int loop = 0; loop < NUMBUFFERS; loop++)
		{
			ALuint DataToRead = (DataLeft > BUFFERSIZE) ? BUFFERSIZE : DataLeft;

			if (DataToRead == DataLeft)
				bFinished = AL_TRUE;
			stream->read(DataToRead, data);
			DataLeft -= DataToRead;
			alBufferData(mBufferList[loop], format, data, DataToRead, freq);	
			if ((error = alGetError()) != AL_NO_ERROR) {
				return false;
			}
			++numBuffers;
			if(bFinished)
				break;
		}

		// Queue the buffers on the source
		alSourceQueueBuffers(mSource, numBuffers, mBufferList);
		if ((error = alGetError()) != AL_NO_ERROR) {
			return false;
		}

		buffersinqueue = numBuffers;
		alSourcei(mSource, AL_LOOPING, AL_FALSE);
		bReady = true;
	}
	else {
		return false;
	}

	bIsValid = true;

   return true;
}

bool WavStreamSource::updateBuffers() {

	ALint			processed;
	ALuint			BufferID;
	ALint			error;
	char data[BUFFERSIZE];

	// don't do anything if buffer isn't initialized
	if(!bIsValid)
		return false;

	if(bFinished && mDescription.mIsLooping) {
		resetStream();
	}

	// reset AL error code
	alGetError();

	// Get status
	alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed);

	// If some buffers have been played, unqueue them and load new audio into them, then add them to the queue
	if (processed > 0)
	{

		while (processed)
		{
			alSourceUnqueueBuffers(mSource, 1, &BufferID);
			if ((error = alGetError()) != AL_NO_ERROR)
				return false;

			if (!bFinished)
			{
				ALuint DataToRead = (DataLeft > BUFFERSIZE) ? BUFFERSIZE : DataLeft;

				if (DataToRead == DataLeft) {
					bFinished = AL_TRUE;
				}
					
				stream->read(DataToRead, data);
				DataLeft -= DataToRead;
					
				alBufferData(BufferID, format, data, DataToRead, freq);
				if ((error = alGetError()) != AL_NO_ERROR)
					return false;

				// Queue buffer
				alSourceQueueBuffers(mSource, 1, &BufferID);
				if ((error = alGetError()) != AL_NO_ERROR)
					return false;

				processed--;

				if(bFinished && mDescription.mIsLooping) {
					resetStream();
				}
			}
			else
			{
				buffersinqueue--;
				processed--;

				if (buffersinqueue == 0)
				{
					bFinishedPlaying = AL_TRUE;
					return AL_FALSE;
				}
			}
		}
	}

	return AL_TRUE;
}

void WavStreamSource::freeStream() {
	bReady = false;

	
	if(stream != NULL)
		ResourceManager->closeStream(stream);
	stream = NULL;

	if(bBuffersAllocated) {
		if(mBufferList[0] != 0)
			alDeleteBuffers(NUMBUFFERS, mBufferList);
		for(int i = 0; i < NUMBUFFERS; i++)
			mBufferList[i] = 0;

		bBuffersAllocated = false;
	}
}

void WavStreamSource::resetStream() {
	stream->setPosition(dataStart);
	DataLeft = DataSize;
	bFinished = AL_FALSE;
}

#include "console/console.h"

F32 WavStreamSource::getElapsedTime()
{
   Con::warnf( "GetElapsedTime not implemented in WaveStreams yet" );
   return -1.f;
}

F32 WavStreamSource::getTotalTime()
{
   Con::warnf( "GetTotalTime not implemented in WaveStreams yet" );
   return -1.f;
}
