/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/debug.h"
#include "common/file.h"
#include "common/list.h"
#include "common/memstream.h"

#include "audio/audiostream.h"
#include "audio/decoders/wave.h"
#include "audio/decoders/vorbis.h"
#include "audio/mods/mod_xm_s3m.h"

#include "sludge/allfiles.h"
#include "sludge/newfatal.h"
#include "sludge/sound.h"
#include "sludge/moreio.h"
#include "sludge/fileset.h"
#include "sludge/sludge.h"

namespace Sludge {

const int SoundManager::MAX_SAMPLES = 8;
const int SoundManager::MAX_MODS = 3;

SoundManager::SoundManager() {
	_soundCache = nullptr;
	_soundCache = new SoundThing[MAX_SAMPLES];

	_modCache = nullptr;
	_modCache = new SoundThing[MAX_MODS];

	init();
}

SoundManager::~SoundManager() {
	killSoundStuff();

	delete []_soundCache;
	_soundCache = nullptr;

	delete []_modCache;
	_modCache = nullptr;
}

void SoundManager::init() {
	// there's possibility that several sound list played at the same time
	_soundListHandles.clear();

	_soundOK = false;
	_silenceIKillYou = false;
	_isHandlingSoundList = false;

	_defVol = 128;
	_defSoundVol = 255;
	_modLoudness = 0.95f;

	_emptySoundSlot = 0;
}

bool SoundManager::initSoundStuff() {
	for (int a = 0; a < MAX_SAMPLES; ++a) {
		_soundCache[a].fileLoaded = -1;
		_soundCache[a].looping = false;
		_soundCache[a].inSoundList = false;
	}

	for (int a = 0; a < MAX_MODS; ++a) {
		_modCache[a].fileLoaded = -1;
		_modCache[a].looping = false;
		_modCache[a].inSoundList = false;
	}

	return _soundOK = true;
}

void SoundManager::killSoundStuff() {
	if (!_soundOK)
		return;

	for (int i = 0; i < MAX_SAMPLES; ++i)
		freeSound(i);

	for (int i = 0; i < MAX_MODS; ++i)
		stopMOD(i);
}

/*
 * Some setters:
 */
void SoundManager::setMusicVolume(int a, int v) {
	if (!_soundOK)
		return;

	if (g_sludge->_mixer->isSoundHandleActive(_modCache[a].handle)) {
		_modCache[a].vol = v;
		g_sludge->_mixer->setChannelVolume(_modCache[a].handle, _modLoudness * v);
	}
}

void SoundManager::setDefaultMusicVolume(int v) {
	_defVol = v;
}

void SoundManager::setSoundVolume(int a, int v) {
	if (!_soundOK)
		return;
	int ch = findInSoundCache(a);
	if (ch != -1) {
		if (g_sludge->_mixer->isSoundHandleActive(_soundCache[ch].handle)) {
			_soundCache[ch].vol = v;
			g_sludge->_mixer->setChannelVolume(_soundCache[ch].handle, v);
		}
	}
}

void SoundManager::setDefaultSoundVolume(int v) {
	_defSoundVol = v;
}

void SoundManager::setSoundLoop(int a, int s, int e) {
//#pragma unused (a,s,e)
}

int SoundManager::findInSoundCache(int a) {
	int i;
	for (i = 0; i < MAX_SAMPLES; i++) {
		if (_soundCache[i].fileLoaded == a) {
			return i;
		}
	}
	return -1;
}

void SoundManager::stopMOD(int i) {
	if (!_soundOK)
		return;

	if (_modCache[i].fileLoaded >= 0) {
		if (g_sludge->_mixer->isSoundHandleActive(_modCache[i].handle)) {
			g_sludge->_mixer->stopHandle(_modCache[i].handle);
		}
	}
	_modCache[i].fileLoaded = -1;
}

void SoundManager::huntKillSound(int filenum) {
	if (!_soundOK)
		return;

	int gotSlot = findInSoundCache(filenum);
	if (gotSlot == -1)
		return;

	freeSound(gotSlot);
}

void SoundManager::freeSound(int a) {
	if (!_soundOK)
		return;

	_silenceIKillYou = true;
	if (_soundCache[a].fileLoaded >= 0) {
		if (g_sludge->_mixer->isSoundHandleActive(_soundCache[a].handle)) {
			g_sludge->_mixer->stopHandle(_soundCache[a].handle);
			if (_soundCache[a].inSoundList)
				handleSoundLists();
		}
	}

	_soundCache[a].inSoundList = false;
	_soundCache[a].looping = false;
	_soundCache[a].fileLoaded = -1;

	_silenceIKillYou = false;
}

void SoundManager::huntKillFreeSound(int filenum) {
	if (!_soundOK)
		return;
	int gotSlot = findInSoundCache(filenum);
	if (gotSlot == -1)
		return;
	freeSound(gotSlot);
}

/*
 * Loading and playing:
 */
bool SoundManager::playMOD(int f, int a, int fromTrack) {
	if (!_soundOK)
		return true;
	stopMOD(a);

	// load sound
	setResourceForFatal(f);
	uint length = g_sludge->_resMan->openFileFromNum(f);
	if (length == 0) {
		g_sludge->_resMan->finishAccess();
		setResourceForFatal(-1);
		return false;
	}

	// make audio stream
	Common::SeekableReadStream *readStream = g_sludge->_resMan->getData();
	Common::SeekableReadStream *memImage = readStream->readStream(length);

// debug output
#if 0
	Common::DumpFile *dump = new Common::DumpFile();
	Common::String name = Common::String::format("mod_sound_%i", f);
	dump->open(name);
	byte *soundData = new byte[length];
	memImage->read(soundData, length);
	dump->write(soundData, length);
	dump->finalize();
	delete []soundData;
	delete dump;
	memImage->seek(0, SEEK_SET);
#endif

	if (memImage->size() != (int)length || readStream->err()) {
		return fatal("Sound reading failed");
	}
	Audio::AudioStream *stream = Audio::makeModXmS3mStream(memImage, DisposeAfterUse::NO);

	if (stream) {
		// play sound
		_modCache[a].fileLoaded = f;
		_modCache[a].vol = _defVol;
		g_sludge->_mixer->playStream(Audio::Mixer::kMusicSoundType, &_modCache[a].handle, stream, -1, _modCache[a].vol);
	} else {
		_modCache[a].fileLoaded = -1;
	}

	g_sludge->_resMan->finishAccess();
	setResourceForFatal(-1);
	return true;
}

bool SoundManager::stillPlayingSound(int ch) {
	if (_soundOK)
		if (ch != -1)
			if (_soundCache[ch].fileLoaded != -1)
				if (g_sludge->_mixer->isSoundHandleActive(_soundCache[ch].handle))
					return true;
	return false;
}

bool SoundManager::forceRemoveSound() {
	for (int a = 0; a < MAX_SAMPLES; a++) {
		if (_soundCache[a].fileLoaded != -1) {
			freeSound(a);
			return 1;
		}
	}
	return 0;
}

int SoundManager::findEmptySoundSlot() {
	for (int t = 0; t < MAX_SAMPLES; t++) {
		_emptySoundSlot++;
		_emptySoundSlot %= MAX_SAMPLES;
		if (!g_sludge->_mixer->isSoundHandleActive(_soundCache[_emptySoundSlot].handle) && !_soundCache[_emptySoundSlot].inSoundList)
			return _emptySoundSlot;
	}

	// Argh! They're all playing! Let's trash the oldest that's not looping...

	for (int t = 0; t < MAX_SAMPLES; t++) {
		_emptySoundSlot++;
		_emptySoundSlot %= MAX_SAMPLES;
		if (!_soundCache[_emptySoundSlot].looping && !_soundCache[_emptySoundSlot].inSoundList)
			return _emptySoundSlot;
	}

	// Holy crap, they're all looping! What's this twat playing at?

	_emptySoundSlot++;
	_emptySoundSlot %= MAX_SAMPLES;
	return _emptySoundSlot;
}

int SoundManager::cacheSound(int f) {
	return 0; // don't load source in advance
}

int SoundManager::makeSoundAudioStream(int f, Audio::AudioStream *&audiostream, bool loopy) {
	if (!_soundOK)
		return -1;

	int a = findInSoundCache(f);
	if (a == -1) {
		if (f == -2)
			return -1;
		a = findEmptySoundSlot();
	}
	freeSound(a);

	setResourceForFatal(f);
	uint32 length = g_sludge->_resMan->openFileFromNum(f);
	if (!length)
		return -1;

	Common::SeekableReadStream *readStream = g_sludge->_resMan->getData();
	uint curr_ptr = readStream->pos();
	Audio::RewindableAudioStream *stream = Audio::makeWAVStream(readStream->readStream(length), DisposeAfterUse::NO);

#ifdef USE_VORBIS
	if (!stream) {
		readStream->seek(curr_ptr);
		stream = Audio::makeVorbisStream(readStream->readStream(length), DisposeAfterUse::NO);
	}
#endif
	g_sludge->_resMan->finishAccess();

	if (stream) {
		audiostream = Audio::makeLoopingAudioStream(stream, loopy ? 0 : 1);
		_soundCache[a].fileLoaded = f;
		_soundCache[a].looping = loopy;
		setResourceForFatal(-1);
	} else {
		audiostream = nullptr;
		warning(ERROR_SOUND_ODDNESS);
		_soundCache[a].fileLoaded = -1;
		_soundCache[a].looping = false;
		return -1;
	}

	return a;
}

bool SoundManager::startSound(int f, bool loopy) {
	if (_soundOK) {
		// Load sound
		Audio::AudioStream *stream = nullptr;
		int a = makeSoundAudioStream(f, stream, loopy);
		if (a == -1) {
			warning("Failed to cache sound!");
			return false;
		}

		// play sound
		_soundCache[a].looping = loopy;
		_soundCache[a].vol = _defSoundVol;
		g_sludge->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundCache[a].handle, stream, -1, _soundCache[a].vol);
	}
	return true;
}

