/**
* Copyright (c) 2006-2012 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty.  In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
*    claim that you wrote the original software. If you use this software
*    in a product, an acknowledgment in the product documentation would be
*    appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
*    misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/

#include "Audio.h"
#include <common/delay.h>

#include <sound/Decoder.h>

namespace love
{
namespace audio
{
namespace openal
{
	Audio::PoolThread::PoolThread(Pool* pool)
	: pool(pool), finish(false)
	{
	}

	void Audio::PoolThread::main()
	{
		while (true)
		{
			{
				thread::Lock lock(mutex);
				if (finish)
				{
					return;
				}
			}

			pool->update();
			delay(5);
		}
	}

	void Audio::PoolThread::setFinish()
	{
		thread::Lock lock(mutex);
		finish = true;
	}


	Audio::Audio() : distanceModel(DISTANCE_INVERSE_CLAMPED)
	{
		// Passing zero for default device.
		device = alcOpenDevice(0);

		if (device == 0)
			throw love::Exception("Could not open device.");

		context = alcCreateContext(device, 0);

		if (context == 0)
			throw love::Exception("Could not create context.");

		alcMakeContextCurrent(context);

		if (alcGetError(device) != ALC_NO_ERROR)
			throw love::Exception("Could not make context current.");

		/*std::string captureName(alcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER));
		const ALCchar * devices = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
		while (*devices)
		{
			std::string device(devices);
			devices += device.size() + 1;
			if (device.find("Mic") != std::string::npos || device.find("mic") != std::string::npos)
			{
				captureName = device;
			}
		}

		capture = alcCaptureOpenDevice(captureName.c_str(), 8000, AL_FORMAT_MONO16, 262144); // about 32 seconds

		if (!capture)
		{
			// We're not going to prevent LOVE from running without a microphone, but we should warn, at least
			std::cerr << "Warning, couldn't open capture device! No audio input!" << std::endl;
		}*/

		// pool must be allocated after AL context.
		pool = new Pool();

		poolThread = new PoolThread(pool);
		poolThread->start();
	}

	Audio::~Audio()
	{
		poolThread->setFinish();
		poolThread->wait();

		delete poolThread;
		delete pool;

		alcMakeContextCurrent(0);
		alcDestroyContext(context);
		//if (capture) alcCaptureCloseDevice(capture);
		alcCloseDevice(device);
	}


	const char * Audio::getName() const
	{
		return "love.audio.openal";
	}

	love::audio::Source * Audio::newSource(love::sound::Decoder * decoder)
	{
		return new Source(pool, decoder);
	}

	love::audio::Source * Audio::newSource(love::sound::SoundData * soundData)
	{
		return new Source(pool, soundData);
	}

	int Audio::getNumSources() const
	{
		return pool->getNumSources();
	}

	int Audio::getMaxSources() const
	{
		return pool->getMaxSources();
	}

	void Audio::play(love::audio::Source * source)
	{
		source->play();
	}

	void Audio::stop(love::audio::Source * source)
	{
		source->stop();
	}

	void Audio::stop()
	{
		pool->stop();
	}

	void Audio::pause(love::audio::Source * source)
	{
		source->pause();
	}

	void Audio::pause()
	{
		pool->pause();
	}

	void Audio::resume(love::audio::Source * source)
	{
		source->resume();
	}

	void Audio::resume()
	{
		pool->resume();
	}

	void Audio::rewind(love::audio::Source * source)
	{
		source->rewind();
	}

	void Audio::rewind()
	{
		pool->rewind();
	}

	void Audio::setVolume(float volume)
	{
		alListenerf(AL_GAIN, volume);
	}

	float Audio::getVolume() const
	{
		ALfloat volume;
		alGetListenerf(AL_GAIN, &volume);
		return volume;
	}

	void Audio::getPosition(float * v) const
	{
		alGetListenerfv(AL_POSITION, v);
	}

	void Audio::setPosition(float * v)
	{
		alListenerfv(AL_POSITION, v);
	}

	void Audio::getOrientation(float * v) const
	{
		alGetListenerfv(AL_ORIENTATION, v);
	}

	void Audio::setOrientation(float * v)
	{
		alListenerfv(AL_ORIENTATION, v);
	}

	void Audio::getVelocity(float * v) const
	{
		alGetListenerfv(AL_VELOCITY, v);
	}

	void Audio::setVelocity(float * v)
	{
		alListenerfv(AL_VELOCITY, v);
	}

	void Audio::record()
	{
		if (!canRecord()) return;
		alcCaptureStart(capture);
	}

	love::sound::SoundData * Audio::getRecordedData()
	{
		if (!canRecord())
			return NULL;
		int samplerate = 8000;
		ALCint samples;
		alcGetIntegerv(capture, ALC_CAPTURE_SAMPLES, 4, &samples);
		void * data = malloc(samples * (2/sizeof(char)));
		alcCaptureSamples(capture, data, samples);
		love::sound::SoundData * sd = new love::sound::SoundData(data, samples, samplerate, 16, 1);
		free(data);
		return sd;
	}

	love::sound::SoundData * Audio::stopRecording(bool returnData)
	{
		if (!canRecord())
			return NULL;
		love::sound::SoundData * sd = NULL;
		if (returnData)
		{
			sd = getRecordedData();
		}
		alcCaptureStop(capture);
		return sd;
	}

	bool Audio::canRecord()
	{
		return (capture != NULL);
	}
	
	Audio::DistanceModel Audio::getDistanceModel() const
	{
		return this->distanceModel;
	}

	void Audio::setDistanceModel(DistanceModel distanceModel)
	{
		this->distanceModel = distanceModel;
		
		switch (distanceModel)
		{
		case DISTANCE_NONE:
			alDistanceModel(AL_NONE);
			break;
			
		case DISTANCE_INVERSE:
			alDistanceModel(AL_INVERSE_DISTANCE);
			break;

		case DISTANCE_INVERSE_CLAMPED:
			alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
			break;
			
		case DISTANCE_LINEAR:
			alDistanceModel(AL_LINEAR_DISTANCE);
			break;

		case DISTANCE_LINEAR_CLAMPED:
			alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED);
			break;
			
		case DISTANCE_EXPONENT:
			alDistanceModel(AL_EXPONENT_DISTANCE);
			break;

		case DISTANCE_EXPONENT_CLAMPED:
			alDistanceModel(AL_EXPONENT_DISTANCE_CLAMPED);
			break;

		default:
			break;
		}
	}
} // openal
} // audio
} // love
