/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "engines/stark/resources/sound.h"

#include "audio/decoders/vorbis.h"

#include "common/system.h"

#include "engines/stark/formats/iss.h"
#include "engines/stark/formats/xrc.h"
#include "engines/stark/resources/location.h"
#include "engines/stark/services/archiveloader.h"
#include "engines/stark/services/global.h"
#include "engines/stark/services/services.h"
#include "engines/stark/services/stateprovider.h"

namespace Stark {
namespace Resources {

Sound::~Sound() {
}

Sound::Sound(Object *parent, byte subType, uint16 index, const Common::String &name) :
		Object(parent, subType, index, name),
		_enabled(0),
		_looping(false),
		_field_64(0),
		_loopIndefinitely(false),
		_loadFromFile(true),
		_maxDuration(0),
		_stockSoundType(0),
		_field_6C(0),
		_soundType(0),
		_pan(0),
		_volume(0),
		_fadeDurationRemaining(0),
		_fadeTargetVolume(0.0),
		_fadeTargetPan(0.0),
		_shouldStopOnDestroy(true) {
	_type = TYPE;
}

Audio::RewindableAudioStream *Sound::makeAudioStream() {
	Common::SeekableReadStream *stream = nullptr;
	Audio::RewindableAudioStream *audioStream = nullptr;

	// First try the .iss / isn files
	if (_loadFromFile) {
		stream = StarkArchiveLoader->getExternalFile(_filename, _archiveName);
	} else {
		stream = StarkArchiveLoader->getFile(_filename, _archiveName);
	}

	if (stream) {
		audioStream = Formats::makeISSStream(stream, DisposeAfterUse::YES);
	}

	if (!audioStream) {
		// The 2 CD version uses Ogg Vorbis
		Common::String filename = _filename;
		if (_filename.hasSuffix(".iss") || _filename.hasSuffix(".isn")) {
			filename = Common::String(_filename.c_str(), _filename.size() - 4) + ".ovs";
		}

		stream = StarkArchiveLoader->getExternalFile(filename, _archiveName);
		if (stream) {
#ifdef USE_VORBIS
			audioStream = Audio::makeVorbisStream(stream, DisposeAfterUse::YES);
#else
			warning("Cannot decode sound '%s', Vorbis support is not compiled in", filename.c_str());
			delete stream;
#endif
		}
	}

	if (!audioStream) {
		warning("Unable to load sound '%s'", _filename.c_str());
	}

	return audioStream;
}

Audio::Mixer::SoundType Sound::getMixerSoundType() {
	switch (_soundType) {
	case kSoundTypeVoice:
		return Audio::Mixer::kSpeechSoundType;
	case kSoundTypeEffect:
		return Audio::Mixer::kSFXSoundType;
	case kSoundTypeMusic:
		return Audio::Mixer::kMusicSoundType;
	default:
		error("Unknown sound type '%d'", _soundType);
	}
}

void Sound::play() {
	if (isPlaying()) {
		return;
	}

	Audio::RewindableAudioStream *rewindableStream = makeAudioStream();

	if (!rewindableStream) {
		return;
	}

	Audio::AudioStream *playStream;
	if (_looping) {
		playStream = Audio::makeLoopingAudioStream(rewindableStream, 0);
	} else {
		playStream = rewindableStream;
	}

	g_system->getMixer()->playStream(getMixerSoundType(), &_handle, playStream, -1,
	                                 _volume * Audio::Mixer::kMaxChannelVolume, _pan * 127);
}

bool Sound::isPlaying() {
	return g_system->getMixer()->isSoundHandleActive(_handle);
}

void Sound::stop() {
	g_system->getMixer()->stopHandle(_handle);
	_handle = Audio::SoundHandle();
}

void Sound::onPreDestroy() {
	Object::onPreDestroy();

	if (_shouldStopOnDestroy) {
		stop();
	}
}

void Sound::readData(Formats::XRCReadStream *stream) {
	_filename = stream->readString();
	_enabled = stream->readUint32LE();
	_looping = stream->readBool();
	_field_64 = stream->readUint32LE();
	_loopIndefinitely = stream->readBool();
	_maxDuration = stream->readUint32LE();
	_loadFromFile = stream->readBool(); // Used only in the 4CD version
	_stockSoundType = stream->readUint32LE();
	_soundName = stream->readString();
	_field_6C = stream->readUint32LE();
	_soundType = stream->readUint32LE();
	_pan = stream->readFloatLE();
	_volume = stream->readFloatLE();
	_archiveName = stream->getArchiveName();
}

void Sound::printData() {
	debug("filename: %s", _filename.c_str());
	debug("enabled: %d", _enabled);
	debug("looping: %d", _looping);
	debug("field_64: %d", _field_64);
	debug("loopIndefinitely: %d", _loopIndefinitely);
	debug("maxDuration: %d", _maxDuration);
	debug("loadFromFile: %d", _loadFromFile);
	debug("stockSoundType: %d", _stockSoundType);
	debug("soundName: %s", _soundName.c_str());
	debug("field_6C: %d", _field_6C);
	debug("soundType: %d", _soundType);
	debug("pan: %f", _pan);
	debug("volume: %f", _volume);
}

void Sound::onGameLoop() {
	Object::onGameLoop();

	if (_subType == kSoundBackground && !isPlaying()) {
		Location *location = StarkGlobal->getCurrent()->getLocation();
		if (location->getName() != "Amongst Stalls" || StarkGlobal->getCurrentChapter() < 100) {
			play();
		}
	}

	if (_looping && !_loopIndefinitely) {
		// Automatically stop after the maximum run time has been reached
		uint32 elapsedTime = g_system->getMixer()->getSoundElapsedTime(_handle);
		if (elapsedTime > _maxDuration) {
			stop();
		}
	}

	if (_fadeDurationRemaining > 0 && isPlaying()) {
		_volume += (_fadeTargetVolume - _volume) * StarkGlobal->getMillisecondsPerGameloop() / (float) _fadeDurationRemaining;
		_pan += (_fadeTargetPan - _pan) * StarkGlobal->getMillisecondsPerGameloop() / (float) _fadeDurationRemaining;

		_fadeDurationRemaining -= StarkGlobal->getMillisecondsPerGameloop();

		if (_fadeDurationRemaining <= 0) {
			_fadeDurationRemaining = 0;

			_volume = _fadeTargetVolume;
			_pan = _fadeTargetPan;
		}

		g_system->getMixer()->setChannelVolume(_handle, _volume * Audio::Mixer::kMaxChannelVolume);
		g_system->getMixer()->setChannelBalance(_handle, _pan * 127);
	}
}

uint32 Sound::getStockSoundType() const {
	return _stockSoundType;
}

void Sound::changeVolumePan(int32 volume, int32 pan, int32 duration) {
	if (isPlaying()) {
		_fadeDurationRemaining = duration;

		if (_fadeDurationRemaining > 0) {
			_fadeTargetVolume = volume / 100.0f;
			_fadeTargetPan = pan / 100.0f;
		} else {
			_volume = volume / 100.0f;
			_pan = pan / 100.0f;

			g_system->getMixer()->setChannelVolume(_handle, _volume * Audio::Mixer::kMaxChannelVolume);
			g_system->getMixer()->setChannelBalance(_handle, _pan * 127);
		}
	} else {
		if (_fadeDurationRemaining == 0) {
			_volume = volume / 100.0f;
			_pan = pan / 100.0f;
		}
	}
}

void Sound::saveLoadCurrent(ResourceSerializer *serializer) {
	bool playing = isPlaying();
	serializer->syncAsUint32LE(playing);

	if (_subType != kSoundBackground && playing) {
		uint32 elapsed = g_system->getMixer()->getSoundElapsedTime(_handle);
		serializer->syncAsUint32LE(elapsed);
		serializer->syncAsFloat(_volume);
		serializer->syncAsFloat(_pan);
		serializer->syncAsUint32LE(_fadeDurationRemaining);
		serializer->syncAsFloat(_fadeTargetVolume);
		serializer->syncAsFloat(_fadeTargetPan);

		if (serializer->isLoading()) {
			play();
			// TODO: Seek to the "elapsed" position
		}
	}
}

void Sound::onEnginePause(bool pause) {
	g_system->getMixer()->pauseHandle(_handle, pause);
}

void Sound::setStopOnDestroy(bool stopOnDestroy) {
	_shouldStopOnDestroy = stopOnDestroy;
}

} // End of namespace Resources
} // End of namespace Stark
