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

static unsigned short shortSwap(unsigned short inshort)
{
	return ((inshort >> 8) & 0xFF) | ((inshort & 0xFF) << 8);
}

static unsigned long longSwap(unsigned long inlong)
{
	return ((inlong >> 24) & 0xFF) | ((inlong >> 8) & 0xFF00) | ((inlong << 8) & 0xFF0000) | ((inlong << 24) & 0xFF000000);
}

WavSource::WavSource() : SoundSource()
{
	// Set defaults
	dataChunkStart = 0;
	dataChunkEnd = 0;
}

WavSource::~WavSource()
{
	// Kill file
	close();
}

OpenStatus WavSource::openLocation(File *sourceFile)
{
	// Try to open wav file
	_sourceFile = sourceFile;
	
	// Read header
	char header[13];
	_sourceFile->read(header, 1, 12);
	
	// Set dummy values to do a simple string comparison
	header[12] = 0;
	header[4] = ' ';
	header[5] = ' ';
	header[6] = ' ';
	header[7] = ' ';
	
	// Actually compare
	if(strcmp(header, "RIFF    WAVE") != 0)
	{
		// wrong header, Exit
		return OPEN_FAILURE;
	}
	
	// Read format chunk
	WavFormatChunk headerChunk;
	_sourceFile->read(&headerChunk, sizeof(WavFormatChunk), 1);
	
	// Swap bytes
	headerChunk.chunkSize = longSwap(headerChunk.chunkSize);
	headerChunk.wFormatTag = shortSwap(headerChunk.wFormatTag);
	headerChunk.wChannels = shortSwap(headerChunk.wChannels);
	headerChunk.dwSamplesPerSec = longSwap(headerChunk.dwSamplesPerSec);
	headerChunk.dwAvgBytesPerSec = longSwap(headerChunk.dwAvgBytesPerSec);
	headerChunk.wBlockAlign = shortSwap(headerChunk.wBlockAlign);
	headerChunk.wBitsPerSample = shortSwap(headerChunk.wBitsPerSample);
	
	// Check to be sure its actually a format chunk (really strict parsing)
	if(strncmp(headerChunk.chunkID, "fmt ", 4) != 0)
	{	
		// wrong chunk at beginning, Exit
		return OPEN_FAILURE;
	}
	
	// Make sure we aren't using compression
	if(headerChunk.wFormatTag != 1)
	{	
		// Figure out compression
		switch(headerChunk.wFormatTag)
		{
			case 0x0050: // mp1/mp2
			case 0x0055: // mp3
				// Set up the mp3 codec
				strcpy(newFormat, "mp3");
				
				// Seek back to beginning
				_sourceFile->seek(0, SEEK_SET);				
				return OPEN_ANOTHERDECODER;
		}
		
		// unknown compression used, don't load, Exit
		return OPEN_FAILURE;
	}
	
	// Make sure it's only a mono or stereo wav file
	if(headerChunk.wChannels > 2)
	{
		// more than 2 channels, Exit
		return OPEN_FAILURE;
	}
	
	// Set channels (stereo or mono possible)
	numChannels = headerChunk.wChannels;
	
	// Figure out how many bytes used per sample
	if(headerChunk.wBitsPerSample <= 8)
	{
		// 8bps
		sampleBytes = 1;
	}
	else if(headerChunk.wBitsPerSample <= 16)
	{
		// 16bps
		sampleBytes = 2;
	}
	else if(headerChunk.wBitsPerSample <= 24)
	{
		// 24bps
		sampleBytes = 3;
	}
	else if(headerChunk.wBitsPerSample <= 32)
	{
		// 32bps
		sampleBytes = 4;
	}
	else
	{	
		// more than 32bit sound, not supported, Exit
		return OPEN_FAILURE;
	}
	
	// Jump to data chunk
	_sourceFile->seek((headerChunk.chunkSize + 8) + 12, 0);
	
	// Again, strict wav parsing, check for data chunk
	char fmtHeader[5];
	_sourceFile->read(fmtHeader, 1, 4);
	fmtHeader[4] = 0;
	
	// Do the check
	if(strcmp(fmtHeader, "data") != 0)
	{	
		// wrong chunk next, sorry, doing strict only, Exit
		return OPEN_FAILURE;
	}
	
	// Grab the data length
	uint32_t len;
	_sourceFile->read(&len, 4, 1);
	len = longSwap(len);
	
	// Figure out the data start and end from length and current position
	dataChunkStart = _sourceFile->tell();
	dataChunkEnd = dataChunkStart + len;
	
	// Grab frequency
	frequency = headerChunk.dwSamplesPerSec;
	
	// Return success
	return OPEN_SUCCESS;
}

