/* 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 "glk/sound.h"
#include "glk/glk.h"
#include "glk/events.h"
#include "common/file.h"
#include "audio/audiostream.h"
#include "audio/decoders/aiff.h"
#include "audio/decoders/mp3.h"
#include "audio/decoders/raw.h"
#include "audio/decoders/wave.h"

namespace Glk {

Sounds::~Sounds() {
	for (int idx = (int)_sounds.size() - 1; idx >= 0; --idx)
		delete _sounds[idx];
}

void Sounds::removeSound(schanid_t snd) {
	for (uint idx = 0; idx < _sounds.size(); ++idx) {
		if (_sounds[idx] == snd) {
			_sounds.remove_at(idx);
			break;
		}
	}
}

schanid_t Sounds::create(uint rock) {
	schanid_t snd = new SoundChannel(this);
	_sounds.push_back(snd);
	return snd;
}

schanid_t Sounds::iterate(schanid_t chan, uint *rockptr) {
	for (int idx = 0; idx < (int)_sounds.size() - 1; ++idx) {
		if (_sounds[idx] == chan) {
			schanid_t next = _sounds[idx + 1];
			if (*rockptr)
				*rockptr = next->_rock;

			return next;
		}
	}

	return nullptr;
}

void Sounds::poll() {
	for (uint idx = 0; idx < _sounds.size(); ++idx)
		_sounds[idx]->poll();
}

/*--------------------------------------------------------------------------*/

SoundChannel::SoundChannel(Sounds *owner) : _owner(owner), _soundNum(0),
		_rock(0), _notify(0) {
	if (g_vm->gli_register_obj)
		_dispRock = (*g_vm->gli_register_obj)(this, gidisp_Class_Schannel);
}

SoundChannel::~SoundChannel() {
	stop();
	_owner->removeSound(this);

	if (g_vm->gli_unregister_obj)
		(*g_vm->gli_unregister_obj)(this, gidisp_Class_Schannel, _dispRock);
}

uint SoundChannel::play(uint soundNum, uint repeats, uint notify) {
	stop();
	if (repeats == 0)
		return 1;

	// Find a sound of the given name
	Audio::AudioStream *stream;
	Common::File f;
	Common::String nameSnd = Common::String::format("sound%u.snd", soundNum);
	Common::String nameWav = Common::String::format("sound%u.wav", soundNum);
	Common::String nameAiff = Common::String::format("sound%u.aiff", soundNum);
#ifdef USE_MAD
	Common::String nameMp3 = Common::String::format("sound%u.mp3", soundNum);
#endif

	if (f.exists(nameSnd) && f.open(nameSnd)) {
		if (f.readUint16BE() != (f.size() - 2))
			error("Invalid sound filesize");
		byte headerRepeats = f.readByte();
		if (headerRepeats > 0)
			repeats = headerRepeats;
		f.skip(1);
		uint freq = f.readUint16BE();
		f.skip(2);
		uint size = f.readUint16BE();

		Common::SeekableReadStream *s = f.readStream(size);
		stream = Audio::makeRawStream(s, freq, Audio::FLAG_UNSIGNED);

#ifdef USE_MAD
	} else if (f.exists(nameMp3) && f.open(nameMp3)) {
		Common::SeekableReadStream *s = f.readStream(f.size());
		stream = Audio::makeMP3Stream(s, DisposeAfterUse::YES);
#endif
	} else if (f.exists(nameWav) && f.open(nameWav)) {
		Common::SeekableReadStream *s = f.readStream(f.size());
		stream = Audio::makeWAVStream(s, DisposeAfterUse::YES);

	} else if (f.exists(nameAiff) && f.open(nameAiff)) {
		Common::SeekableReadStream *s = f.readStream(f.size());
		stream = Audio::makeAIFFStream(s, DisposeAfterUse::YES);

	} else {
		warning("Could not find sound %u", soundNum);
		return 1;
	}

	_soundNum = soundNum;
	_notify = notify;

	// Set up a repeat if multiple repeats are specified
	if (repeats > 1) {
		Audio::RewindableAudioStream *rwStream = dynamic_cast<Audio::RewindableAudioStream *>(stream);
		assert(rwStream);
		stream = new Audio::LoopingAudioStream(rwStream, repeats, DisposeAfterUse::YES);
	}

	// Start playing the audio
	g_vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, stream);
	return 0;
}

void SoundChannel::stop() {
	g_vm->_mixer->stopHandle(_handle);
}

void SoundChannel::poll() {
	if (!g_vm->_mixer->isSoundHandleActive(_handle) && _notify != 0) {
		uint notify = _notify;
		_notify = 0;
		g_vm->_events->store(evtype_SoundNotify, nullptr, _soundNum, notify);
	}
}

void SoundChannel::setVolume(uint volume, uint duration, uint notify) {
	uint newVol = volume * 255 / 0x10000;
	g_vm->_mixer->setChannelVolume(_handle, newVol);

	if (notify) {
		warning("TODO: Gradual volume change");
		g_vm->_events->store(evtype_VolumeNotify, nullptr, 0, notify);	
	}
}

void SoundChannel::pause() {
	g_vm->_mixer->pauseHandle(_handle, true);
}

void SoundChannel::unpause() {
	g_vm->_mixer->pauseHandle(_handle, false);
}

} // End of namespace Glk
