/* 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 "bladerunner/ambient_sounds.h"

#include "bladerunner/audio_player.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/gameinfo.h"

#include "common/debug.h"
#include "common/system.h"

namespace BladeRunner {

#define NON_LOOPING_SOUNDS 25
#define LOOPING_SOUNDS      3

AmbientSounds::AmbientSounds(BladeRunnerEngine *vm)	: _vm(vm) {
	_nonLoopingSounds = new NonLoopingSound[NON_LOOPING_SOUNDS];
	_loopingSounds = new LoopingSound[LOOPING_SOUNDS];
	_ambientVolume = 65;

	for (int i = 0; i != NON_LOOPING_SOUNDS; ++i) {
		NonLoopingSound &track = _nonLoopingSounds[i];
		track.isActive = false;
	}

	for (int i = 0; i != LOOPING_SOUNDS; ++i) {
		LoopingSound &track = _loopingSounds[i];
		track.isActive = false;
	}
}

AmbientSounds::~AmbientSounds() {
	delete[] _nonLoopingSounds;
	delete[] _loopingSounds;
}

static inline void sort(int &a, int &b) {
	if (a > b) {
		int t = a;
		a = b;
		b = t;
	}
}

void AmbientSounds::addSound(
	int sfxId,
	int timeMin, int timeMax,
	int volumeMin, int volumeMax,
	int panStartMin, int panStartMax,
	int panEndMin, int panEndMax,
	int priority, int unk) {
	const char *name = _vm->_gameInfo->getSfxTrack(sfxId);

	sort(volumeMin, volumeMax);
	sort(panStartMin, panStartMax);
	sort(panEndMin, panEndMax);

	addSoundByName(
				 name,
				 timeMin, timeMax,
				 volumeMin, volumeMax,
				 panStartMin, panStartMax,
				 panEndMin, panEndMax,
				 priority, unk
				);
}

void AmbientSounds::removeNonLoopingSound(int sfxId, bool stopPlaying) {
	const char *name = _vm->_gameInfo->getSfxTrack(sfxId);
	int32 hash = mix_id(name);
	int index = findNonLoopingTrackByHash(hash);
	if (index >= 0) {
		removeNonLoopingSoundByIndex(index, stopPlaying);
	}
}

void AmbientSounds::removeAllNonLoopingSounds(bool stopPlaying) {
	for (int i = 0; i < NON_LOOPING_SOUNDS; i++) {
		removeNonLoopingSoundByIndex(i, stopPlaying);
	}
}

void AmbientSounds::addSpeech(int actorId, int sentenceId, int timeMin, int timeMax, int volumeMin, int volumeMax, int panStartMin, int panStartMax, int panEndMin, int panEndMax, int priority, int unk) {
	sort(volumeMin, volumeMax);
	sort(panStartMin, panStartMax);
	sort(panEndMin, panEndMax);

	char name[13];
	sprintf(name, "%02d-%04d.AUD", actorId, sentenceId); //TODO somewhere here should be also language code
	addSoundByName(name,
					timeMin, timeMax,
					volumeMin, volumeMax,
					panStartMin, panStartMax,
					panEndMin, panEndMax,
					priority, unk);
}

void AmbientSounds::playSound(int sfxId, int volume, int panStart, int panEnd, int priority) {
	const char *name = _vm->_gameInfo->getSfxTrack(sfxId);

	_vm->_audioPlayer->playAud(name, volume * _ambientVolume / 100, panStart, panEnd, priority, AudioPlayer::OVERRIDE_VOLUME);
}

void AmbientSounds::addLoopingSound(int sfxId, int volume, int pan, int delay) {
	const char *name = _vm->_gameInfo->getSfxTrack(sfxId);

	int32 hash = mix_id(name);

	if (findLoopingTrackByHash(hash) >= 0) {
		return;
	}

	int i = findAvailableLoopingTrack();
	if (i == -1) {
		return;
	}
	LoopingSound &track = _loopingSounds[i];

	track.isActive = true;
	strcpy(track.name, name);
	track.hash = hash;
	track.pan = pan;
	track.volume = volume;

	int actualVolumeStart = volume * _ambientVolume / 100;
	int actualVolumeEnd = actualVolumeStart;

	if (delay > 0) {
		actualVolumeStart = 0;
	}

	track.audioPlayerTrack = _vm->_audioPlayer->playAud(name, actualVolumeStart, pan, pan, 99, AudioPlayer::LOOP | AudioPlayer::OVERRIDE_VOLUME);

	if (track.audioPlayerTrack == -1) {
		removeLoopingSoundByIndex(i, 0);
	} else {
		if (delay) {
			_vm->_audioPlayer->adjustVolume(track.audioPlayerTrack, actualVolumeEnd, delay, false);
		}
	}
}

void AmbientSounds::adjustLoopingSound(int sfxId, int volume, int pan, int delay) {
	const char *name = _vm->_gameInfo->getSfxTrack(sfxId);
	int32 hash = mix_id(name);
	int index = findLoopingTrackByHash(hash);

	if (index >= 0 && _loopingSounds[index].audioPlayerTrack != -1 && _vm->_audioPlayer->isActive(_loopingSounds[index].audioPlayerTrack)) {
		if (volume != -1) {
			_loopingSounds[index].volume = volume;
			_vm->_audioPlayer->adjustVolume(_loopingSounds[index].audioPlayerTrack, _ambientVolume * volume / 100, delay, false);
		}
		if (pan != -101) {
			_loopingSounds[index].pan = pan;
			_vm->_audioPlayer->adjustPan(_loopingSounds[index].audioPlayerTrack, pan, delay);
		}
	}
}

void AmbientSounds::removeLoopingSound(int sfxId, int delay) {
	const char *name = _vm->_gameInfo->getSfxTrack(sfxId);
	int32 hash = mix_id(name);
	int index = findLoopingTrackByHash(hash);
	if (index >= 0) {
		removeLoopingSoundByIndex(index, delay);
	}
}

void AmbientSounds::removeAllLoopingSounds(int delay) {
	for (int i = 0; i < LOOPING_SOUNDS; i++) {
		removeLoopingSoundByIndex(i, delay);
	}
}

void AmbientSounds::tick() {
	uint32 now = g_system->getMillis();

	for (int i = 0; i != NON_LOOPING_SOUNDS; ++i) {
		NonLoopingSound &track = _nonLoopingSounds[i];

		if (!track.isActive || track.nextPlayTime > now) {
			continue;
		}

		int panEnd;
		int panStart = _vm->_rnd.getRandomNumberRng(track.panStartMin, track.panStartMax);
		if (track.panEndMin == -101) {
			panEnd = panStart;
		} else {
			panEnd = _vm->_rnd.getRandomNumberRng(track.panEndMin, track.panEndMax);
		}

		track.volume = _vm->_rnd.getRandomNumberRng(track.volumeMin, track.volumeMax);

		track.audioPlayerTrack = _vm->_audioPlayer->playAud(
															 track.name,
															 track.volume * _ambientVolume / 100,
															 panStart,
															 panEnd,
															 track.priority,
															 AudioPlayer::OVERRIDE_VOLUME
															);

		track.nextPlayTime = now + _vm->_rnd.getRandomNumberRng(track.timeMin, track.timeMax);
	}
}

int AmbientSounds::findAvailableNonLoopingTrack() {
	for (int i = 0; i != NON_LOOPING_SOUNDS; ++i) {
		if (!_nonLoopingSounds[i].isActive) {
			return i;
		}
	}

	return -1;
}

int AmbientSounds::findNonLoopingTrackByHash(int32 hash) {
	for (int i = 0; i != NON_LOOPING_SOUNDS; ++i) {
		NonLoopingSound &track = _nonLoopingSounds[i];

		if (track.isActive && track.hash == hash) {
			return i;
		}
	}

	return -1;
}

int AmbientSounds::findAvailableLoopingTrack() {
	for (int i = 0; i != LOOPING_SOUNDS; ++i) {
		if (!_loopingSounds[i].isActive) {
			return i;
		}
	}

	return -1;
}

int AmbientSounds::findLoopingTrackByHash(int32 hash) {
	for (int i = 0; i != LOOPING_SOUNDS; ++i) {
		LoopingSound &track = _loopingSounds[i];

		if (track.isActive && track.hash == hash) {
			return i;
		}
	}

	return -1;
}

void AmbientSounds::addSoundByName(
	const char *name,
	int timeMin, int timeMax,
	int volumeMin, int volumeMax,
	int panStartMin, int panStartMax,
	int panEndMin, int panEndMax,
	int priority, int unk) {
	if (strlen(name) > 12) {
		error("AmbientSounds::addSoundByName: Overlong name '%s'", name);
	}

	int i = findAvailableNonLoopingTrack();
	if (i < 0) {
		return;
	}

	NonLoopingSound &track = _nonLoopingSounds[i];

	uint32 now = _vm->getTotalPlayTime();

	track.isActive = true;
	strcpy(track.name, name);
	track.hash = mix_id(name);
	track.timeMin = 1000 * timeMin;
	track.timeMax = 1000 * timeMax;
	track.nextPlayTime = now + _vm->_rnd.getRandomNumberRng(track.timeMin, track.timeMax);
	track.volumeMin = volumeMin;
	track.volumeMax = volumeMax;
	track.volume = 0;
	track.panStartMin = panStartMin;
	track.panStartMax = panStartMax;
	track.panEndMin = panEndMin;
	track.panEndMax = panEndMax;
	track.priority = priority;
}

void AmbientSounds::removeNonLoopingSoundByIndex(int index, bool stopPlaying) {
	NonLoopingSound &track = _nonLoopingSounds[index];
	if (stopPlaying) {
		if (track.isActive && track.audioPlayerTrack != -1 && _vm->_audioPlayer->isActive(track.audioPlayerTrack)) {
			_vm->_audioPlayer->stop(track.audioPlayerTrack, stopPlaying);
		}
	}
	track.isActive = false;
	track.audioPlayerTrack = -1;
	//	track.field_45 = 0;
}

void AmbientSounds::removeLoopingSoundByIndex(int index, int delay) {
	LoopingSound &track = _loopingSounds[index];
	if (track.isActive && track.audioPlayerTrack != -1 && _vm->_audioPlayer->isActive(track.audioPlayerTrack)) {
		if (delay > 0) {
			_vm->_audioPlayer->adjustVolume(track.audioPlayerTrack, 0, delay, false);
		} else {
			_vm->_audioPlayer->stop(track.audioPlayerTrack, false);
		}
	}
	track.isActive = false;
	track.name[0] = 0;
	track.hash = 0;
	track.audioPlayerTrack = -1;
	track.volume = 0;
	track.pan = 0;
}

} // End of namespace BladeRunner
