/* 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.
 *
 */

#ifndef DRACI_SOUND_H
#define DRACI_SOUND_H

#include "common/str.h"
#include "common/file.h"
#include "common/list.h"
#include "audio/mixer.h"

namespace Common {
class Archive;
class SeekableReadStream;
}

namespace Draci {

enum SoundFormat { RAW, RAW80, MP3, OGG, FLAC };	// RAW80 means skip the first 80 bytes

/**
 *  Represents individual files inside the archive.
 */
struct SoundSample {
	uint _offset;		// For internal use of LegacySoundArchive
	uint _length;

	uint _frequency;	// Only when _format == RAW or RAW80
	SoundFormat _format;

	byte *_data;		// At most one of these two pointer can be non-NULL
	Common::SeekableReadStream* _stream;

	SoundSample() : _offset(0), _length(0), _frequency(0), _format(RAW), _data(NULL), _stream(NULL) { }
	// The standard copy constructor is good enough, since we only store numbers and pointers.
	// Don't call close() automaticall in the destructor, otherwise copying causes SIGSEGV.
	void close() {
		delete[] _data;
		delete _stream;
		_data = NULL;
		_stream = NULL;
	}
};

/**
 * An abstract wrapper around archives of sound samples or dubbing.
 */
class SoundArchive {
public:
	SoundArchive() { }
	virtual ~SoundArchive() { }

	/**
	 * Returns the number of sound samples in the archive.  Zero means that
	 * a fake empty archive has been opened and the caller may consider
	 * opening a different one, for example with compressed music.
	 */
	virtual uint size() const = 0;

	/**
	 * Checks whether there is an archive opened. Should be called before reading
	 * from the archive to check whether opening of the archive has succeeded.
	 */
	virtual bool isOpen() const = 0;

	/**
	 * Removes cached samples from memory.
	 */
	virtual void clearCache() = 0;

	/**
	 * Caches a given sample into memory and returns a pointer into it.  We
	 * own the returned pointer, but the user may call .close() on it,
	 * which deallocates the memory of the actual sample data.  If freq is
	 * nonzero, then the sample is played at a different frequency (only
	 * works for uncompressed samples).
	 */
	virtual SoundSample *getSample(int i, uint freq) = 0;
};

/**
 * Reads CD.SAM (with dubbing) and CD2.SAM (with sound samples) from the
 * original game.  Caches all read samples in a thread-safe manner.
 */
class LegacySoundArchive : public SoundArchive {
public:
	LegacySoundArchive(const char *path, uint defaultFreq) :
	_path(NULL), _samples(NULL), _sampleCount(0), _defaultFreq(defaultFreq), _opened(false), _f(NULL) {
		openArchive(path);
	}
	virtual ~LegacySoundArchive() { closeArchive(); }

	void openArchive(const char *path);
	void closeArchive();

	virtual uint size() const { return _sampleCount; }
	virtual bool isOpen() const { return _opened; }

	virtual void clearCache();
	virtual SoundSample *getSample(int i, uint freq);

private:
	const char *_path;    ///< Path to file
	SoundSample *_samples;          ///< Internal array of files
	uint _sampleCount;         ///< Number of files in archive
	uint _defaultFreq;	///< The default sampling frequency of the archived samples
	bool _opened;            ///< True if the archive is opened, false otherwise
	Common::File *_f;	///< Opened file
};

/**
 * Reads ZIP archives with uncompressed files containing lossy-compressed
 * samples in arbitrary format.  Doesn't do any real caching and is
 * thread-safe.
 */
class ZipSoundArchive : public SoundArchive {
public:
	ZipSoundArchive() : _archive(NULL), _path(NULL), _extension(NULL), _format(RAW), _sampleCount(0), _defaultFreq(0), _cache() { }
	virtual ~ZipSoundArchive() { closeArchive(); }

	void openArchive(const char *path, const char *extension, SoundFormat format, int raw_frequency = 0);
	void closeArchive();

	virtual uint size() const { return _sampleCount; }
	virtual bool isOpen() const { return _archive != NULL; }

	virtual void clearCache();
	virtual SoundSample *getSample(int i, uint freq);

private:
	Common::Archive *_archive;
	const char *_path;
	const char *_extension;
	SoundFormat _format;
	uint _sampleCount;
	uint _defaultFreq;

	// Since we typically play at most 1 dubbing at a time, we could get
	// away with having just 1 record allocated and reusing it each time.
	// However, that would be thread-unsafe if two samples were played.
	// Although the player does not do that, it is nicer to allow for it in
	// principle.  For each dubbed sentence, we allocate a new record in
	// the following link-list, which is cleared during each location
	// change.  The dubbed samples themselves are manually deallocated
	// after they end.
	Common::List<SoundSample> _cache;
};

#define SOUND_HANDLES 10

enum sndHandleType {
	kFreeHandle,
	kEffectHandle,
	kVoiceHandle
};

struct SndHandle {
	Audio::SoundHandle handle;
	sndHandleType type;
};

// Taken from engines/saga/sound.h and simplified (in particular, removed
// decompression until we support compressed files too).
class Sound {
public:

	Sound(Audio::Mixer *mixer);
	~Sound() {}

	uint playSound(const SoundSample *buffer, int volume, bool loop);
	void pauseSound();
	void resumeSound();
	void stopSound();
	bool isMutedSound() const { return _muteSound; }

	uint playVoice(const SoundSample *buffer);
	void pauseVoice();
	void resumeVoice();
	void stopVoice();
	bool isMutedVoice() const { return _muteVoice; }

	void stopAll() { stopVoice(); stopSound(); }

	void setVolume();

	bool showSubtitles() const { return _showSubtitles; }
	int talkSpeed() const { return _talkSpeed; }

 private:
	// Returns the length of the sound sample in miliseconds.
	uint playSoundBuffer(Audio::SoundHandle *handle, const SoundSample &buffer, int volume,
				sndHandleType handleType, bool loop);

	SndHandle *getHandle();

	Audio::Mixer *_mixer;

	bool _muteSound;
	bool _muteVoice;
	bool _showSubtitles;
	int _talkSpeed;

	SndHandle _handles[SOUND_HANDLES];
};

} // End of namespace Draci

#endif // DRACI_SOUND_H
