#include <string.h>
#include <malloc.h>
#include "vorbissource.h"

static size_t ov_callback_read(void *ptr, size_t size, size_t nmemb, void *datasource)
{
	File *tFile = (File *)datasource;
	
	return tFile->read(ptr, size, nmemb);
}

static int ov_callback_seek(void *datasource, ogg_int64_t offset, int whence)
{
	File *tFile = (File *)datasource;
	
	return tFile->seek(offset, whence);
}

static long int ov_callback_tell(void *datasource)
{
	File *tFile = (File *)datasource;
	
	return tFile->tell();
}

VorbisSource::VorbisSource() : SoundSource()
{
	// Ensure no bad open causes a false success on reading
	opened = 0;
}

VorbisSource::~VorbisSource()
{
	// Ensure proper cleanup
	if(opened) { ov_clear(&vf); }
	opened = 0;
}

OpenStatus VorbisSource::openLocation(File *sourceFile)
{
	// Remember no audio samples rendered
	rendered = 0;
	
	// Try to open file
	_sourceFile = sourceFile;
	
	// First, check if this is oggflac?
	u8 identifier[5];
	
	// Grab bytes at correct offset
	_sourceFile->seek(28, SEEK_SET);
	_sourceFile->read(identifier, 1, 5);
	_sourceFile->seek(0, SEEK_SET);
	
	if(identifier[0] == 0x7F && identifier[1] == 0x46 && identifier[2] == 0x4C && identifier[3] == 0x41 && identifier[4] == 0x43)
	{
		// It is, set up flac codec
		strcpy(newFormat, "flac");		
		return OPEN_ANOTHERDECODER;
	}
	
	// Create callbacks and open
	ov_callbacks ovc = { ov_callback_read, ov_callback_seek, NULL, ov_callback_tell };
	if(ov_open_callbacks(sourceFile, &vf, NULL, 0, ovc) < 0)
	{
		return OPEN_FAILURE;
	}
	
	// Grab file information
	vi = ov_info(&vf, -1);
	
	// Ensure that it is only mono or stereo
	if(vi->channels > 2)
	{
		// No doing this
		ov_clear(&vf);
		
		return OPEN_FAILURE;
	}
	
	// Success!
	opened = 1;
	
	// Return success
	return OPEN_SUCCESS;
}

int VorbisSource::renderChunk(uint8_t *buf, int &renderSamples)
{
	// Safe to seek back now
	rendered = 1;
	
	// Ensure no blips from end
	memset(buf, 0, renderSamples * 4);
	
	// Two render methods for mono and stereo
	vi = ov_info(&vf, -1);
	
	if(!vi) { return 0; }
	
	if(vi->channels == 1)
	{
		// Mono
		int16_t *tBuf = (int16_t *)buf;
		int16_t *tOut = (int16_t *)memalign(32, renderSamples * 2);
		
		// Returns raw bytes output
		int current_section;
		int ret = ov_read(&vf, (char *)tOut, renderSamples * 2, 1, 2, 1, &current_section);
		
		if(ret > 0)
		{
			// Figure out length
			renderSamples = ret / 2;
			
			// Copy mono to stereo
			for(int i = 0; i < renderSamples; i++)
			{
				tBuf[i << 1] = tOut[i];
				tBuf[(i << 1) + 1] = tOut[i];
			}
			
			// Free up memory
			free(tOut);
			
			return 1;
		}
		
		// Free up memory
		free(tOut);
	}
	else
	{
		// Stereo
		int current_section;
		
		// Returns raw bytes output
		int ret = ov_read(&vf, (char *)buf, renderSamples * 4, 1, 2, 1, &current_section);
		
		if(ret > 0)
		{
			// Calculate number of actual samples
			renderSamples = ret / 4;
			
			return 1;
		}
	}
	
	// Failed to render
	return 0;
}

void VorbisSource::seekSeconds(int seconds)
{
	// No seeking on unrendered
	if(!rendered) { return; }
	
	// Seek in seconds
	ov_time_seek_page(&vf, (double)seconds);
}

int VorbisSource::tellSeconds()
{
	// Grab current position from vorbis structure
	return ov_time_tell(&vf);
}

int VorbisSource::getLength()
{
	// Grab length from vorbis structure
	return ov_time_total(&vf, -1);
}

uint32_t VorbisSource::getFrequency()
{
	// Send back pre-fetched frequency of playback
	vi = ov_info(&vf, -1);
	
	if(!vi) { return 48000; }
	
	return vi->rate;
}

void VorbisSource::close()
{
	// Kill renderer if it exists
	if(opened) { ov_clear(&vf); }
	opened = 0;
}

void VorbisSource::getNowPlaying(const char *fmtString, char *output)
{
	// Grab the filename
	char tFile[MAXPATHLEN];
	_sourceFile->getCurrentUrl(tFile);
	
	// Find the filename only
	for(int i = strlen(tFile); i >= 0; i--)
	{
		if(tFile[i] == '/')
		{
			// Found it!
			strcpy(output, &tFile[i + 1]);
			
			// Exit
			return;
		}
	}
	
	// Default
	strcpy(output, "Nothing!");
}

int VorbisSource::supportsFormat(const char *ext)
{
	// Four formats
	if(strcmp(ext, "ogg") == 0) { return 1; }
	
	// We support no other formats
	return 0;
}

void VorbisSource::readAhead()
{
	// Read ahead some
	_sourceFile->readAhead();
}