void SoundManager::saveSounds(Common::WriteStream *stream) {
	if (_soundOK) {
		for (int i = 0; i < MAX_SAMPLES; i++) {
			if (_soundCache[i].looping) {
				stream->writeByte(1);
				stream->writeUint16BE(_soundCache[i].fileLoaded);
				stream->writeUint16BE(_soundCache[i].vol);
			}
		}
	}
	stream->writeByte(0);
	stream->writeUint16BE(_defSoundVol);
	stream->writeUint16BE(_defVol);
}

void SoundManager::loadSounds(Common::SeekableReadStream *stream) {
	for (int i = 0; i < MAX_SAMPLES; i++)
		freeSound(i);

	while (stream->readByte()) {
		int fileLoaded = stream->readUint16BE();
		_defSoundVol = stream->readUint16BE();
		startSound(fileLoaded, 1);
	}

	_defSoundVol = stream->readUint16BE();
	_defVol = stream->readUint16BE();
}

bool SoundManager::getSoundCacheStack(StackHandler *sH) {
	Variable newFileHandle;
	newFileHandle.varType = SVT_NULL;

	for (int a = 0; a < MAX_SAMPLES; a++) {
		if (_soundCache[a].fileLoaded != -1) {
			setVariable(newFileHandle, SVT_FILE, _soundCache[a].fileLoaded);
			if (!addVarToStackQuick(newFileHandle, sH->first))
				return false;
			if (sH->last == NULL)
				sH->last = sH->first;
		}
	}
	return true;
}

