//	DwindlingSounder.cpp - The Sounder for my Dwindling Worlds game.
//  ----------------------------------------------------------------------------
//	This file is part of 'NiallsAVLib', base code for any kind of audiovisual
//	apps.
//	Copyright (C) 2012  Niall Moody
//	
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	This program 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 General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.
//	----------------------------------------------------------------------------

#include "sound/SoundFileLoader.h"
#include "DwindlingSounder.h"
#include "DebugHeaders.h"
#include "Random.h"

#include <cassert>

using namespace std;

//------------------------------------------------------------------------------
DwindlingSounder::DwindlingSounder():
Thread("Dwindling Worlds Sounder"),
noiseOn(true),
stuckBufferOn(false),
timeToStop(false),
barStartCallback(0),
generateCallbacks(false),
barPos(0.0f)
{
	startThread(this);
}

//------------------------------------------------------------------------------
DwindlingSounder::~DwindlingSounder()
{
	map<wstring, PreLoadedBuffer>::iterator it;

	timeToStop = true;
	Thread::wait(300);

	for(it=preLoadedBuffers.begin();
		it!=preLoadedBuffers.end();
		++it)
	{
		delete [] it->second.buffer;
	}
}

//------------------------------------------------------------------------------
void DwindlingSounder::getAudio(float *input, float *output, int numSamples)
{
	int i;
	bool soundsPlaying = false;

	if(generateCallbacks)
	{
		for(i=0;i<numSamples;++i)
		{
			barPos += (1.0f/44100.0f);
			if(barPos >= (4.0f*(60.0f/120.0f))) //4 beats * (num seconds in a minute/beats per minute)
			{
				barPos -= (4.0f*(60.0f/120.0f));
				if(barStartCallback)
					barStartCallback->barStart();
			}
		}
	}

	for(i=0;i<NumSounds;++i)
	{
		if(sounds[i].getActive())
		{
			soundsPlaying = true;

			sounds[i].getAudio(output, numSamples);
		}
	}

	//Play noise.
	if(noiseOn)
	{
		if(soundsPlaying)
			noise.setAttenuate(true);
		else
			noise.setAttenuate(false);
		noise.getAudio(output, numSamples);
	}

	//Add any stuck buffer effect.
	if(stuckBufferOn)
		stuckBuffer.getAudio(output, numSamples);
}

//------------------------------------------------------------------------------
void DwindlingSounder::loadSoundFileResource(SoundFileLoaderPtr soundFileLoader)
{
	int i, j;
	const int64_t fileLength = soundFileLoader->getFileLength();
	const int64_t shortFileMaxSize = (2 * 44100);

	soundFiles.insert(make_pair(soundFileLoader->getName(), soundFileLoader));

	if(fileLength < shortFileMaxSize)
	{
		int64_t bufPos;
		PreLoadedBuffer buffer;

		buffer.buffer = new float[(uint32_t)fileLength * 2];
		buffer.size = (int)fileLength * 2;

		bufPos = 0;
		while(bufPos < fileLength)
		{
			bufPos = soundFileLoader->loadChunk(buffer.buffer,
												bufPos,
												buffer.size,
												0);
		}

		//Convert mono files to stereo.
		if(soundFileLoader->getNumChannels() < 2)
		{
			for(j=(buffer.size-2);j>=0;j-=2)
			{
				buffer.buffer[j] = buffer.buffer[j/2];
				buffer.buffer[j+1] = buffer.buffer[j];
			}
		}

		preLoadedBuffers.insert(make_pair(soundFileLoader->getName(), buffer));
	}
	else
	{
		int bufPos;
		int64_t tempPos;
		vector<vector<float> >& initialBuffers = soundFileLoader->getInitialBuffers();
		float tempBuf[StreamingSound::NumBuffers][StreamingSound::BufferSize*2];

		for(i=0;i<StreamingSound::NumBuffers;++i)
		{
			bufPos = 0;
			while(bufPos < StreamingSound::BufferSize)
			{
				tempPos = (StreamingSound::BufferSize * i) + bufPos;

				bufPos = soundFileLoader->loadChunk(tempBuf[i],
													tempPos,
													(StreamingSound::BufferSize-bufPos) * 2,
													bufPos);
			}
		}

		//Convert mono files to stereo.
		if(soundFileLoader->getNumChannels() < 2)
		{
			for(i=0;i<StreamingSound::NumBuffers;++i)
			{
				for(j=(StreamingSound::BufferSize * 2)-2;j>=0;j-=2)
				{
					tempBuf[i][j] = tempBuf[i][j/2];
					tempBuf[i][j+1] = tempBuf[i][j];
				}
			}
		}

		//Just in case.
		for(i=0;i<(int)initialBuffers.size();++i)
			initialBuffers[i].clear();
		initialBuffers.clear();

		//Copy into soundFileLoader's initialBuffers.
		for(i=0;i<StreamingSound::NumBuffers;++i)
		{
			initialBuffers.push_back(vector<float>());

			for(j=0;j<(StreamingSound::BufferSize * 2);++j)
				initialBuffers[i].push_back(tempBuf[i][j]);
		}
	}
}