int WavSource::renderChunk(uint8_t *buf, int &renderSamples)
{
	// Ensure no blips at end
	memset(buf, 0, renderSamples * 4);
	
	// Render data (16bit stereo, so 2 bytes per sample, 2 channels)
	switch(sampleBytes)
	{
		case 1:
			if(numChannels == 2)
			{
				// Stereo is slightly easier than mono, but everything still has to be byteswapped
				int readAmount = renderSamples * 2;
				int dataEnd = 0;
				
				if(_sourceFile->tell() + readAmount > dataChunkEnd)
				{
					// End of data, truncate read
					readAmount = dataChunkEnd - _sourceFile->tell();
					dataEnd = 1;
				}
				
				// Space
				uint8_t *tBuf = (uint8_t *)memalign(32, renderSamples * 2);
				memset(tBuf, 0, renderSamples * 2);
				
				// Grab data
				if(_sourceFile->read(tBuf, readAmount, 1) == 0) { dataEnd = 1; }
				
				// Hit every number of samples twice (stereo) and convert from 8bit to 16bit
				uint16_t *tData = (uint16_t *)buf;
				
				for(int i = 0; i < (renderSamples * 2); i++)
				{
					// Do actual conversion
					tData[i] = (tBuf[i] - 128) << 8;
				}
				
				// Free memory
				free(tBuf);
				
				if(dataEnd)
				{
					// No more data available
					return 0;
				}
			}
			else
			{
				// Read in single channel, then copy to both channels on buffer
				int readAmount = renderSamples;
				int dataEnd = 0;
				
				if(_sourceFile->tell() + readAmount > dataChunkEnd)
				{
					// End of data, truncate read
					readAmount = dataChunkEnd - _sourceFile->tell();
					dataEnd = 1;
				}
				
				// Allocate space
				uint8_t *tBuf = (uint8_t *)memalign(32, renderSamples);
				memset(tBuf, 0, renderSamples);
				
				// Grab actual data
				if(_sourceFile->read(tBuf, readAmount, 1) == 0) { dataEnd = 1; }
				
				// Copy data over
				for(int i = 0; i < renderSamples; i++)
				{
					uint16_t *tOutBuf = (uint16_t *)&buf[i << 2];
					
					// Precalc swap
					uint16_t tSample = (tBuf[i] - 128) << 8;
					
					// Twice, one for each channel
					tOutBuf[0] = tSample;
					tOutBuf[1] = tSample;
				}
				
				// Free memory
				free(tBuf);
				
				if(dataEnd)
				{
					// No more data available
					return 0;
				}
			}
			
			break;
		case 2:
			if(numChannels == 2)
			{
				// Stereo is slightly easier than mono, but everything still has to be byteswapped
				int readAmount = renderSamples * 4;
				int dataEnd = 0;
				
				if(_sourceFile->tell() + readAmount > dataChunkEnd)
				{
					// End of data, truncate read
					readAmount = dataChunkEnd - _sourceFile->tell();
					dataEnd = 1;
				}
				
				// Grab actual data
				if(_sourceFile->read(buf, readAmount, 1) == 0) { dataEnd = 1; }
				
				// Byteswap it
				uint16_t *tData = (uint16_t *)buf;
				
				// Hit every number of samples twice (stereo)
				for(int i = 0; i < (renderSamples * 2); i++)
				{
					// Do actual swap
					tData[i] = shortSwap(tData[i]);
				}
				
				if(dataEnd)
				{
					// No more data available
					return 0;
				}
			}
			else
			{
				// Read in single channel, then copy to both channels on buffer
				int readAmount = renderSamples * 2;
				int dataEnd = 0;
				
				if(_sourceFile->tell() + readAmount > dataChunkEnd)
				{
					// End of data, truncate read
					readAmount = dataChunkEnd - _sourceFile->tell();
					dataEnd = 1;
				}
				
				// Allocate space
				uint16_t *tBuf = (uint16_t *)memalign(32, renderSamples * 2);
				memset(tBuf, 0, renderSamples * 2);
				
				// Grab actual data
				if(_sourceFile->read(tBuf, readAmount, 1) == 0) { dataEnd = 1; }
				
				// Copy data over
				for(int i = 0; i < renderSamples; i++)
				{
					uint16_t *tOutBuf = (uint16_t *)&buf[i << 2];
					
					// Precalc swap
					uint16_t tSample = shortSwap(tBuf[i]);
					
					// Twice, one for each channel
					tOutBuf[0] = tSample;
					tOutBuf[1] = tSample;
				}
				
				// Free memory
				free(tBuf);
				
				if(dataEnd)
				{
					// No more data available
					return 0;
				}
			}
			
			break;
		case 3:
			if(numChannels == 2)
			{
				// Stereo is slightly easier than mono, but everything still has to be converted
				int readAmount = renderSamples * 6;
				int dataEnd = 0;
				
				if(_sourceFile->tell() + readAmount > dataChunkEnd)
				{
					// End of data, truncate read
					readAmount = dataChunkEnd - _sourceFile->tell();
					dataEnd = 1;
				}
				
				// Space
				uint8_t *tBuf = (uint8_t *)memalign(32, renderSamples * 6);
				memset(tBuf, 0, renderSamples * 6);
				
				// Grab data
				if(_sourceFile->read(tBuf, readAmount, 1) == 0) { dataEnd = 1; }
				
				// Hit every number of samples twice (stereo) and convert from 8bit to 16bit
				uint16_t *tData = (uint16_t *)buf;
				
				for(int i = 0; i < (renderSamples * 2); i++)
				{
					// Do actual conversion
					tData[i] = (tBuf[(i * 3) + 2] << 8) | tBuf[(i * 3) + 1];
				}
				
				// Free memory
				free(tBuf);
				
				if(dataEnd)
				{
					// No more data available
					return 0;
				}
			}
			else
			{
				// Read in single channel, then copy to both channels on buffer
				int readAmount = renderSamples * 3;
				int dataEnd = 0;
				
				if(_sourceFile->tell() + readAmount > dataChunkEnd)
				{
					// End of data, truncate read
					readAmount = dataChunkEnd - _sourceFile->tell();
					dataEnd = 1;
				}
				
				// Allocate space
				uint8_t *tBuf = (uint8_t *)memalign(32, renderSamples * 3);
				memset(tBuf, 0, renderSamples * 3);
				
				// Grab actual data
				if(_sourceFile->read(tBuf, readAmount, 1) == 0) { dataEnd = 1; }
				
				// Copy data over
				for(int i = 0; i < renderSamples; i++)
				{
					uint16_t *tOutBuf = (uint16_t *)&buf[i << 2];
					
					// Precalc swap
					uint16_t tSample = (tBuf[(i * 3) + 2] << 8) | tBuf[(i * 3) + 1];
					
					// Twice, one for each channel
					tOutBuf[0] = tSample;
					tOutBuf[1] = tSample;
				}
				
				// Free memory
				free(tBuf);
				
				if(dataEnd)
				{
					// No more data available
					return 0;
				}
			}
			
			break;
		case 4:
			// Wii SD is not fast enough to stream stereo 32bit wav, but other data sources might be, so leaving it in
			if(numChannels == 2)
			{
				// Read in u32 samples, then convert to 16bit
				int readAmount = renderSamples * 8;
				int dataEnd = 0;
				
				if(_sourceFile->tell() + readAmount > dataChunkEnd)
				{
					// End of data, truncate read
					readAmount = dataChunkEnd - _sourceFile->tell();
					dataEnd = 1;
				}
				
				// Space
				uint16_t *tBuf = (uint16_t *)memalign(32, renderSamples * 8);
				memset(tBuf, 0, renderSamples * 8);
				
				// Grab data
				if(_sourceFile->read(tBuf, readAmount, 1) == 0) { dataEnd = 1; }
				
				// Hit every number of samples twice (stereo) and convert from 32bit to 16bit
				uint16_t *tData = (uint16_t *)buf;
				
				for(int i = 0; i < (renderSamples * 2); i++)
				{
					// Do actual conversion
					tData[i] = shortSwap(tBuf[(i << 1) + 1]);
				}
				
				// Free memory
				free(tBuf);
				
				if(dataEnd)
				{
					// No more data available
					return 0;
				}
			}
			else
			{
				// Read in single channel, then copy to both channels on buffer
				int readAmount = renderSamples * 4;
				int dataEnd = 0;
				
				if(_sourceFile->tell() + readAmount > dataChunkEnd)
				{
					// End of data, truncate read
					readAmount = dataChunkEnd - _sourceFile->tell();
					dataEnd = 1;
				}
				
				// Allocate space
				uint16_t *tBuf = (uint16_t *)memalign(32, renderSamples * 4);
				memset(tBuf, 0, renderSamples * 4);
				
				// Grab actual data
				if(_sourceFile->read(tBuf, readAmount, 1) == 0) { dataEnd = 1; }
				
				// Copy data over
				for(int i = 0; i < renderSamples; i++)
				{
					uint16_t *tOutBuf = (uint16_t *)&buf[i << 2];
					
					// Precalc swap
					uint16_t tSample = shortSwap(tBuf[(i << 1) + 1]);
					
					// Twice, one for each channel
					tOutBuf[0] = tSample;
					tOutBuf[1] = tSample;
				}
				
				// Free memory
				free(tBuf);
				
				if(dataEnd)
				{
					// No more data available
					return 0;
				}
			}
			
			break;
		default:
			// Never hits here, but just in case
			break;
	}
	
	// More data is available
	return 1;
}

void WavSource::seekSeconds(int seconds)
{
	// Figure out seeking by seconds
	_sourceFile->seek(((seconds * frequency) * numChannels * sampleBytes) + dataChunkStart, 0);
}

int WavSource::tellSeconds()
{
	// Calculate number of seconds in
	return (((_sourceFile->tell() - dataChunkStart) / numChannels) / sampleBytes) / frequency;
}

int WavSource::getLength()
{
	// Length in seconds to sound processor	
	return (((dataChunkEnd - dataChunkStart) / numChannels) / sampleBytes) / frequency;
}

uint32_t WavSource::getFrequency()
{
	// Send back pre-fetched frequency of playback
	return frequency;
}

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