#include <string.h>
#include "SoundMixer.hpp"

SoundMixer::SoundMixer(void)
:use_sd_fat(false)
#ifdef USE_SDFAT
,sd_fat()
#endif
{
	for(int i = 0; i < CHANNEL_COUNT; ++ i)
	{
		this->buffer[i] = NULL;
		this->size[i] = 0;
		this->sample[i] = 0;
		this->loop[i] = false;
	}
#ifdef USE_SDFAT
	for(int i = 0; i < CHANNEL_COUNT; ++ i)
	{
		this->sd_file[i] = NULL;
	}
	if(this->sd_fat.begin(10, SPI_FULL_SPEED))
	{
		this->use_sd_fat = true;
	}
#endif
}

SoundMixer::~SoundMixer(void)
{
	Clear();
}

void SoundMixer::Append(int channel, const unsigned char* buffer, int size, bool loop)
{
#ifdef USE_SDFAT
	this->sd_file[channel] = NULL;
#endif
	this->buffer[channel] = buffer;
	this->size[channel] = size;
	this->sample[channel] = 0;
	this->loop[channel] = loop;
}

void SoundMixer::Append(int channel, const char* path, bool loop)
{
	if(this->use_sd_fat == false)
	{
		return;
	}
#ifdef USE_SDFAT
	SdFile* sd_file = new SdFile;
	if(sd_file->open(path, O_READ))
	{
		this->sd_file[channel] = sd_file;
	}
	else
	{
		this->sd_file[channel] = NULL;
	}
#endif
	this->buffer[channel] = NULL;
	this->size[channel] = 0;
	this->sample[channel] = 0;
	this->loop[channel] = loop;
}

void SoundMixer::Delete(int channel)
{
#ifdef USE_SDFAT
	if(this->sd_file[channel] != NULL)
	{
		this->sd_file[channel]->close();
		delete this->sd_file[channel];
		this->sd_file[channel] = NULL;
	}
#endif
	this->buffer[channel] = NULL;
	this->size[channel] = 0;
	this->sample[channel] = 0;
	this->loop[channel] = false;
}

void SoundMixer::Read(unsigned char* buffer, int size)
{
	memset(buffer, 0x80, size);
	for(int channel = 0; channel < CHANNEL_COUNT; ++ channel)
	{
		if(this->buffer[channel] == NULL)
		{
			// SD card file
			if(this->use_sd_fat == false)
			{
				// not use SD card
				continue;
			}
#ifdef USE_SDFAT
			if(this->sd_file[channel] == NULL)
			{
				// this channel is free
				continue;
			}
			int sd_file_size = static_cast<int>(this->sd_file[channel]->fileSize());
			int mix_size = size;
			int left_size = sd_file_size - this->sample[channel];
			if(left_size < size)
			{
				mix_size = left_size;
			}
			if(mix_size > 0)
			{
				unsigned char sd_read_buffer[size];
				this->sd_file[channel]->read(sd_read_buffer, mix_size);
				MixBuffer(buffer, sd_read_buffer, mix_size);
				this->sample[channel] += mix_size;
				if(this->loop[channel] == true)
				{
					if(size > left_size)
					{
						int left_mix_size = size - left_size;
						if(left_mix_size > 0)
						{
							this->sd_file[channel]->rewind();
							this->sample[channel] = 0;
							this->sd_file[channel]->read(sd_read_buffer, left_mix_size);
							MixBuffer(&buffer[mix_size], sd_read_buffer, left_mix_size);
							this->sample[channel] += left_mix_size;
						}
					}
				}
			}
			if(this->sample[channel] >= sd_file_size)
			{
				this->sd_file[channel]->rewind();
				this->sample[channel] = 0;
				if(this->loop[channel] == false)
				{
					Delete(channel);
				}
			}
#endif
		}
		else
		{
			// Memory data
			int mix_size = size;
			int left_size = this->size[channel] - this->sample[channel];
			if(left_size < size)
			{
				mix_size = left_size;
			}
			if(mix_size > 0)
			{
				MixBuffer(buffer, &this->buffer[channel][this->sample[channel]], mix_size);
				this->sample[channel] += mix_size;
				if(this->loop[channel] == true)
				{
					if(size > left_size)
					{
						int left_mix_size = size - left_size;
						if(left_mix_size > 0)
						{
							this->sample[channel] = 0;
							MixBuffer(&buffer[mix_size], &this->buffer[channel][this->sample[channel]], left_mix_size);
							this->sample[channel] += left_mix_size;
						}
					}
				}
			}
			if(this->sample[channel] >= this->size[channel])
			{
				this->sample[channel] = 0;
				if(this->loop[channel] == false)
				{
					Delete(channel);
				}
			}
		}
	}
}

void SoundMixer::Clear(void)
{
	for(int channel = 0; channel < CHANNEL_COUNT; ++ channel)
	{
		Delete(channel);
	}
}

void SoundMixer::MixBuffer(unsigned char* destination_buffer, const unsigned char* source_buffer, int size)
{
	for(int sample = 0; sample < size; ++ sample)
	{
		short data = static_cast<short>(source_buffer[sample]) + static_cast<short>(destination_buffer[sample]) - 128;
		data = ((data > 0) ? ((data < 255) ? data : 255) : 0);
		destination_buffer[sample] = static_cast<unsigned char>(data);
	}
}
