#include <cmath>

#include "SoundBase.h"
#include "SoundBaseGlobal.h"

namespace Engine {

static SoundBase* SoundInstance = NULL;

/* Sound Sample */
SoundSample::~SoundSample() {
	Stop();

	Mix_FreeChunk(mMixChunk);
	mMixChunk = NULL;
}

bool SoundSample::Load (const std::string &filename) {
	assert (mMixChunk == NULL);

	LogDebug("Loading SoundSample %s", filename.c_str());
	mMixChunk = Mix_LoadWAV(filename.c_str());

	if (mMixChunk == NULL) {
		LogError("Loading of SoundSample %s failed: %s", filename.c_str(), Mix_GetError());
		return false;
	}

	return true;
}

void SoundSample::Play() {
	mChannel = Mix_PlayChannel(-1, mMixChunk, 0);
}

void SoundSample::Loop(int count) {
	mChannel = Mix_PlayChannel(-1, mMixChunk, count);
}

void SoundSample::Stop() {
	if (mChannel != -1)
		Mix_HaltChannel(mChannel);
}

int SoundBase::OnInit(int argc, char* argv[]) {
	SoundInstance = this;

	int audio_rate = 22050;
	Uint16 audio_format = AUDIO_S16;
	int audio_channels = 2;
	int audio_buffers = 1024;

	LogDebug("Sound Init");
	if (Mix_OpenAudio (audio_rate, audio_format, audio_channels, audio_buffers)) {
		LogError ("Unable to initialize the sound system! Error message: %s", SDL_GetError());
	}

	Mix_QuerySpec (&audio_rate, &audio_format, &audio_channels);
	LogDebug("Sound: rate: %d channels: %d format: %d", audio_rate, audio_channels, audio_format);

	mCurrentMusicName = "";
	mCurrentMusic = NULL;

	Mix_VolumeMusic(MIX_MAX_VOLUME/2);

	return 0;
}

void SoundBase::OnDestroy() {
	LogDebug ("Sound Destroy");

	// stop music
	HaltMusic();

	// stop and clear all samples
	mSamples.clear();

	Mix_CloseAudio();

	SoundInstance = NULL;
}

/* Module specific functions */
void SoundBase::PlaySound (const std::string &sound_name) {
	if (!SoundInstance) {
		LogError("Could not play sound %s: sound system not initialized!",
				sound_name.c_str());
	}

	SampleIter iter = mSamples.find(sound_name);	

	if (iter == mSamples.end()) {
		SoundSamplePtr sample (new SoundSample);
		sample->Load(sound_name);
		mSamples[sound_name] = sample;

		iter = mSamples.find(sound_name);
	}

	iter->second->Play();
}

void SoundBase::PlaySoundLoop (const std::string &sound_name, int count) {
	if (!SoundInstance) {
		LogError("Could not play sound %s: sound system not initialized!",
				sound_name.c_str());
	}

	SampleIter iter = mSamples.find(sound_name);	

	if (iter == mSamples.end()) {
		SoundSamplePtr sample (new SoundSample);
		sample->Load(sound_name);
		mSamples[sound_name] = sample;

		iter = mSamples.find(sound_name);
	}

	iter->second->Loop(count);
}

void SoundBase::HaltSoundLoop (const std::string &sound_name) {
	if (!SoundInstance) {
		LogError("Could not play sound %s: sound system not initialized!",
				sound_name.c_str());
	}

	SampleIter iter = mSamples.find(sound_name);	

	if (iter == mSamples.end()) {
		SoundSamplePtr sample (new SoundSample);
		sample->Load(sound_name);
		mSamples[sound_name] = sample;

		iter = mSamples.find(sound_name);
	}

	iter->second->Stop();
}

void SoundBase::PlayMusic (const std::string &music_name) {
	if (!SoundInstance) {
		LogError("Could not play music %s: sound system not initialized!",
				music_name.c_str());
	}

	if (music_name == mCurrentMusicName)
		return;

	if (mCurrentMusic) {
		Mix_HaltMusic();
		Mix_FreeMusic(mCurrentMusic);
		mCurrentMusic = NULL;
	}

	LogDebug("Loading Music %s", music_name.c_str());
	mCurrentMusic = Mix_LoadMUS(music_name.c_str());

	if (mCurrentMusic == NULL) {
		LogError("Loading of Music failed: %s", Mix_GetError());
		return;
	}

	Mix_PlayMusic(mCurrentMusic, -1);
}

void SoundBase::HaltMusic () {
	if (!SoundInstance) {
		LogError("Could not stop music: sound system not initialized!");
	}

	Mix_HaltMusic();
	Mix_FreeMusic(mCurrentMusic);
	mCurrentMusic = NULL;
}

void SoundBase::SetMusicVolume(const float &music_volume) {
	int mix_volume = static_cast<int> (fabsf(music_volume) * MIX_MAX_VOLUME);

	Mix_VolumeMusic(mix_volume);
}

float SoundBase::GetMusicVolume() {
	return static_cast<float>(Mix_VolumeMusic(-1)) / static_cast<float>(MIX_MAX_VOLUME);
}

void SoundBase::SetEffectsVolume(const float &effects_volume) {
	int mix_volume = static_cast<int> (fabsf(effects_volume) * MIX_MAX_VOLUME);

	Mix_Volume(-1, mix_volume);
}

float SoundBase::GetEffectsVolume() {
	return static_cast<float>(Mix_Volume(-1, -1)) / static_cast<float>(MIX_MAX_VOLUME);
}
/*
 * Global Functions
 */

void PlaySound (const std::string &sound_name) {
	if (!SoundInstance) {
		LogError("Could not play sound %s: sound system not initialized!",
				sound_name.c_str());
	}
	
	SoundInstance->PlaySound(sound_name);
}

void PlayMusic (const std::string &music_name) {
	if (!SoundInstance) {
		LogError("Could not play sound %s: sound system not initialized!",
				music_name.c_str());
	}
	
	SoundInstance->PlayMusic(music_name);
}

void PlaySoundLoop (const std::string &sound_name, int count) {
	if (!SoundInstance) {
		LogError("Could not play sound loop %s: sound system not initialized!",
				sound_name.c_str());
	}

	SoundInstance->PlaySoundLoop(sound_name, count);
}

void HaltSoundLoop (const std::string &sound_name) {
	if (!SoundInstance) {
		LogError("Could not halt sound %s: sound system not initialized!",
				sound_name.c_str());
	}

	SoundInstance->HaltSoundLoop(sound_name);
}

void SetMusicVolume (const float &music_volume) {
	if (!SoundInstance) {
		LogError("Could set music volume: sound system not initialized!");
	}

	SoundInstance->SetMusicVolume(music_volume);
}

float GetMusicVolume () {
	if (!SoundInstance) {
		LogError("Could get music volume: sound system not initialized!");
	}

	return SoundInstance->GetMusicVolume();
}

void SetEffectsVolume (const float &effects_volume) {
	if (!SoundInstance) {
		LogError("Could set effects volume: sound system not initialized!");
	}

	SoundInstance->SetEffectsVolume(effects_volume);
}

float GetEffectsVolume () {
	if (!SoundInstance) {
		LogError("Could get effects volume: sound system not initialized!");
	}

	return SoundInstance->GetEffectsVolume();
}

};
