/*
 * $Id$
 *
 * ***** BEGIN LGPL LICENSE BLOCK *****
 *
 * Copyright 2009 Jörg Hermann Müller
 *
 * This file is part of AudaSpace.
 *
 * AudaSpace is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * AudaSpace is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with AudaSpace.  If not, see <http://www.gnu.org/licenses/>.
 *
 * ***** END LGPL LICENSE BLOCK *****
 */

#include "AUD_SDLMixerReader.h"
#include "AUD_Buffer.h"

#include <cstring>

inline Uint16 AUD_TO_SDL(AUD_SampleFormat format)
{
	// SDL only supports 8 and 16 bit audio
	switch(format)
	{
	case AUD_FORMAT_U8:
		return AUDIO_U8;
	case AUD_FORMAT_S16:
		return AUDIO_S16SYS;
	default:
		AUD_THROW(AUD_ERROR_SDL);
	}
}

// greatest common divisor
inline int gcd(int a, int b)
{
	int c;

	// make sure a is the bigger
	if(b > a)
	{
		c = b;
		b = a;
		a = c;
	}

	// greetings from Euclides
	while(b != 0)
	{
		c = a % b;
		a = b;
		b = c;
	}
	return a;
}

AUD_SDLMixerReader::AUD_SDLMixerReader(AUD_IReader* reader,
											 AUD_Specs specs)
{
	if(reader == NULL)
		AUD_THROW(AUD_ERROR_READER);

	m_reader = reader;
	m_tspecs = specs;
	m_sspecs = reader->getSpecs();

	try
	{
		// SDL only supports 8 and 16 bit sample formats
		if(SDL_BuildAudioCVT(&m_cvt,
							 AUD_TO_SDL(m_sspecs.format),
							 m_sspecs.channels,
							 m_sspecs.rate,
							 AUD_TO_SDL(specs.format),
							 specs.channels,
							 specs.rate) == -1)
			AUD_THROW(AUD_ERROR_SDL);
	}
	catch(AUD_Exception e)
	{
		delete m_reader; AUD_DELETE("reader")
		throw;
	}

	m_eor = false;
	m_rsposition = 0;
	m_rssize = 0;
	m_ssize = m_sspecs.rate / gcd(specs.rate, m_sspecs.rate);
	m_tsize = m_tspecs.rate * m_ssize / m_sspecs.rate;

	m_buffer = new AUD_Buffer(); AUD_NEW("buffer")
	m_rsbuffer = new AUD_Buffer(); AUD_NEW("buffer")
}

AUD_SDLMixerReader::~AUD_SDLMixerReader()
{
	delete m_reader; AUD_DELETE("reader")
	delete m_buffer; AUD_DELETE("buffer")
	delete m_rsbuffer; AUD_DELETE("buffer")
}

bool AUD_SDLMixerReader::isSeekable()
{
	return m_reader->isSeekable();
}

void AUD_SDLMixerReader::seek(int position)
{
	m_reader->seek(position * m_ssize / m_tsize);
	m_eor = false;
}

int AUD_SDLMixerReader::getLength()
{
	return m_reader->getLength() * m_tsize / m_ssize;
}

int AUD_SDLMixerReader::getPosition()
{
	return m_reader->getPosition() * m_tsize / m_ssize;
}

AUD_Specs AUD_SDLMixerReader::getSpecs()
{
	return m_tspecs;
}

AUD_ReaderType AUD_SDLMixerReader::getType()
{
	return m_reader->getType();
}

bool AUD_SDLMixerReader::notify(AUD_Message &message)
{
	return m_reader->notify(message);
}

void AUD_SDLMixerReader::read(int & length, sample_t* & buffer)
{
	// sample count for the target buffer without getting a shift
	int tns = length + m_tsize - length % m_tsize;
	// sample count for the source buffer without getting a shift
	int sns = tns * m_ssize / m_tsize;
	// target sample size
	int tss = AUD_SAMPLE_SIZE(m_tspecs);
	// source sample size
	int sss = AUD_SAMPLE_SIZE(m_sspecs);

	// input is output buffer
	int buf_size = AUD_MAX(tns*tss, sns*sss);

	// resize if necessary
	if(m_rsbuffer->getSize() < buf_size)
		m_rsbuffer->resize(buf_size, true);

	if(m_buffer->getSize() < length*tss)
		m_buffer->resize(length*tss);

	buffer = m_buffer->getBuffer();
	int size;
	int index = 0;
	sample_t* buf;

	while(index < length)
	{
		if(m_rsposition == m_rssize)
		{
			// no more data
			if(m_eor)
				length = index;
			// mix
			else
			{
				// read from source
				size = sns;
				m_reader->read(size, buf);

				// prepare
				m_cvt.buf = m_rsbuffer->getBuffer();
				m_cvt.len = size*sss;
				memcpy(m_cvt.buf, buf, size*sss);

				// convert
				SDL_ConvertAudio(&m_cvt);

				// end of reader
				if(size < sns)
					m_eor = true;

				m_rsposition = 0;
				m_rssize = size * m_tsize / m_ssize;
			}
		}

		// size to copy
		size = AUD_MIN(m_rssize-m_rsposition, length-index);

		// copy
		memcpy(m_buffer->getBuffer() + index * tss,
			   m_rsbuffer->getBuffer() + m_rsposition * tss,
			   size*tss);
		m_rsposition += size;
		index += size;
	}
}