//------------------------------------------------------------------------------
bool DwindlingSounder::run()
{
	int i;

	while(!timeToStop)
	{
		for(i=0;i<NumSounds;++i)
		{
			if(sounds[i].getActive())
				sounds[i].threadUpdate();
		}

		//Wait a bit before looping.
		Thread::wait(62);
	}

	return true;
}

//------------------------------------------------------------------------------
int DwindlingSounder::activateSound(const wstring& sound)
{
	int i = -1;

	//If it's a short sound, set it up accordingly.
	if(preLoadedBuffers.find(sound) != preLoadedBuffers.end())
	{
		for(i=0;i<NumSounds;++i)
		{
			if(!sounds[i].getActive())
			{
				sounds[i].setFade(true);
				sounds[i].setLoop(true);
				sounds[i].setLevel(0.66f);
				sounds[i].startPlaying(preLoadedBuffers[sound].buffer,
									   preLoadedBuffers[sound].size);

				break;
			}
		}
	}
	//Ditto if it's a longer file.
	else if(soundFiles.find(sound) != soundFiles.end())
	{
		for(i=0;i<NumSounds;++i)
		{
			if(!sounds[i].getActive())
			{
				sounds[i].setFade(true);
				sounds[i].setLoop(true);
				sounds[i].setLevel(0.66f);
				sounds[i].startPlaying(soundFiles[sound]);

				break;
			}
		}
	}
	else
		DebugStatement(L"DwindlingSounder: Could not find sound " << sound << " to activate.");

	return i;
}

//------------------------------------------------------------------------------
void DwindlingSounder::deactivateSound(int index)
{
	if(index > -1)
	{
		if(sounds[index].getActive())
		{
			sounds[index].setFade(true);
			sounds[index].stopPlaying();
		}
	}
}

//------------------------------------------------------------------------------
void DwindlingSounder::killAllSounds()
{
	int i;

	for(i=0;i<NumSounds;++i)
	{
		if(sounds[i].getActive())
		{
			sounds[i].setFade(true);
			sounds[i].setLevel(1.0f);
			sounds[i].stopPlaying();
		}
	}
}

//------------------------------------------------------------------------------
void DwindlingSounder::triggerSound(const std::wstring& sound, float pan)
{
	int i;

	//If it's a short sound, set it up accordingly.
	if(preLoadedBuffers.find(sound) != preLoadedBuffers.end())
	{
		for(i=0;i<NumSounds;++i)
		{
			if(!sounds[i].getActive())
			{
				sounds[i].setFade(false);
				sounds[i].setLoop(false);
				sounds[i].setPan(pan);
				sounds[i].setLevel(4.0f);
				sounds[i].startPlaying(preLoadedBuffers[sound].buffer,
									   preLoadedBuffers[sound].size);

				break;
			}
		}
	}
	//Ditto if it's a longer file.
	else if(soundFiles.find(sound) != soundFiles.end())
	{
		for(i=0;i<NumSounds;++i)
		{
			if(!sounds[i].getActive())
			{
				sounds[i].setFade(false);
				sounds[i].setLoop(false);
				sounds[i].setPan(pan);
				sounds[i].setLevel(4.0f);
				sounds[i].startPlaying(soundFiles[sound]);

				break;
			}
		}
	}
	else
		DebugStatement(L"DwindlingSounder: Could not find sound " << sound << " to play as a one-shot.");
}

//------------------------------------------------------------------------------
void DwindlingSounder::playMusic(const std::wstring& sound)
{
	int i;

	//If it's a short sound, set it up accordingly.
	if(preLoadedBuffers.find(sound) != preLoadedBuffers.end())
	{
		for(i=0;i<NumSounds;++i)
		{
			if(!sounds[i].getActive())
			{
				sounds[i].setFade(false);
				sounds[i].setLoop(true);
				sounds[i].setLevel(1.5f);
				sounds[i].startPlaying(preLoadedBuffers[sound].buffer,
									   preLoadedBuffers[sound].size);
				musicIndex = i;

				break;
			}
		}
	}
	//Ditto if it's a longer file.
	else if(soundFiles.find(sound) != soundFiles.end())
	{
		for(i=0;i<NumSounds;++i)
		{
			if(!sounds[i].getActive())
			{
				sounds[i].setFade(false);
				sounds[i].setLoop(true);
				sounds[i].setLevel(1.5f);
				sounds[i].startPlaying(soundFiles[sound]);
				musicIndex = i;

				break;
			}
		}
	}
	else
		DebugStatement(L"DwindlingSounder: Could not find sound " << sound << " to play as a one-shot.");

	generateCallbacks = true;
}

//------------------------------------------------------------------------------
void DwindlingSounder::stopMusic()
{
	sounds[musicIndex].setFade(true);
	sounds[musicIndex].stopPlaying();
}

//------------------------------------------------------------------------------
void DwindlingSounder::setNoiseLevel(float val)
{
	noise.setLevel(val);
}

//------------------------------------------------------------------------------
void DwindlingSounder::killNoise()
{
	noiseOn = false;
}

//------------------------------------------------------------------------------
void DwindlingSounder::killStuckBuffer()
{
	stuckBufferOn = false;
}

//------------------------------------------------------------------------------
void DwindlingSounder::setCallback(Callback *c)
{
	barStartCallback = c;
}
