#include "Sound.hpp"

#include "soloud.h"
#include "soloud_wav.h"
#include "Exception.hpp"
#include <iostream>
#include "soloud_audiosource.h"

using std::unique_ptr;
using std::move;
using std::cout;
using std::endl;
using std::swap;
using namespace glm;

SoundController::SoundController(SoundFile* file, SoLoud::Soloud* engine, bool start_playing, bool play_3d, PlayOptions play_options):
	m_engine(engine),
	m_file(file),
	m_play_3d(play_3d)
{
	if(m_play_3d)
	{
		m_sound = m_engine->play3d(*m_file->m_sound,
								  0, 0, 0,
								  0, 0, 0,
								  play_options.volume, !start_playing);
		m_engine->set3dSourceMinMaxDistance(m_sound, m_file->m_SoundManager->m_default_min_distance, m_file->m_SoundManager->m_default_max_distance);
		m_engine->set3dSourceAttenuation(m_sound, m_file->m_SoundManager->m_default_attenuation_model, m_file->m_SoundManager->m_default_attenuation_value);
	}
	else
		m_sound = m_engine->play(*file->m_sound, play_options.volume, 0.0, !start_playing);

	if(play_options.looped)
	{
		m_engine->setLooping(m_sound, true);
	}
	else
	{
		m_engine->setLooping(m_sound, false);
	}

	if(play_options.priority)
		m_engine->setProtectVoice(m_sound, play_options.priority);
}


SoundController::SoundController(SoundController&& v):
	m_engine(nullptr)
{
	swap(m_engine, v.m_engine);
	swap(m_file, v.m_file);
	swap(m_sound, v.m_sound);
	swap(m_play_3d, v.m_play_3d);
}


SoundController& SoundController::operator=(SoundController&& v)
{
	swap(m_engine, v.m_engine);
	swap(m_file, v.m_file);
	swap(m_sound, v.m_sound);
	swap(m_play_3d, v.m_play_3d);

	return *this;
}

SoundController::~SoundController()
{
	if(m_engine)
		m_engine->stop(m_sound);
}


void SoundController::Restart(PlayOptions options)
{
	// start the sound new
	if(!m_engine->isValidVoiceHandle(m_sound))
	{
		if(m_play_3d)
		{
			m_sound = m_engine->play3d(*m_file->m_sound,
									  0, 0, 0, // position
									  0, 0, 0, // velocity
									  options.volume);
			m_engine->set3dSourceMinMaxDistance(m_sound, m_file->m_SoundManager->m_default_min_distance, m_file->m_SoundManager->m_default_max_distance);
			m_engine->set3dSourceAttenuation(m_sound, m_file->m_SoundManager->m_default_attenuation_model, m_file->m_SoundManager->m_default_attenuation_value);
		}
		else
			m_sound = m_engine->play(*m_file->m_sound, options.volume);
	}
	else
	{
		m_engine->seek(m_sound, 0);
		m_engine->setPause(m_sound, false);
	}

	if(options.looped)
	{
		m_engine->setLooping(m_sound, true);
	}
	else
	{
		m_engine->setLooping(m_sound, false);
	}

	if(options.priority)
		m_engine->setProtectVoice(m_sound, options.priority);
}


bool SoundController::IsPlaying()
{
	if(!m_engine)
		return false;
	if(!m_engine->isValidVoiceHandle(m_sound)) // sound was maybe already terminated.
		return false;
	return ! m_engine->getPause(m_sound);
}


void SoundController::Stop()
{
	m_engine->stop(m_sound);
}


void SoundController::SetRandomPosition()
{
	auto len = m_file->m_sound->getLength();
	std::uniform_int_distribution<> dist(0, len);
	m_engine->seek(m_sound, dist(m_file->m_SoundManager->m_rnd));
}

void SoundController::SetPosition(vec3 position)
{
	m_engine->set3dSourcePosition(m_sound, position.x, position.y, position.z);
}

void SoundController::FadeOut(float time)
{
	m_engine->fadeVolume(m_sound, 0, time);
	m_engine->scheduleStop(m_sound, time);
}

//----------------------------------


SoundFile::SoundFile(SoundManager* soundManager, SoLoud::Wav* sound):
	m_SoundManager(soundManager),
	m_engine(&soundManager->m_SoundEngine),
	m_sound(sound)
{
}

SoundFile::SoundFile(SoundFile&& v)
{
	swap(m_SoundManager, v.m_SoundManager);
	swap(m_engine, v.m_engine);
	swap(m_sound, v.m_sound);
}

SoundFile& SoundFile::operator=(SoundFile&& v)
{
	swap(m_SoundManager, v.m_SoundManager);
	swap(m_engine, v.m_engine);
	swap(m_sound, v.m_sound);

	return *this;
}

 
void SoundFile::Play(PlayOptions options)
{
	auto handle = m_engine->play(*m_sound, options.volume);
	if(options.looped)
		m_engine->setLooping(handle, true);

	if(options.priority)
		m_engine->setProtectVoice(handle, options.priority);
}

SoundController SoundFile::PlayControlled(bool start_playing, bool play_3d, PlayOptions play_options)
{
	return SoundController(this, m_engine, start_playing, play_3d, play_options);
}


//---------------------------------

SoundManager::SoundManager():
	m_SoundEngine(),
	m_rnd(std::random_device()())
{
	m_SoundEngine.init(SoLoud::Soloud::CLIP_ROUNDOFF, SoLoud::Soloud::BACKENDS::MINIAUDIO);

	m_SoundEngine.setMaxActiveVoiceCount(64);
}

SoundManager::~SoundManager()
{
	m_SoundEngine.deinit();
}

SoundFile SoundManager::LoadSound(std::string filename)
{
	auto it = m_loaded_sounds.find(filename);

	if(it != m_loaded_sounds.end()) // sound already loaded
	{
		return SoundFile(this, it->second.get());
	}
	else // load sound
	{
		cout << "loading " << filename << endl;
		auto new_sound = std::make_unique<SoLoud::Wav>();

		if(!m_disable_loading)
		{
			auto r = new_sound->load(filename.c_str());
			if(r != SoLoud::SOLOUD_ERRORS::SO_NO_ERROR)
			{
				throw Exception() << ex_message("Sound could not be loaded") << ex_file(filename);
			}
		}

		auto ptr = new_sound.get();
		m_loaded_sounds[filename] = move(new_sound);
		return SoundFile(this, ptr);
	}
}


void SoundManager::SetPosition(vec3 position, vec3 look_dir, vec3 up)
{
	m_SoundEngine.set3dListenerPosition(position.x, position.y, position.z);
	m_SoundEngine.set3dListenerAt(look_dir.x, look_dir.y, look_dir.z);
	m_SoundEngine.set3dListenerUp(up.x, up.y, up.z);
	//const auto velocity = irrklang::vec3df(0, 0, 0);
	//m_SoundEngine->setListenerPosition(Vector(position), Vector(look_dir), velocity, Vector(up));
}

void SoundManager::SetDefaultAttenuation(float min_distance, float max_distance, float value, unsigned int model)
{
	m_default_min_distance = min_distance;
	m_default_max_distance = max_distance;
	m_default_attenuation_value = value;
	m_default_attenuation_model = model;
}

void SoundManager::update()
{
	m_SoundEngine.update3dAudio();
}

void SoundManager::DisableLoading()
{
	m_disable_loading = true;
}
