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

#ifndef TITANIC_SOUND_MANAGER_H
#define TITANIC_SOUND_MANAGER_H

#include "titanic/core/list.h"
#include "titanic/support/simple_file.h"
#include "titanic/sound/audio_buffer.h"
#include "titanic/sound/proximity.h"
#include "titanic/sound/qmixer.h"
#include "titanic/sound/wave_file.h"
#include "titanic/true_talk/dialogue_file.h"

namespace Titanic {

enum VolumeMode {
	VOL_NORMAL = -1, VOL_QUIET = -2, VOL_VERY_QUIET = -3, VOL_MUTE = -4
};

/**
 * Abstract interface class for a sound manager
 */
class CSoundManager {
protected:
	uint _handleCtr;
	// Old volume levels, deprecated in favor of setting the volumes
	// directly in the ScummVM mixer
	double _musicPercent;
	double _speechPercent;
	double _masterPercent;
	double _parrotPercent;
public:
	CSoundManager();
	virtual ~CSoundManager() {}

	/**
	 * Loads a sound
	 * @param name		Name of sound resource
	 * @returns			Loaded wave file
	 */
	virtual CWaveFile *loadSound(const CString &name) { return nullptr; }

	/**
	 * Loads a speech resource from a dialogue file
	 * @param name		Name of sound resource
	 * @returns			Loaded wave file
	 */
	virtual CWaveFile *loadSpeech(CDialogueFile *dialogueFile, int speechId) { return 0; }

	/**
	 * Loads a music file
	 * @param name		Name of music resource
	 * @returns			Loaded wave file
	 * @remarks The original only classified music as what's produced in the
	 * music room puzzle. For ScummVM, we've reclassified some wave files that
	 * contain background music as music as well.
	 */
	virtual CWaveFile *loadMusic(const CString &name) { return nullptr; }

	/**
	 * Loads a music file from a streaming audio buffer
	 * @param buffer	Audio buffer
	 * @returns			Loaded wave file
	 */
	virtual CWaveFile *loadMusic(CAudioBuffer *buffer, DisposeAfterUse::Flag disposeAfterUse) { return nullptr; }

	/**
	 * Start playing a previously loaded wave file
	 */
	virtual int playSound(CWaveFile &waveFile, CProximity &prox) = 0;

	/**
	 * Stop playing the specified sound
	 */
	virtual void stopSound(int handle) = 0;

	/**
	 * Stops a designated range of channels
	 */
	virtual void stopChannel(int channel) = 0;

	virtual void proc9(int handle) {}

	/**
	 * Stops sounds on all playing channels
	 */
	virtual void stopAllChannels() = 0;

	/**
	 * Sets the volume for a sound
	 * @param handle	Handle for sound
	 * @param volume	New volume
	 * @param seconds	Number of seconds to transition to the new volume
	 */
	virtual void setVolume(int handle, uint volume, uint seconds) = 0;

	/**
	 * Set the position for a sound
	 * @param handle	Handle for sound
	 * @param x			x position in metres
	 * @param y			y position in metres
	 * @param z			z position in metres
	 * @param panRate	Rate in milliseconds to transition
	 */
	virtual void setVectorPosition(int handle, double x, double y, double z, uint panRate) {}

	/**
	 * Set the position for a sound
	 * @param handle	Handle for sound
	 * @param range		Range value in metres
	 * @param azimuth	Azimuth value in degrees
	 * @param elevation	Elevation value in degrees
	 * @param panRate	Rate in milliseconds to transition
	 */
	virtual void setPolarPosition(int handle, double range, double azimuth, double elevation, uint panRate) {}

	/**
	 * Returns true if the given sound is currently active
	 */
	virtual bool isActive(int handle) = 0;

	/**
	 * Returns true if the given sound is currently active
	 */
	virtual bool isActive(const CWaveFile *waveFile) { return false; }

	/**
	 * Handles regularly updating the mixer
	 */
	virtual void waveMixPump() = 0;

	/**
	 * Returns the movie latency
	 */
	virtual uint getLatency() const { return 0; }

	/**
	 * Sets the music volume percent
	 */
	virtual void setMusicPercent(double percent) = 0;