bool SoundManager::deleteSoundFromList(SoundList*&s) {
	// Don't delete a playing sound.
	if (s->cacheIndex)
		return false;

	SoundList*o = NULL;
	if (!s->next) {
		o = s->prev;
		if (o)
			o->next = NULL;
		delete s;
		s = o;
		return (s != NULL);
	}
	if (s != s->next) {
		o = s->next;
		o->prev = s->prev;
		if (o->prev)
			o->prev->next = o;
	}
	delete s;
	s = o;
	return (s != NULL);
}

void SoundManager::handleSoundLists() {
	if (_isHandlingSoundList)
		return;
	_isHandlingSoundList = true;
	for (SoundListHandles::iterator it = _soundListHandles.begin(); it != _soundListHandles.end(); ++it) {
		SoundList*s = (*it);
		int a = s->cacheIndex;
		bool remove = false;
		if (!g_sludge->_mixer->isSoundHandleActive(_soundCache[a].handle)) { // reach the end of stream
			s->cacheIndex = false;
			_soundCache[a].inSoundList = false;
			if (_silenceIKillYou) {
				while (deleteSoundFromList(s))
					;
				remove = (s == NULL); // s not null if still playing
			} else {
				if (s->next) {
					if (s->next == s) { // loop the same sound
						int v = _defSoundVol;
						_defSoundVol = _soundCache[a].vol;
						startSound(s->sound, true);
						_defSoundVol = v;
						while (deleteSoundFromList(s))
							;
						remove = (s == NULL); // s not null if still playing
					} else { // repush the next sound list
						s->next->vol = _soundCache[a].vol;
						playSoundList(s->next);
						remove = true; // remove this one
					}

				} else {
					while (deleteSoundFromList(s))
						;
					remove = (s == NULL); // s not null if still playing
				}
			}
		}
		if (remove) {
			it = _soundListHandles.reverse_erase(it);
		}
	}
	_isHandlingSoundList = false;
}

