#include "Snd.h"

using namespace std;

#define NUM_OF_DYNBUF	2
#define DYNBUF_SIZE		10024
#define ERRMSG(Msg) MessageBox(NULL, Msg, "OpenAL Error", MB_OK | MB_ICONEXCLAMATION)

typedef struct 
{
	unsigned int	ID;
	string		Filename;
	unsigned int	Rate;
	unsigned int	Format;
} SndInfo;

typedef map<ALuint, SndInfo> TBuf;

TBuf		Buffers;
ALCdevice	*pDevice;
ALCcontext	*pContext;

string ExtractFileExt(const string &Filename)
{
	if (Filename.size() == 0) return "";

	int i = Filename.size();
	string buf;
	while((Filename[i] != '.') && (i > 0))
	{
		buf = Filename[i--] + buf;
	}
	_strupr((char *)buf.c_str());
	return buf;
}

ALboolean CheckALCError()
{
	ALenum ErrCode;
	string Err = "ALC error: ";
	if ((ErrCode = alcGetError(pDevice)) != ALC_NO_ERROR)
	{
		Err += (char *)alcGetString(pDevice, ErrCode);
		ERRMSG(Err.data());
		return AL_FALSE;
	}
	return AL_TRUE;
}

ALboolean CheckALError()
{
	ALenum ErrCode;
	string Err = "OpenAL error: ";
	if ((ErrCode = alGetError()) != AL_NO_ERROR)
	{
		Err += (char *)alGetString(ErrCode);
		ERRMSG(Err.data());
		return AL_FALSE;
	}
	return AL_TRUE;
}

bool InitializeOpenAL()
{
	ALfloat ListenerPos[] = { 0.0, 0.0, 0.0 };
	ALfloat ListenerVel[] = { 0.0, 0.0, 0.0 };
	ALfloat ListenerOri[] = { 0.0, 0.0, -1.0,  0.0, 1.0, 0.0 };

	pDevice = alcOpenDevice(NULL);
	if (!pDevice)
	{
		ERRMSG("Default sound device not present");
		return false;
	}

	pContext = alcCreateContext(pDevice, NULL);
	if (!CheckALCError()) return false;

	alcMakeContextCurrent(pContext);

	alListenerfv(AL_POSITION,    ListenerPos);
	alListenerfv(AL_VELOCITY,    ListenerVel);
	alListenerfv(AL_ORIENTATION, ListenerOri);
	return true;
}

void DestroyOpenAL()
{
	for (TBuf::iterator i = Buffers.begin(); i != Buffers.end(); i++)
		alDeleteBuffers(1, &i->second.ID);
	alcMakeContextCurrent(NULL);
	alcDestroyContext(pContext);
	alcCloseDevice(pDevice);
}

Snd::Snd(const string &Filename, bool Looped)
{
	for (int i = 0; i < 3; i++)
		mPos[i] = mVel[i] = 0.0f;
	mSourceID = 0;

	Open(Filename, Looped);
}

Snd::~Snd()
{
	Close();
}

bool Snd::Open(const string &Filename, bool Looped)
{
	ifstream a(Filename.c_str());
	if (!a.is_open()) return false;
	a.close();

	mLooped		= Looped;

	alGenSources(1, &mSourceID);
	if (!CheckALError()) return false;

	alSourcef (mSourceID, AL_PITCH,		1.0f);
	alSourcef (mSourceID, AL_GAIN,		1.0f);
	alSourcefv(mSourceID, AL_POSITION,	mPos);
	alSourcefv(mSourceID, AL_VELOCITY,	mVel);
	alSourcei (mSourceID, AL_LOOPING,	mLooped);

	string Ext = ExtractFileExt(Filename).data();
	if (Ext == "WAV") return LoadWavFile(Filename);
	return false;
}

bool Snd::LoadWavFile(const string &Filename)
{
	SndInfo		buffer;
	ALenum		format;
	ALvoid		*data;
	ALsizei		size;
	ALsizei		freq;
	ALboolean	loop;
	ALuint		BufID = 0;

	buffer.Filename = Filename;
	for (TBuf::iterator i = Buffers.begin(); i != Buffers.end(); i++)
	{
		if (i->second.Filename == Filename) BufID = i->first;
	}

	if (!BufID)
	{
		alGenBuffers(1, &buffer.ID);
		if (!CheckALError()) return false;
		alutLoadWAVFile((ALbyte *)Filename.data(), &format, &data, &size, &freq, &loop);
		if (!CheckALError()) return false;
	
		buffer.Format			= format;
		buffer.Rate				= freq;
		alBufferData(buffer.ID, format, data, size, freq);
		if (!CheckALError()) return false;
		alutUnloadWAV(format, data, size, freq);
		if (!CheckALError()) return false;

		Buffers[buffer.ID] = buffer;
	}
	else 
		buffer = Buffers[BufID];

	alSourcei (mSourceID, AL_BUFFER,	buffer.ID);

	return true;
}

void Snd::Play()
{
	alSourcePlay(mSourceID);
}

void Snd::Close()
{
	alSourceStop(mSourceID);
	if (alIsSource(mSourceID)) alDeleteSources(1, &mSourceID);
}

void Snd::Stop()
{
	alSourceStop(mSourceID);
}

void Snd::Move(float X, float Y, float Z)
{
	ALfloat Pos[3] = { X, Y, Z };
	alSourcefv(mSourceID, AL_POSITION, Pos);
}