	/**
	 * Sets the speech volume percent
	 */
	virtual void setSpeechPercent(double percent) = 0;

	/**
	 * Sets the master volume percent
	 */
	virtual void setMasterPercent(double percent) = 0;

	/**
	 * Sets the Parrot NPC volume percent
	 */
	virtual void setParrotPercent(double percent) = 0;

	/**
	 * Called when a game is about to be loaded
	 */
	virtual void preLoad() { stopAllChannels(); }

	/**
	 * Load the data for the class from file
	 */
	void load(SimpleFile *file) {}

	/**
	 * Called after loading of a game is completed
	 */
	virtual void postLoad() {}

	/**
	 * Called when a game is about to be saved
	 */
	virtual void preSave() {}

	/**
	 * Save the data for the class to file
	 */
	void save(SimpleFile *file) const {}

	/**
	 * Called after saving is complete
	 */
	virtual void postSave() {}

	/**
	 * Sets the position and orientation for the listener (player)
	 */
	virtual void setListenerPosition(double posX, double posY, double posZ,
		double directionX, double directionY, double directionZ, bool stopSounds) {}

	/**
	 * Returns the music volume percent
	 */
	double getMusicVolume() const { return _musicPercent; }

	/**
	 * Returns the speech volume percent
	 */
	double getSpeechVolume() const { return _speechPercent; }

	/**
	 * Returns the parrot volume percent
	 */
	double getParrotVolume() const { return _parrotPercent; }

	/**
	 * Gets the volume for a given mode? value
	 */
	uint getModeVolume(VolumeMode mode);
};

class QSoundManagerSound : public ListItem {
public:
	CWaveFile *_waveFile;
	int _iChannel;
	CEndTalkerFn _endFn;
	TTtalker *_talker;
public:
	QSoundManagerSound() : ListItem(), _waveFile(nullptr),
		_iChannel(0), _endFn(nullptr), _talker(nullptr) {}
	QSoundManagerSound(CWaveFile *waveFile, int iChannel, CEndTalkerFn endFn, TTtalker *talker) :
		ListItem(), _waveFile(waveFile), _iChannel(iChannel), _endFn(endFn), _talker(talker) {}
};

class QSoundManagerSounds : public List<QSoundManagerSound> {
public:
	/**
	 * Adds a new sound entry to the list
	 */
	void add(CWaveFile *waveFile, int iChannel, CEndTalkerFn endFn, TTtalker *talker);

	/**
	 * Flushes a wave file attached to the specified channel
	 */
	void flushChannel(int iChannel);

	/**
	 * Flushes a wave file attached to the specified channel
	 */
	void flushChannel(CWaveFile *waveFile, int iChannel);

	/**
	 * Returns true if the list contains the specified wave file
	 */
	bool contains(const CWaveFile *waveFile) const;
};

/**
 * Concrete sound manager class that handles interfacing with
 * the QMixer sound mixer class
 */
class QSoundManager : public CSoundManager, public QMixer {
	struct Slot {
		CWaveFile *_waveFile;
		bool _isTimed;
		uint _ticks;
		int _channel;
		int _handle;
		PositioningMode _positioningMode;

		Slot() : _waveFile(0), _isTimed(0), _ticks(0), _channel(-1),
			_handle(0), _positioningMode(POSMODE_NONE) {}
		void clear();
	};
private:
	QSoundManagerSounds _sounds;
	Common::Array<Slot> _slots;
	uint _channelsVolume[16];
	int _channelsMode[16];
private:
	/**
	 * Updates the volume for a channel
	 * @param channel	Channel to be update
	 * @param panRate	Time in milliseconds for change to occur
	 */
	void updateVolume(int channel, uint panRate);

	/**
	 * Updates all the volumes
	 */
	void updateVolumes();

	/**
	 * Called by the QMixer when a sound finishes playing
	 */
	static void soundFinished(int iChannel, CWaveFile *waveFile, void *soundManager);

	/**
	 * Finds the first free slot
	 */
	int findFreeSlot();

	/**
	 * Sets a channel volume
	 */
	void setChannelVolume(int iChannel, uint volume, uint mode);

