//Copyright (C) 2007 SiegeLord
//See ReadMe.txt and License.txt for copying information

#include "../include/engine/sound.h"
#include "../include/engine/error.h"
#include "../include/engine/fileio.h"


//I got this from some site...


/************************************************************************************************************************
	The following function are the vorbis callback functions.  As their names suggest, they are expected to work in exactly the
	same way as normal c io functions (fread, fclose etc.).  Its up to us to return the information that the libs need to parse
	the file from memory
************************************************************************************************************************/
//---------------------------------------------------------------------------------
// Function	: VorbisRead
// Purpose	: Callback for the Vorbis read function
// Info		:
//---------------------------------------------------------------------------------
size_t VorbisRead(void *ptr			/* ptr to the data that the vorbis files need*/,
				  size_t byteSize	/* how big a byte is*/,
				  size_t sizeToRead /* How much we can read*/,
				  void *datasource	/* this is a pointer to the data we passed into ov_open_callbacks (our SSoundFile struct*/)
{
	size_t				spaceToEOF;			// How much more we can read till we hit the EOF marker
	size_t				actualSizeToRead;	// How much data we are actually going to read from memory
	SSoundFile*			vorbisData;			// Our vorbis data, for the typecast

	// Get the data in the right format
	vorbisData = (SSoundFile*)datasource;

	// Calculate how much we need to read.  This can be sizeToRead*byteSize or less depending on how near the EOF marker we are
	spaceToEOF = vorbisData->dataSize - vorbisData->dataRead;
	if ((sizeToRead*byteSize) < spaceToEOF)
		actualSizeToRead = (sizeToRead*byteSize);
	else
		actualSizeToRead = spaceToEOF;

	// A simple copy of the data from memory to the datastruct that the vorbis libs will use
	if (actualSizeToRead)
	{
		// Copy the data from the start of the file PLUS how much we have already read in
		memcpy(ptr, (char*)vorbisData->dataPtr + vorbisData->dataRead, actualSizeToRead);
		// Increase by how much we have read by
		vorbisData->dataRead += (actualSizeToRead);
	}

	// Return how much we read (in the same way fread would)
	return actualSizeToRead;
}

//---------------------------------------------------------------------------------
// Function	: VorbisSeek
// Purpose	: Callback for the Vorbis seek function
// Info		:
//---------------------------------------------------------------------------------
int VorbisSeek(void *datasource		/*this is a pointer to the data we passed into ov_open_callbacks (our SSoundFile struct*/,
			   ogg_int64_t offset	/*offset from the point we wish to seek to*/,
			   int whence			/*where we want to seek to*/)
{
	ogg_int64_t			spaceToEOF;		// How much more we can read till we hit the EOF marker
	ogg_int64_t			actualOffset;	// How much we can actually offset it by
	SSoundFile*			vorbisData;		// The data we passed in (for the typecast)

	// Get the data in the right format
	vorbisData = (SSoundFile*)datasource;

	// Goto where we wish to seek to
	switch (whence)
	{
	case SEEK_SET: // Seek to the start of the data file
		// Make sure we are not going to the end of the file
		if (vorbisData->dataSize >= offset)
			actualOffset = offset;
		else
			actualOffset = vorbisData->dataSize;
		// Set where we now are
		vorbisData->dataRead = (int)actualOffset;
		break;
	case SEEK_CUR: // Seek from where we are
		// Make sure we dont go past the end
		spaceToEOF = vorbisData->dataSize - vorbisData->dataRead;
		if (offset < spaceToEOF)
			actualOffset = (offset);
		else
			actualOffset = spaceToEOF;
		// Seek from our currrent location
		vorbisData->dataRead += actualOffset;
		break;
	case SEEK_END: // Seek from the end of the file
		vorbisData->dataRead = vorbisData->dataSize+1;
		break;
	default:

		Throw("*** ERROR *** Unknown seek command in VorbisSeek\n", "");
		break;
	};

	return 0;
}

