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

void error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data)
{
	// Dummy function
}

FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data)
{
	// Grab file
	File *tFile = ((FlacInfo *)client_data)->tFile;
	
	// read from it
	*bytes = tFile->read(buffer, sizeof(FLAC__byte), *bytes);
	
	// Set appropriate return code
	if(*bytes == 0)
	{
		return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
	}
	
	return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
}

FLAC__StreamDecoderSeekStatus seek_callback(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data)
{
	// Grab file
	File *tFile = ((FlacInfo *)client_data)->tFile;
	
	// Seek
	tFile->seek(absolute_byte_offset, SEEK_SET);
	
	// Return successful
	return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
}

FLAC__StreamDecoderTellStatus tell_callback(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data)
{
	// Grab file
	File *tFile = ((FlacInfo *)client_data)->tFile;
	
	// Grab location
	*absolute_byte_offset = (FLAC__uint64)tFile->tell();
	
	// Return successful
	return FLAC__STREAM_DECODER_TELL_STATUS_OK;
}

FLAC__StreamDecoderLengthStatus length_callback(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data)
{
	// Set the length
	*stream_length = ((FlacInfo *)client_data)->fLen;
	
	// Return successful
	return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
}

FLAC__bool eof_callback(const FLAC__StreamDecoder *decoder, void *client_data)
{
	// Grab file
	File *tFile = ((FlacInfo *)client_data)->tFile;
	
	// Set the length
	int end = ((FlacInfo *)client_data)->fLen;
	
	// Grab location
	int loc = tFile->tell();
	
	// If the length is at the end, then eof
	if(loc == end)
	{
		return true;
	}
	
	// Seek back
	tFile->seek(loc, SEEK_SET);
	
	// Return not eof
	return false;
}

void metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data)
{
	FlacInfo *fInfoPtr = (FlacInfo *)client_data;
	
	if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) 
	{
		fInfoPtr->samples = metadata->data.stream_info.total_samples;
		fInfoPtr->bits = metadata->data.stream_info.bits_per_sample;
		fInfoPtr->channels = metadata->data.stream_info.channels;
		fInfoPtr->frequency = metadata->data.stream_info.sample_rate;
	}
}

FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data)
{
	FlacInfo *fInfoPtr = (FlacInfo *)client_data;
	
	// Get min and max for clipping
	FLAC__int32 MIN = -(1L << (fInfoPtr->bits - 1));
	FLAC__int32 MAX = ~MIN;
	
	// Loop and place everytihng onto the ring buffer
	for(u32 i = 0; i < frame->header.blocksize; i++)
	{
		FLAC__int32 sample = buffer[0][i];
		int16_t outSample = 0;
		
		// Clip 		
		if(sample > MAX) 
		{
			sample = MAX;
		}
		else if(sample < MIN) 
		{
			sample = MIN;
		}
		
		// Figure out left sample from bits
		if(fInfoPtr->bits == 16)
		{
			// Direct conversion
			outSample = sample;
		}
		else if(fInfoPtr->bits == 8)		
		{
			// Add lower 8 bits
			outSample = sample << 8;
		}
		else if(fInfoPtr->bits == 24)
		{
			// Take off lower 8 bits
			outSample = sample >> 8;
		}
		
		// Add left sample
		fInfoPtr->outBuf.put(&outSample, sizeof(int16_t));
		
		if(fInfoPtr->channels == 1)
		{
			// Add left as right
			fInfoPtr->outBuf.put(&outSample, sizeof(int16_t));
		}
		else
		{
			sample = buffer[1][i];
			
			// Clip 		
			if(sample > MAX) 
			{
				sample = MAX;
			}
			else if(sample < MIN) 
			{
				sample = MIN;
			}
			
			// Figure out left sample from bits
			if(fInfoPtr->bits == 16)
			{
				// Direct conversion
				outSample = sample;
			}
			else
			{
				// Add lower 8 bits
				outSample = sample << 8;
			}
			
			// Add left sample
			fInfoPtr->outBuf.put(&outSample, sizeof(int16_t));
		}
	}
	
	// More samples!
	return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

FlacSource::FlacSource() : SoundSource()
{
	// Initialize flac library
	decoder = NULL;
	
	// No frequency
	fInfo.frequency = 0;
}

FlacSource::~FlacSource()
{
	// Kill flac if still exists
	if(decoder) { FLAC__stream_decoder_delete(decoder); }
	decoder = NULL;
	
	// Kill file
	close();
}

OpenStatus FlacSource::openLocation(File *sourceFile)
{
	// Remember no audio samples rendered
	rendered = 0;
	
	// No frequency
	fInfo.frequency = 0;
	
	// Try to open file
	_sourceFile = sourceFile;
	
	// Seek to end to get file length
	_sourceFile->seek(0, SEEK_END);
	fInfo.fLen = _sourceFile->tell();
	_sourceFile->seek(0, SEEK_SET);
	
	// Ensure file is set here too
	fInfo.tFile = sourceFile;
	
	// Init decoder
	decoder = FLAC__stream_decoder_new();
	
	if(!decoder)
	{
		// Can't render
		return OPEN_FAILURE;
	}
	// Initialize file
	FLAC__StreamDecoderInitStatus init;

	// Figure out if oggflac or normal flac
	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 ogg
		init = FLAC__stream_decoder_init_ogg_stream(decoder, read_callback, seek_callback, tell_callback, length_callback, eof_callback, write_callback, metadata_callback, error_callback, &fInfo);
	}
	else
	{
		// Normal
		init = FLAC__stream_decoder_init_stream(decoder, read_callback, seek_callback, tell_callback, length_callback, eof_callback, write_callback, metadata_callback, error_callback, &fInfo);
	}
	
	// If we couldn't init the file, exit
	if(init != FLAC__STREAM_DECODER_INIT_STATUS_OK)
	{
		if(decoder) { FLAC__stream_decoder_delete(decoder); }
		decoder = NULL;
		
		return OPEN_FAILURE;
	}
	
	// Pass metadata
	if(!FLAC__stream_decoder_process_until_end_of_metadata(decoder))
	{
		if(decoder) { FLAC__stream_decoder_delete(decoder); }
		decoder = NULL;
		
		return OPEN_FAILURE;
	}
	
	// Only 1 or 2 channel
	if(fInfo.channels > 2)
	{
		if(decoder) { FLAC__stream_decoder_delete(decoder); }
		decoder = NULL;
		
		return OPEN_FAILURE;
	}
	
	// Init samples
	fInfo.outBuf.init();
	
	// Return success
	return OPEN_SUCCESS;
}

