/* ResidualVM - A 3D game interpreter
 *
 * ResidualVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the AUTHORS
 * 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 "engines/myst3/ambient.h"
#include "engines/myst3/database.h"
#include "engines/myst3/myst3.h"
#include "engines/myst3/state.h"
#include "engines/myst3/sound.h"

namespace Myst3 {

Ambient::Ambient(Myst3Engine *vm) :
	_vm(vm),
	_cueStartFrame(0) {
	_cueSheet.reset();
}

Ambient::~Ambient() {
}

void Ambient::playCurrentNode(uint32 volume, uint32 fadeOutDelay) {
	if (!fadeOutDelay) fadeOutDelay = 1;

	uint32 node = _vm->_state->getLocationNode();
	uint32 room = _vm->_state->getLocationRoom();
	uint32 age = _vm->_state->getLocationAge();

	// Load sound descriptors
	loadNode(node, room, age);

	// Adjust volume
	scaleVolume(volume);

	// Play sounds
	applySounds(fadeOutDelay);
}

void Ambient::scaleVolume(uint32 volume) {
	for (uint i = 0; i < _sounds.size(); i++)
		_sounds[i].volume = _sounds[i].volume * volume / 100;
}

void Ambient::loadNode(uint32 node, uint32 room, uint32 age) {
	_sounds.clear();
	_cueSheet.reset();

	if (node == 0)
		node = _vm->_state->getLocationNode();

	_vm->_state->setAmbiantPreviousFadeOutDelay(_vm->_state->getAmbiantFadeOutDelay());

	_scriptAge = age;
	_scriptRoom = room;

	_vm->runAmbientScripts(node);

	if (_sounds.size() == 0)
		_vm->runAmbientScripts(32766);
}

void Ambient::addSound(uint32 id, int32 volume, int32 heading, int32 headingAngle, int32 u1, int32 fadeOutDelay) {
	if (!volume)
		volume = 1;

	AmbientSound s;

	if (volume >= 0) {
		s.volume = volume;
		s.volumeFlag = 0;
	} else {
		s.volume = -volume;
		s.volumeFlag = 1;
	}

	s.id = id;
	s.heading = heading;
	s.headingAngle = headingAngle;
	s.u1 = u1;
	s.fadeOutDelay = fadeOutDelay;

	_sounds.push_back(s);
}

void Ambient::setCueSheet(uint32 id, int32 volume, int32 heading, int32 headingAngle) {
	_cueSheet.reset();

	if (volume >= 0) {
		_cueSheet.volume = volume;
		_cueSheet.volumeFlag = 0;
	} else {
		_cueSheet.volume = -volume;
		_cueSheet.volumeFlag = 1;
	}

	_cueSheet.id = id;
	_cueSheet.heading = heading;
	_cueSheet.headingAngle = headingAngle;
}

uint16 Ambient::delayForCue(uint32 id) {
	const AmbientCue &cue = _vm->_db->getAmbientCue(id);

	// Return a delay in frames inside the bounds
	return _vm->_rnd->getRandomNumberRng(cue.minFrames, cue.maxFrames);
}

uint32 Ambient::nextCueSound(uint32 id) {
	static uint32 lastId = 0;
	const AmbientCue &cue = _vm->_db->getAmbientCue(id);

	// Only one sound, no way it can be different from the previous one
	if (cue.tracks.size() == 1) {
		return cue.tracks[0];
	}

	// Make sure the new random sound is different from the last one
	uint32 soundId;
	do {
		uint index = _vm->_rnd->getRandomNumber(cue.tracks.size() - 1);
		soundId = cue.tracks[index];
	} while (soundId == lastId);

	lastId = soundId;

	return soundId;
}

void Ambient::updateCue() {
	if (_cueSheet.id) {
		if (!_cueStartFrame) {
			_cueStartFrame = _vm->_state->getFrameCount() + delayForCue(_cueSheet.id);
		}
		if (_vm->_state->getFrameCount() >= _cueStartFrame) {
			_cueStartFrame = 0;
			uint32 soundId = nextCueSound(_cueSheet.id);

			uint heading;
			if (_cueSheet.heading == 32766) {
				heading = _vm->_rnd->getRandomNumberRng(0, 359);
			} else {
				heading = _cueSheet.heading;
			}
			_vm->_sound->playCue(soundId, _cueSheet.volume, heading, _cueSheet.headingAngle);
		}
	}
}

void Ambient::applySounds(uint32 fadeOutDelay) {
	// Reset the random sounds
	_cueStartFrame = 0;
	if (!_cueSheet.id) {
		_vm->_sound->stopCue(fadeOutDelay);
	}

	// Age all sounds
	_vm->_sound->age();

	// Setup the selected sounds
	for (uint i = 0; i < _sounds.size(); i++) {
		const AmbientSound &sound = _sounds[i];

		bool existingChannel;
		SoundChannel *channel = _vm->_sound->getChannelForSound(sound.id, kAmbient, &existingChannel);

		// The sound was already playing
		if (!existingChannel) {
			uint volume = 0;
//			if (sound.volumeFlag) // TODO: Used in the original
				volume = sound.volume;

			channel->play(sound.id, volume, sound.heading, sound.headingAngle, true, kAmbient);
		}

		if (channel->_playing) {
			channel->fade(sound.volume, sound.heading, sound.headingAngle, fadeOutDelay);
			channel->_age = 0;
			channel->_ambientFadeOutDelay = sound.fadeOutDelay;
		}
	}

	// Fade out old playing ambient sounds
	_vm->_sound->fadeOutOldSounds(fadeOutDelay);
}

} // End of namespace Myst3
