/* ScummVM - Scumm Interpreter
 * Copyright (C) 2001-2004 The ScummVM project
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

#include "stdafx.h"

#include "common/file.h"
#include "common/util.h"

#include "simon/sound.h"

#include "sound/flac.h"
#include "sound/mp3.h"
#include "sound/voc.h"
#include "sound/vorbis.h"

namespace Simon {

#define SOUND_BIG_ENDIAN true

class BaseSound {
protected:
	File *_file;
	uint32 *_offsets;
	SoundMixer *_mixer;
	bool _freeOffsets;

public:
	BaseSound(SoundMixer *mixer, File *file, uint32 base = 0, bool bigendian = false);
	BaseSound(SoundMixer *mixer, File *file, uint32 *offsets, bool bigendian = false);
	virtual ~BaseSound();
	virtual void playSound(uint sound, PlayingSoundHandle *handle, byte flags) = 0;
};

class WavSound : public BaseSound {
public:
	WavSound(SoundMixer *mixer, File *file, uint32 base = 0, bool bigendian = false) : BaseSound(mixer, file, base, bigendian) {};
	WavSound(SoundMixer *mixer, File *file, uint32 *offsets) : BaseSound(mixer, file, offsets) {};
	void playSound(uint sound, PlayingSoundHandle *handle, byte flags);
};

class VocSound : public BaseSound {
public:
	VocSound(SoundMixer *mixer, File *file, uint32 base = 0, bool bigendian = false) : BaseSound(mixer, file, base, bigendian) {};
	void playSound(uint sound, PlayingSoundHandle *handle, byte flags);
};
class RawSound : public BaseSound {
public:
	RawSound(SoundMixer *mixer, File *file, uint32 base = 0, bool bigendian = false) : BaseSound(mixer, file, base, bigendian) {};
	void playSound(uint sound, PlayingSoundHandle *handle, byte flags);
};

BaseSound::BaseSound(SoundMixer *mixer, File *file, uint32 base, bool bigendian) {
	_mixer = mixer;
	_file = file;

	uint res = 0;
	uint32 size;

	_file->seek(base + sizeof(uint32), SEEK_SET);
	if (bigendian)
		size = _file->readUint32BE();
	else
		size = _file->readUint32LE();

	res = size / sizeof(uint32);

	_offsets = (uint32 *)malloc(size + sizeof(uint32));
	_freeOffsets = true;

	_file->seek(base, SEEK_SET);

	if (_file->read(_offsets, size) != size)
		error("Can't read offsets");

	for (uint i = 0; i < res; i++) {
#if defined(SCUMM_BIG_ENDIAN)
	if (!(bigendian))
		_offsets[i] = FROM_LE_32(_offsets[i]);
#endif
	if (bigendian)
			_offsets[i] = TO_BE_32(_offsets[i]);
		_offsets[i] += base;
	}

	// only needed for mp3
	_file->seek(0, SEEK_END);
	_offsets[res] = _file->pos();
}

BaseSound::BaseSound(SoundMixer *mixer, File *file, uint32 *offsets, bool bigendian) {
	_mixer = mixer;
	_file = file;
	_offsets = offsets;
	_freeOffsets = false;
}

BaseSound::~BaseSound() {
	if (_freeOffsets)
		free(_offsets);
	delete _file;
}

#if !defined(__GNUC__)
#pragma START_PACK_STRUCTS
#endif

struct WaveHeader {
	uint32 riff;
	uint32 unk;
	uint32 wave;
	uint32 fmt;

	uint32 size;

	uint16 format_tag;
	uint16 channels;
	uint32 samples_per_sec;
	uint32 avg_bytes;

	uint16 block_align;
	uint16 bits_per_sample;
} GCC_PACK;

#if !defined(__GNUC__)
#pragma END_PACK_STRUCTS
#endif
	
void WavSound::playSound(uint sound, PlayingSoundHandle *handle, byte flags) {
	if (_offsets == NULL)
		return;

	WaveHeader wave_hdr;
	uint32 data[2];

	flags |= SoundMixer::FLAG_AUTOFREE;

	_file->seek(_offsets[sound], SEEK_SET);

	if (_file->read(&wave_hdr, sizeof(wave_hdr)) != sizeof(wave_hdr) ||
			wave_hdr.riff != MKID('RIFF') || wave_hdr.wave != MKID('WAVE')
			|| wave_hdr.fmt != MKID('fmt ') || READ_LE_UINT16(&wave_hdr.format_tag) != 1
			|| READ_LE_UINT16(&wave_hdr.channels) != 1
			|| READ_LE_UINT16(&wave_hdr.bits_per_sample) != 8) {
		error("playWav(%d): can't read RIFF header", sound);
	}

	_file->seek(FROM_LE_32(wave_hdr.size) - sizeof(wave_hdr) + 20, SEEK_CUR);

	data[0] = _file->readUint32LE();
	data[1] = _file->readUint32LE();
	if (data[0] != 'atad') {
		error("playWav(%d): can't read data header", sound);
	}

	byte *buffer = (byte *)malloc(data[1]);
	_file->read(buffer, data[1]);

	_mixer->playRaw(handle, buffer, data[1], FROM_LE_32(wave_hdr.samples_per_sec), flags);
}

void VocSound::playSound(uint sound, PlayingSoundHandle *handle, byte flags) {
	if (_offsets == NULL)
		return;

	_file->seek(_offsets[sound], SEEK_SET);

	int size, samples_per_sec;
	byte *buffer = loadVOCFromStream(*_file, size, samples_per_sec);

	_mixer->playRaw(handle, buffer, size, samples_per_sec, flags | SoundMixer::FLAG_AUTOFREE);
}

void RawSound::playSound(uint sound, PlayingSoundHandle *handle, byte flags) {
	if (_offsets == NULL)
		return;

	_file->seek(_offsets[sound], SEEK_SET);

	uint size = _file->readUint32BE();
	byte *buffer = (byte *)malloc(size);
	_file->read(buffer, size);

	_mixer->playRaw(handle, buffer, size, 22050, flags | SoundMixer::FLAG_AUTOFREE);
}

#ifdef USE_MAD
class MP3Sound : public BaseSound {
public:
	MP3Sound(SoundMixer *mixer, File *file, uint32 base = 0) : BaseSound(mixer, file, base) {};
	void playSound(uint sound, PlayingSoundHandle *handle, byte flags);
};

void MP3Sound::playSound(uint sound, PlayingSoundHandle *handle, byte flags)
{
	if (_offsets == NULL)
		return;

	_file->seek(_offsets[sound], SEEK_SET);

	int i = 1;
	while (_offsets[sound + i] == _offsets[sound])
			i++;

	uint32 size = _offsets[sound + i] - _offsets[sound];

	_mixer->playInputStream(handle, makeMP3Stream(_file, size), false);
}
#endif

#ifdef USE_VORBIS
class VorbisSound : public BaseSound {
public:
	VorbisSound(SoundMixer *mixer, File *file, uint32 base = 0) : BaseSound(mixer, file, base) {};
	void playSound(uint sound, PlayingSoundHandle *handle, byte flags);
};

void VorbisSound::playSound(uint sound, PlayingSoundHandle *handle, byte flags)
{
	if (_offsets == NULL)
		return;

	_file->seek(_offsets[sound], SEEK_SET);

	int i = 1;
	while (_offsets[sound + i] == _offsets[sound])
			i++;

	uint32 size = _offsets[sound + i] - _offsets[sound];

	_mixer->playInputStream(handle, makeVorbisStream(_file, size), false);
}
#endif

#ifdef USE_FLAC
class FlacSound : public BaseSound {
public:
	FlacSound(SoundMixer *mixer, File *file, uint32 base = 0) : BaseSound(mixer, file, base) {};
	void playSound(uint sound, PlayingSoundHandle *handle, byte flags);
};

void FlacSound::playSound(uint sound, PlayingSoundHandle *handle, byte flags)
{
	if (_offsets == NULL)
		return;

	_file->seek(_offsets[sound], SEEK_SET);

	int i = 1;
	while (_offsets[sound + i] == _offsets[sound])
			i++;

	uint32 size = _offsets[sound + i] - _offsets[sound];

	_mixer->playInputStream(handle, makeFlacStream(_file, size), false);
}
#endif

Sound::Sound(const byte game, const GameSpecificSettings *gss, SoundMixer *mixer)
	: _game(game), _mixer(mixer) {
	_voice = 0;
	_effects = 0;

	_effects_paused = false;
	_ambient_paused = false;

	_filenums = 0;
	_last_voice_file = 0;
	_offsets = 0;

	_voice_file = false;
	_ambient_playing = 0;

	File *file = new File();
	const char *s;

#ifdef USE_FLAC
	if (!_voice && gss->flac_filename && gss->flac_filename[0]) {
		file->open(gss->flac_filename);
		if (file->isOpen()) {
			_voice_file = true;
			_voice = new FlacSound(_mixer, file);
		}
	}
#endif
#ifdef USE_MAD
	if (!_voice && gss->mp3_filename && gss->mp3_filename[0]) {
		file->open(gss->mp3_filename);
		if (file->isOpen()) {
			_voice_file = true;
			_voice = new MP3Sound(_mixer, file);
		}
	}
#endif
#ifdef USE_VORBIS
	if (!_voice && gss->vorbis_filename && gss->vorbis_filename[0]) {
		file->open(gss->vorbis_filename);
		if (file->isOpen()) {
			_voice_file = true;
			_voice = new VorbisSound(_mixer, file);
		}
	}
#endif
	if (!_voice) {
		// for simon2 mac/amiga, only read index file
		file->open("voices.idx");
		if (file->isOpen() == true) {
			file->seek(0, SEEK_END);
			int end = file->pos();
			file->seek(0, SEEK_SET);
			_filenums = (uint16 *)malloc((end / 6 + 1) * 2);
			_offsets = (uint32 *)malloc((end / 6 + 1) * 4);

			for (int i = 1; i <= end / 6; i++) {
				_filenums[i] = file->readUint16BE();
				_offsets[i] = file->readUint32BE();
			}
			_voice_file = true;
			delete file;
		} else if (_game & GF_WIN) {
			s = gss->wav_filename;
			file->open(s);
			if (file->isOpen() == false) {
				warning("Can't open voice file %s", s);
				delete file;
			} else	{
				_voice_file = true;
				_voice = new WavSound(_mixer, file);
			}
		} else if (_game == GAME_SIMON1CD32) {
			// simon1cd32 uses separate voice files
			return;
		} else if (_game & GF_TALKIE) {
			s = gss->voc_filename;
			file->open(s);
			if (file->isOpen() == false) {
				warning("Can't open voice file %s", s);
				delete file;
			} else {
				_voice_file = true;
				_voice = new VocSound(_mixer, file);
			}
		}
	}

	if (_game == GAME_SIMON1ACORN || _game == GAME_SIMON1TALKIE) {
		file = new File();
#ifdef USE_MAD
		if (!_effects && gss->mp3_effects_filename && gss->mp3_effects_filename[0]) {
			file->open(gss->mp3_effects_filename);
			if (file->isOpen()) {
				_effects = new MP3Sound(_mixer, file);
			}
		}
#endif
#ifdef USE_VORBIS
		if (!_effects && gss->vorbis_effects_filename && gss->vorbis_effects_filename[0]) {
			file->open(gss->vorbis_effects_filename);
			if (file->isOpen()) {
				_effects = new VorbisSound(_mixer, file);
			}
		}
#endif
#ifdef USE_FLAC
		if (!_effects && gss->flac_effects_filename && gss->flac_effects_filename[0]) {
			file->open(gss->flac_effects_filename);
			if (file->isOpen()) {
				_effects = new FlacSound(_mixer, file);
			}
		}
#endif
		if (!_effects) {
			s = gss->voc_effects_filename;
			file->open(s);
			if (file->isOpen() == false) {
				warning("Can't open effects file %s", s);
			} else {
				_effects = new VocSound(_mixer, file);
			}
		}
	}
}

Sound::~Sound() {
	delete _voice;
	delete _effects;
	
	free(_filenums);
	free(_offsets);
}

void Sound::readSfxFile(const char *filename) {
	stopAll();

	File *file = new File();
	file->open(filename);

	if (file->isOpen() == false) {
		char *filename2;
		filename2 = (char *)malloc(strlen(filename) + 2);
		strcpy(filename2, filename);
		strcat(filename2, ".");
		file->open(filename2);
		free(filename2);
		if (file->isOpen() == false) {
			if (atoi(filename + 6) != 1 && atoi(filename + 6) != 30)
				warning("readSfxFile: Can't load sfx file %s", filename);
			return;
		}
	}

	delete _effects;
	if (_game == GAME_SIMON1CD32) {
		_effects = new VocSound(_mixer, file, 0, SOUND_BIG_ENDIAN);
	} else
		_effects = new WavSound(_mixer, file);
}

void Sound::loadSfxTable(File *gameFile, uint32 base) {
	stopAll();

	if (_game & GF_WIN)
		_effects = new WavSound(_mixer, gameFile, base);
	else
		_effects = new VocSound(_mixer, gameFile, base);
}

void Sound::readVoiceFile(const char *filename) {
	stopAll();

	File *file = new File();
	file->open(filename);

	if (file->isOpen() == false) {
		char *filename2;
		filename2 = (char *)malloc(strlen(filename) + 2);
		strcpy(filename2, filename);
		strcat(filename2, ".");
		file->open(filename2);
		free(filename2);
		if (file->isOpen() == false) {
			warning("readVoiceFile: Can't load voice file %s", filename);
			return;
		}
	}

	delete _voice;
	_voice = new RawSound(_mixer, file, 0, SOUND_BIG_ENDIAN);
}

void Sound::playVoice(uint sound) {
	if (_filenums) {
		if (_last_voice_file != _filenums[sound]) {
			stopAll();

			char filename[16];
			_last_voice_file = _filenums[sound];
			sprintf(filename, "voices%d.dat", _filenums[sound]);
			File *file = new File();
			file->open(filename);
			if (file->isOpen() == false) {
				warning("playVoice: Can't load voice file %s", filename);
				return;
			} 
			delete _voice;
			_voice = new WavSound(_mixer, file, _offsets);
		}
	}

	if (!_voice)
		return;

	_mixer->stopHandle(_voice_handle);
	_voice->playSound(sound, &_voice_handle, (_game == GAME_SIMON1CD32) ? 0 : SoundMixer::FLAG_UNSIGNED);
}

void Sound::playEffects(uint sound) {
	if (!_effects)
		return;
	
	if (_effects_paused)
		return;

	_effects->playSound(sound, &_effects_handle, (_game == GAME_SIMON1CD32) ? 0 : SoundMixer::FLAG_UNSIGNED);
}

void Sound::playAmbient(uint sound) {
	if (!_effects)
		return;

	if (sound == _ambient_playing)
		return;

	_ambient_playing = sound;

	if (_ambient_paused)
		return;

	_mixer->stopHandle(_ambient_handle);
	_effects->playSound(sound, &_ambient_handle, SoundMixer::FLAG_LOOP|SoundMixer::FLAG_UNSIGNED);
}

bool Sound::hasVoice() {
	return _voice_file;
}

void Sound::stopVoice() {
	_mixer->stopHandle(_voice_handle);
}

void Sound::stopAll() {
	_mixer->stopAll();
	_ambient_playing = 0;
}

void Sound::effectsPause(bool b) {
	_effects_paused = b;
}

void Sound::ambientPause(bool b) {
	_ambient_paused = b;

	if (_ambient_paused && _ambient_playing) {
		_mixer->stopHandle(_ambient_handle);
	} else if (_ambient_playing) {
		uint tmp = _ambient_playing;
		_ambient_playing = 0;
		playAmbient(tmp);
	}
}

} // End of namespace Simon