//---------------------------------------------------------------------------------
// Function	: VorbisClose
// Purpose	: Callback for the Vorbis close function
// Info		:
//---------------------------------------------------------------------------------
int VorbisClose(void *datasource /*this is a pointer to the data we passed into ov_open_callbacks (our SSoundFile struct*/)
{
	// This file is called when we call ov_close.  If we wanted, we could free our memory here, but
	// in this case, we will free the memory in the main body of the program, so dont do anything

	return 1;
}

//---------------------------------------------------------------------------------
// Function	: VorbisTell
// Purpose	: Classback for the Vorbis tell function
// Info		:
//---------------------------------------------------------------------------------
long VorbisTell(void *datasource /*this is a pointer to the data we passed into ov_open_callbacks (our SSoundFile struct*/)
{
	SSoundFile*	vorbisData;

	// Get the data in the right format
	vorbisData = (SSoundFile*)datasource;

	// We just want to tell the vorbis libs how much we have read so far
	return vorbisData->dataRead;
}

/************************************************************************************************************************
	End of Vorbis callback functions
************************************************************************************************************************/

map<string, SSoundFile*> g_mSoundFiles;
ov_callbacks g_pVorbisCallbacks;

void InitVorbis()
{
	g_pVorbisCallbacks.read_func = VorbisRead;
	g_pVorbisCallbacks.close_func = VorbisClose;
	g_pVorbisCallbacks.seek_func = VorbisSeek;
	g_pVorbisCallbacks.tell_func = VorbisTell;
}

SSoundFile* FindSound(const char* filename)
{
	map<string, SSoundFile*>::iterator it;

	it = g_mSoundFiles.find(string(filename));

	if(it == g_mSoundFiles.end())//can't find it, so load a new one
	{
		SSoundFile* ret = ForceLoadSound(filename);

		g_mSoundFiles.insert(pair<string, SSoundFile*>(string(filename), ret));

		return ret;
	}
	else//return the existing one
		return it->second;
}

SSoundFile* ForceLoadSound(const char* filename)
{
	PACKFILE* file = LoadFile(filename);
	if(file)
	{
		SSoundFile* ret = new SSoundFile;

		int length;

		length = ReadFile(file, &(ret->dataPtr));

		ret->dataRead = 0;
		ret->dataSize = length;

		OggVorbis_File ogg_file;

		if(0 == ov_test_callbacks(ret, &ogg_file, NULL, 0, g_pVorbisCallbacks))
		{
			ov_raw_seek(&ogg_file, 0);
			ov_clear(&ogg_file);
			ret->dataRead = 0;
			CloseFile(file);

			return ret;
		}
		else
		{
			Throw("Not a valid .ogg file!\n", filename);
		}
	}
	CloseFile(file);

	return 0;
}

int PrepareSoundForPlayback(SSoundFile* source, OggVorbis_File *file, vorbis_info** info)
{
	int ret = ov_open_callbacks(source, file, NULL, 0, g_pVorbisCallbacks);
	if(0 == ret)
	{
		*info = ov_info(file, -1);
		return 1;
	}
	return 0;
}

void StopSoundPlayback(OggVorbis_File *file)
{
	ov_clear(file);
}

char g_cTransferBuf[g_nBufferSize * 4];

int Stream(OggVorbis_File* file, void* buf, int* section, int amt, bool loop)
{
	_ASSERT(file);

	int amt_so_far = 0;

	while(amt_so_far != amt)//fill it up
	{
		int ret = ov_read(file, g_cTransferBuf, amt - amt_so_far, 0, 2, 0, section);
		if(ret > 0)
		{
			memcpy((char*)buf + amt_so_far, g_cTransferBuf, ret);
			amt_so_far += ret;
		}
		else if(ret == 0)
		{
			if(loop)
				ov_raw_seek(file, 0);
			else
				return 0;
		}
	}

	return amt_so_far;
}

void FreeSounds()
{
	map<string, SSoundFile*>::iterator it = g_mSoundFiles.begin();

	while(it != g_mSoundFiles.end())
	{
		delete (char*)it->second->dataPtr;
		delete it->second;
		it++;
	}
}