	/**
	 * Resets the specified channel and returns a new free one
	 */
	int resetChannel(int iChannel);
public:
	int _field18;
	int _field1C;

public:
	QSoundManager(Audio::Mixer *mixer);
	~QSoundManager() override;

	/**
	 * Loads a sound
	 * @param name		Name of sound resource
	 * @returns			Loaded wave file
	 */
	CWaveFile *loadSound(const CString &name) override;

	/**
	 * Loads a speech resource from a dialogue file
	 * @param name		Name of sound resource
	 * @returns			Loaded wave file
	 */
	CWaveFile *loadSpeech(CDialogueFile *dialogueFile, int speechId) override;

	/**
	 * Loads a music file
	 * @param name		Name of music resource
	 * @returns			Loaded wave file
	 * @remarks The original only classified music as what's produced in the
	 * music room puzzle. For ScummVM, we've reclassified some wave files that
	 * contain background music as music as well.
	 */
	CWaveFile *loadMusic(const CString &name) override;

	/**
	 * Loads a music file from a streaming audio buffer
	 * @param buffer	Audio buffer
	 * @returns			Loaded wave file
	 */
	CWaveFile *loadMusic(CAudioBuffer *buffer, DisposeAfterUse::Flag disposeAfterUse) override;

	/**
	 * Start playing a previously loaded sound resource
	 */
	int playSound(CWaveFile &waveFile, CProximity &prox) override;

	/**
	 * Stop playing the specified sound
	 */
	void stopSound(int handle) override;

	/**
	 * Stops a designated range of channels
	 */
	void stopChannel(int channel) override;

	/**
	 * Flags that a sound can be freed if a timeout is set
	 */
	virtual void setCanFree(int handle);

	/**
	 * Stops sounds on all playing channels
	 */
	void stopAllChannels() override;

	/**
	 * Sets the volume for a sound
	 * @param handle	Handle for sound
	 * @param volume	New volume
	 * @param seconds	Number of seconds to transition to the new volume
	 */
	void setVolume(int handle, uint volume, uint seconds) override;

	/**
	 * Set the position for a sound
	 * @param handle	Handle for sound
	 * @param x			x position in metres
	 * @param y			y position in metres
	 * @param z			z position in metres
	 * @param panRate	Rate in milliseconds to transition
	 */
	void setVectorPosition(int handle, double x, double y, double z, uint panRate) override;

	/**
	 * Set the position for a sound
	 * @param handle	Handle for sound
	 * @param range		Range value in metres
	 * @param azimuth	Azimuth value in degrees
	 * @param elevation	Elevation value in degrees
	 * @param panRate	Rate in milliseconds to transition
	 */
	void setPolarPosition(int handle, double range, double azimuth, double elevation, uint panRate) override;

	/**
	 * Returns true if the given sound is currently active
	 */
	bool isActive(int handle) override;

	/**
	 * Returns true if the given sound is currently active
	 */
	bool isActive(const CWaveFile *waveFile) override;

	/**
	 * Handles regularly updating the mixer
	 */
	void waveMixPump() override;

	/**
	 * Returns the movie latency
	 */
	uint getLatency() const override;

	/**
	 * Sets the music volume percent
	 */
	void setMusicPercent(double percent) override;

	/**
	 * Sets the speech volume percent
	 */
	void setSpeechPercent(double percent) override;

	/**
	 * Sets the master volume percent
	 */
	void setMasterPercent(double percent) override;

	/**
	 * Sets the Parrot NPC volume percent
	 */
	void setParrotPercent(double percent) override;

	/**
	 * Sets the position and orientation for the listener (player)
	 */
	void setListenerPosition(double posX, double posY, double posZ,
		double directionX, double directionY, double directionZ, bool stopSounds) override;

	/**
	 * Starts a wave file playing
	 */
	virtual int playWave(CWaveFile *waveFile, int iChannel, uint flags, CProximity &prox);

	/**
	 * Called when a wave file is freed
	 */
	void soundFreed(Audio::SoundHandle &handle);
};

} // End of namespace Titanic

#endif /* TITANIC_QSOUND_MANAGER_H */