int FlacSource::renderChunk(uint8_t *buf, int &renderSamples)
{
	// Safe to seek back now
	rendered = 1;
	
	// Ensure no blips at end
	memset(buf, 0, renderSamples * 4);
	
	// Try to pull maximum samples off of ring buffer
	while(true)
	{
		int nSamples = (fInfo.outBuf.amount() < (uint32_t)(renderSamples * 4)) ? fInfo.outBuf.amount() : renderSamples * 4;
		
		if(nSamples == 0)
		{
			// Try to decode
			if(FLAC__stream_decoder_get_state(decoder) == FLAC__STREAM_DECODER_END_OF_STREAM)
			{
				// No more samples
				renderSamples = 0;
				
				// No more data available
				return 0;
			}
			
			// Ok to decode
			FLAC__stream_decoder_process_single(decoder);
		}
		else
		{
			// Copy data over
			fInfo.outBuf.get(buf, nSamples);
			
			// Remember number of samples
			renderSamples = nSamples / 4;
			
			// More data available!
			return 1;
		}
	}
}

void FlacSource::seekSeconds(int seconds)
{
	// No seeking
	if(!rendered) { return; }
	
	// Figure out seeking by seconds
	FLAC__stream_decoder_seek_absolute(decoder, seconds * fInfo.frequency);
	FLAC__stream_decoder_flush(decoder);
}

int FlacSource::tellSeconds()
{
	// Nothing?
	if(!decoder) { return -1; }
	
	// Calculate number of seconds in
	FLAC__uint64 pos;	
	if(!FLAC__stream_decoder_get_decode_position(decoder, &pos))
	{
		// Fails for ogg.
		pos = _sourceFile->tell();
	}
	
	// Figure out what that means in actual seconds
	double percent = (double)pos / (double)fInfo.fLen;
	
	// Return seconds
	return (int)(percent * (double)(fInfo.samples / fInfo.frequency));
}

int FlacSource::getLength()
{
	// Make sure no division by zero
	if(fInfo.frequency > 0)
	{
		// Length in seconds to sound processor
		return fInfo.samples / fInfo.frequency;
	}
	else
	{
		// No length
		return -1;
	}
}

uint32_t FlacSource::getFrequency()
{
	// Send back pre-fetched frequency of playback
	if(!fInfo.frequency) { return 48000; }
	
	return fInfo.frequency;
}

void FlacSource::close()
{
	if(decoder) { FLAC__stream_decoder_delete(decoder); }
	decoder = NULL;
	
	// Close source file, no other cleanup needed
	_sourceFile->close();
}

void FlacSource::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 FlacSource::supportsFormat(const char *ext)
{
	// Only one format
	if(strcmp(ext, "flac") == 0) { return 1; }
	
	// We support no other formats, so this function always fails
	return 0;
}

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