// loop a list of sound
void SoundManager::playSoundList(SoundList*s) {
	if (_soundOK) {
		// Load sound
		Audio::AudioStream *stream;
		int a = makeSoundAudioStream(s->sound, stream, false);
		if (a == -1) {
			warning("Failed to cache sound!");
			return;
		}

		// Play sound
		_soundCache[a].looping = false;
		if (s->vol < 0)
			_soundCache[a].vol = _defSoundVol;
		else
			_soundCache[a].vol = s->vol;
		s-> cacheIndex = a;
		g_sludge->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundCache[a].handle, stream, -1, _soundCache[a].vol);
		_soundCache[a].inSoundList = true;

		// push sound list
		_soundListHandles.push_back(s);

	}
}

void playMovieStream(int a) {
#if 0
	if (! soundOK) return;
	ALboolean ok;
	ALuint src;

	alGenSources(1, &src);
	if (alGetError() != AL_NO_ERROR) {
		debugOut("Failed to create OpenAL source!\n");
		return;
	}

	alSourcef(src, AL_GAIN, (float) soundCache[a].vol / 256);

	ok = alurePlaySourceStream(src, soundCache[a].stream,
			10, 0, sound_eos_callback, &intpointers[a]);
	if (!ok) {
		debugOut("Failed to play stream: %s\n", alureGetErrorString());
		alDeleteSources(1, &src);
		if (alGetError() != AL_NO_ERROR) {
			debugOut("Failed to delete OpenAL source!\n");
		}

		soundCache[a].playingOnSource = 0;
	} else {
		soundCache[a].playingOnSource = src;
		soundCache[a].playing = true;
	}
#endif
}

#if 0
int initMovieSound(int f, ALenum format, int audioChannels, ALuint samplerate,
		ALuint(*callback)(void *userdata, ALubyte *data, ALuint bytes)) {
	if (! soundOK) return 0;

	int retval;
	int a = findEmptySoundSlot();
	freeSound(a);

	soundCache[a].looping = false;
#if 0
	// audioChannel * sampleRate gives us a buffer of half a second. Not much, but it should be enough.
	soundCache[a].stream = alureCreateStreamFromCallback(
			callback,
			&intpointers[a], format, samplerate,
			audioChannels * samplerate, 0, NULL);
#endif
	if (soundCache[a].stream != NULL) {
		soundCache[a].fileLoaded = f;
		soundCache[a].vol = defSoundVol;
		retval = a;
	} else {
#if 0
		debugOut("Failed to create stream from sound: %s\n",
				alureGetErrorString());
#endif
		warning(ERROR_SOUND_ODDNESS);
		soundCache[a].stream = NULL;
		soundCache[a].playing = false;
		soundCache[a].playingOnSource = 0;
		soundCache[a].fileLoaded = -1;
		retval = -1;
	}
	//fprintf (stderr, "Stream %d created. Sample rate: %d Channels: %d\n", retval, samplerate, audioChannels);

	return retval;
}
#endif

uint SoundManager::getSoundSource(int index) {
	warning("getSoundSource, Unimplemented");
	return 0; /*soundCache[index].playingOnSource;*/
}

} // End of namespace Sludge
