/* 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.
 *
 * Additional copyright for this file:
 * Copyright (C) 1995-1997 Presto Studios, Inc.
 *
 * 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 "audio/audiostream.h"
#include "audio/decoders/aiff.h"
#include "audio/decoders/quicktime.h"
#include "common/file.h"
#include "common/system.h"

#include "pegasus/fader.h"
#include "pegasus/sound.h"

namespace Pegasus {

Sound::Sound() {
	_stream = nullptr;
	_volume = 0xFF;
	_fader = nullptr;
}

Sound::~Sound() {
	disposeSound();
}

void Sound::disposeSound() {
	stopSound();
	delete _stream; _stream = nullptr;
}

void Sound::initFromAIFFFile(const Common::String &fileName) {
	disposeSound();

	Common::File *file = new Common::File();
	if (!file->open(fileName)) {
		warning("Failed to open AIFF file '%s'", fileName.c_str());
		delete file;
		return;
	}

	Audio::RewindableAudioStream *stream = Audio::makeAIFFStream(file, DisposeAfterUse::YES);

	_stream = dynamic_cast<Audio::SeekableAudioStream *>(stream);

	if (!_stream) {
		delete stream;
		warning("AIFF stream '%s' is not seekable", fileName.c_str());
		return;
	}
}

void Sound::initFromQuickTime(const Common::String &fileName) {
	disposeSound();

	_stream = Audio::makeQuickTimeStream(fileName);

	if (!_stream)
		warning("Failed to open QuickTime file '%s'", fileName.c_str());
}

void Sound::attachFader(SoundFader *fader) {
	if (_fader)
		_fader->attachSound(nullptr);

	_fader = fader;

	if (_fader)
		_fader->attachSound(this);
}

void Sound::playSound() {
	if (!isSoundLoaded())
		return;

	stopSound();

	// Make sure the sound is back at the beginning before we play it
	_stream->rewind();

	if (_fader)
		setVolume(_fader->getFaderValue());

	g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_handle, _stream, -1, _volume, 0, DisposeAfterUse::NO);
}

void Sound::loopSound() {
	if (!isSoundLoaded())
		return;

	stopSound();

	// Create a looping stream
	Audio::AudioStream *loopStream = new Audio::LoopingAudioStream(_stream, 0, DisposeAfterUse::NO);

	// Assume that if there is a fader, we're going to fade the sound in.
	if (_fader)
		setVolume(0);

	g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_handle, loopStream, -1, _volume, 0, DisposeAfterUse::YES);
}

void Sound::playSoundSegment(uint32 start, uint32 end) {
	if (!isSoundLoaded())
		return;

	stopSound();

	Audio::AudioStream *subStream = new Audio::SubSeekableAudioStream(_stream, Audio::Timestamp(0, start, 600), Audio::Timestamp(0, end, 600), DisposeAfterUse::NO);

	g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_handle, subStream, -1, _volume, 0, DisposeAfterUse::YES);
}

void Sound::stopSound() {
	g_system->getMixer()->stopHandle(_handle);
}

void Sound::setVolume(const uint16 volume) {
	// Clipping the volume to [0x00, 0xFF] instead of Apple's [0, 0x100]
	// We store the volume in case SetVolume is called before the sound starts

	_volume = (volume == 0x100) ? 0xFF : volume;
	g_system->getMixer()->setChannelVolume(_handle, _volume);
}

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

bool Sound::isSoundLoaded() const {
	return _stream != nullptr;
}

SoundTimeBase::SoundTimeBase() {
	setScale(600);
	_startScale = 600;
	_stopScale = 600;
	_setToStart = false;
}

void SoundTimeBase::playSoundSegment(uint32 startTime, uint32 endTime) {
	_startTime = startTime;
	_stopTime = endTime;
	_setToStart = true;
	_time = Common::Rational(startTime, getScale());
	setRate(1);
	Sound::playSoundSegment(startTime, endTime);
}

void SoundTimeBase::updateTime() {
	if (_setToStart) {
		if (isPlaying()) {
			// Not at the end, let's get the time
			uint numFrames = g_system->getMixer()->getSoundElapsedTime(_handle) * 600 / 1000;

			// WORKAROUND: Our mixer is woefully inaccurate and quite often returns
			// times that exceed the actual length of the clip. We'll just fake times
			// that are under the final time to ensure any trigger for the end time is
			// only sent when the sound has actually stopped.
			if (numFrames >= (_stopTime - _startTime))
				numFrames = _stopTime - _startTime - 1;

			_time = Common::Rational(_startTime + numFrames, getScale());
		} else {
			// Assume we reached the end
			_setToStart = false;
			_time = Common::Rational(_stopTime, getScale());
		}
	}
}

} // End of namespace Pegasus
