/* 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 TSAGE_SOUND_H
#define TSAGE_SOUND_H

#include "common/scummsys.h"
#include "common/mutex.h"
#include "common/queue.h"
#include "audio/mixer.h"
#include "common/list.h"
#include "tsage/saveload.h"
#include "tsage/core.h"

namespace Audio {
class QueuingAudioStream;
}

namespace OPL {
class OPL;
}

namespace TsAGE {

class Sound;

#define SOUND_ARR_SIZE 16
#define ROLAND_DRIVER_NUM 2
#define ADLIB_DRIVER_NUM 3
#define SBLASTER_DRIVER_NUM 4
#define CALLBACKS_PER_SECOND 60

struct trackInfoStruct {
	int _numTracks;
	int _chunks[SOUND_ARR_SIZE];
	int _voiceTypes[SOUND_ARR_SIZE];
};

enum SoundDriverStatus {SNDSTATUS_FAILED = 0, SNDSTATUS_DETECTED = 1, SNDSTATUS_SKIPPED = 2};
enum VoiceType {VOICETYPE_0 = 0, VOICETYPE_1 = 1};

class SoundDriverEntry {
public:
	int _driverNum;
	SoundDriverStatus _status;
	int _field2, _field6;
	Common::String _shortDescription;
	Common::String _longDescription;
};

struct GroupData {
	uint32 _groupMask;
	const byte *_pData;
};

struct RegisterValue {
	uint8 _regNum;
	uint8 _value;

	RegisterValue(int regNum, int value) {
		_regNum = regNum; _value = value;
	}
};

class SoundDriver {
public:
	Common::String _shortDescription, _longDescription;
	int _minVersion, _maxVersion;
	// The following fields were originally held in separate arrays in the SoundManager class
	uint32 _groupMask;
	const GroupData *_groupOffset;
	int _driverResID;
public:
	SoundDriver();
	virtual ~SoundDriver() {}

	const Common::String &getShortDriverDescription() { return _shortDescription; }
	const Common::String &getLongDriverDescription() { return _longDescription; }

	virtual bool open() { return true; }						// Method #0
	virtual void close() {}										// Method #1
	virtual bool reset() { return true; }						// Method #2
	virtual const GroupData *getGroupData() { return NULL; }	// Method #3
	virtual void installPatch(const byte *data, int size) {}	// Method #4
	virtual void poll() {}										// Method #5
	virtual void method6() {}									// Method #6
	virtual int setMasterVolume(int volume) { return 0; }		// Method #7
	virtual void proc16() {}									// Method #8
	virtual void proc18(int al, VoiceType voiceType) {}			// Method #9
	virtual void proc20(int al, VoiceType voiceType) {}			// Method #10
	virtual void proc22(int al, VoiceType voiceType, int v3) {}	// Method #11
	virtual void proc24(int channel, int voiceIndex, Sound *sound, int v1, int v2) {}
	virtual void setProgram(int channel, int program) {}			// Method #13
	virtual void setVolume1(int channel, int v2, int v3, int volume) {}
	virtual void setPitchBlend(int channel, int pitchBlend) {}		// Method #15
	virtual void playSound(const byte *channelData, int dataOffset, int program, int channel, int v0, int v1) {}// Method #16
	virtual void updateVoice(int channel) {}						// Method #17
	virtual void proc36() {}										// Method #18
	virtual void proc38(int channel, int cmd, int value) {}			// Method #19
	virtual void setPitch(int channel, int pitchBlend) {}			// Method #20
	virtual void proc42(int channel, int cmd, int value, int *v1, int *v2) {}	// Method #21
};

struct VoiceStructEntryType0 {
	Sound *_sound;
	int _channelNum;
	int _priority;
	bool _fieldA;
	Sound *_sound2;
	int _channelNum2;
	int _priority2;
	bool _field12;
	Sound *_sound3;
	int _channelNum3;
	int _priority3;
};

struct VoiceStructEntryType1 {
	int _field4;
	int _field5;
	int _field6;
	Sound *_sound;
	int _channelNum;
	int _priority;
	Sound *_sound2;
	int _channelNum2;
	int _priority2;
	Sound *_sound3;
	int _channelNum3;
	int _priority3;
};

struct VoiceStructEntry {
	int _voiceNum;
	int _field1;
	SoundDriver *_driver;

	VoiceStructEntryType0 _type0;
	VoiceStructEntryType1 _type1;
};

class VoiceTypeStruct {
public:
	VoiceType _voiceType;
	int _total;
	int _numVoices;
	int _field3;

	Common::Array<VoiceStructEntry> _entries;
};

class SoundManager : public SaveListener {
private:
	SoundDriver *instantiateDriver(int driverNum);
public:
	bool _sndmgrReady;
	int _ourSndResVersion, _ourDrvResVersion;
	SynchronizedList<Sound *> _playList;
	Common::List<SoundDriver *> _installedDrivers;
	VoiceTypeStruct *_voiceTypeStructPtrs[SOUND_ARR_SIZE];
	uint32 _groupsAvail;
	int _masterVol;
	int _newVolume;
	Common::Mutex _serverDisabledMutex;
	Common::Mutex _serverSuspendedMutex;
	bool _driversDetected;
	SynchronizedList<Sound *> _soundList;
	Common::List<SoundDriverEntry> _availableDrivers;
	bool _needToRethink;
	// Misc flags
	bool _soTimeIndexFlag;
public:
	SoundManager();
	~SoundManager() override;

	void dispatch();
	void listenerSynchronize(Serializer &s) override;
	virtual void postInit();
	void syncSounds();

	static void saveNotifier(bool postFlag);
	void saveNotifierProc(bool postFlag);
	static void loadNotifier(bool postFlag);
	void loadNotifierProc(bool postFlag);

	void installConfigDrivers();
	Common::List<SoundDriverEntry> &buildDriverList(bool detectFlag);
	Common::List<SoundDriverEntry> &getDriverList(bool detectFlag);
	void dumpDriverList();
	void installDriver(int driverNum);
	bool isInstalled(int driverNum) const;
	void unInstallDriver(int driverNum);
	void checkResVersion(const byte *soundData);
	int determineGroup(const byte *soundData);
	int extractPriority(const byte *soundData);
	int extractLoop(const byte *soundData);
	bool isOnPlayList(Sound *sound);
	void extractTrackInfo(trackInfoStruct *trackInfo, const byte *soundData, int groupNum);
	void addToSoundList(Sound *sound);
	void removeFromSoundList(Sound *sound);
	void addToPlayList(Sound *sound);
	void removeFromPlayList(Sound *sound);
	void rethinkVoiceTypes();
	void updateSoundVol(Sound *sound);
	void updateSoundPri(Sound *sound);
	void updateSoundLoop(Sound *sound);
	void setMasterVol(int volume);
	int getMasterVol() const;
	void loadSound(int soundNum, bool showErrors);
	void unloadSound(int soundNum);
	bool isFading();

	// _sf methods
	static SoundManager &sfManager();
	static void sfTerminate();
	static int sfDetermineGroup(const byte *soundData);
	static void sfAddToPlayList(Sound *sound);
	static void sfRemoveFromPlayList(Sound *sound);
	static bool sfIsOnPlayList(Sound *sound);
	static void sfRethinkSoundDrivers();
	static void sfRethinkVoiceTypes();
	static void sfUpdateVolume(Sound *sound);
	static void sfDereferenceAll();
	static void sfUpdatePriority(Sound *sound);
	static void sfUpdateLoop(Sound *sound);
	static void sfSetMasterVol(int volume);
	static void sfExtractTrackInfo(trackInfoStruct *trackInfo, const byte *soundData, int groupNum);
	static void sfExtractGroupMask();
	static bool sfInstallDriver(SoundDriver *driver);
	static void sfUnInstallDriver(SoundDriver *driver);
	static void sfInstallPatchBank(SoundDriver *driver, const byte *bankData);
	static void sfDoAddToPlayList(Sound *sound);
	static bool sfDoRemoveFromPlayList(Sound *sound);
	static void sfDoUpdateVolume(Sound *sound);
	static void sfSoundServer(void *);
	static void sfProcessFading();
	static void sfUpdateVoiceStructs();
	static void sfUpdateVoiceStructs2();
};

class Sound: public EventHandler {
private:
	void _prime(int soundResID, bool dontQueue);
	void _primeBuffer(const byte *soundData);
	void _unPrime();
public:
	bool _stoppedAsynchronously;
	int _soundResID;
	int _group;
	int _sndResPriority;
	int _fixedPriority;
	int _sndResLoop;
	int _fixedLoop;
	int _priority;
	int _volume;
	int _loop;
	int _pausedCount;
	int _mutedCount;
	int _hold;
	int _cueValue;
	int _fadeDest;
	int _fadeSteps;
	int _fadeTicks;
	int _fadeCounter;
	bool _stopAfterFadeFlag;
	uint32 _timer;
	uint32 _newTimeIndex;
	int _loopTimer;
	int _chProgram[SOUND_ARR_SIZE];
	int _chModulation[SOUND_ARR_SIZE];
	int _chVolume[SOUND_ARR_SIZE];
	int _chPan[SOUND_ARR_SIZE];
	int _chDamper[SOUND_ARR_SIZE];
	int _chPitchBlend[SOUND_ARR_SIZE];
	int _chVoiceType[SOUND_ARR_SIZE];
	int _chNumVoices[SOUND_ARR_SIZE];
	int _chSubPriority[SOUND_ARR_SIZE];
	int _chFlags[SOUND_ARR_SIZE];
	bool _chWork[SOUND_ARR_SIZE];
	trackInfoStruct _trackInfo;
	byte *_channelData[SOUND_ARR_SIZE];
	int _trkChannel[SOUND_ARR_SIZE];
	int _trkState[SOUND_ARR_SIZE];
	int _trkLoopState[SOUND_ARR_SIZE];
	int _trkIndex[SOUND_ARR_SIZE];
	int _trkLoopIndex[SOUND_ARR_SIZE];
	int _trkRest[SOUND_ARR_SIZE];
	int _trkLoopRest[SOUND_ARR_SIZE];

	bool _primed;
	bool _isEmpty;
	byte *_remoteReceiver;
public:
	Sound();
	~Sound() override;

	void synchronize(Serializer &s) override;
	void orientAfterRestore();

	void play(int soundResID);
	void stop();
	void prime(int soundResID);
	void unPrime();
	void go();
	void halt(void);
	bool isPlaying();
	int getSoundNum() const;
	bool isPrimed() const;
	bool isPaused() const;
	bool isMuted() const;
	void pause(bool flag);
	void mute(bool flag);
	void fade(int fadeDest, int fadeSteps, int fadeTicks, bool stopAfterFadeFlag);
	void setTimeIndex(uint32 timeIndex);
	uint32 getTimeIndex() const;
	int getCueValue() const;
	void setCueValue(int cueValue);
	void setVol(int volume);
	int getVol() const;
	void setPri(int priority);
	void setLoop(int flag);
	int getPri() const;
	int getLoop();
	void holdAt(int amount);
	void release();
	void orientAfterDriverChange();

	// _so methods
	void soPrimeSound(bool dontQueue);
	void soSetTimeIndex(uint timeIndex);
	bool soServiceTracks();
	void soPrimeChannelData();
	void soRemoteReceive();
	void soServiceTrackType0(int trackIndex, const byte *channelData);
	void soUpdateDamper(VoiceTypeStruct *voiceType, int channelNum, VoiceType mode, int v0);
	void soPlaySound(VoiceTypeStruct *vtStruct, const byte *channelData, int channelNum, VoiceType voiceType, int v0, int v1);
	void soPlaySound2(VoiceTypeStruct *vtStruct, const byte *channelData, int channelNum, VoiceType voiceType, int v0);
	void soProc38(VoiceTypeStruct *vtStruct, int channelNum, VoiceType voiceType, int cmd, int value);
	void soProc40(VoiceTypeStruct *vtStruct, int channelNum, int pitchBlend);
	void soDoTrackCommand(int channelNum, int command, int value);
	bool soDoUpdateTracks(int command, int value);
	void soSetTrackPos(int trackIndex, int trackPos, int cueValue);

	void soServiceTrackType1(int trackIndex, const byte *channelData);
	int soFindSound(VoiceTypeStruct *vtStruct, int channelNum);
};

class ASound: public EventHandler {
public:
	Sound _sound;
	EventHandler *_endAction;
	int _cueValue;

	ASound();
	~ASound() override;
	void synchronize(Serializer &s) override;
	void dispatch() override;

	void play(int soundNum, EventHandler *endAction = NULL, int volume = 127);
	void stop();
	void prime(int soundNum, Action *action = NULL);
	void unPrime();
	void go() { _sound.go(); }
	void hault(void) { _sound.halt(); }
	bool isPlaying() { return _sound.isPlaying(); }
	int getSoundNum() const { return _sound.getSoundNum(); }
	bool isPaused() const { return _sound.isPaused(); }
	bool isMuted() const { return _sound.isMuted(); }
	void pause(bool flag) { _sound.pause(flag); }
	void mute(bool flag) { _sound.mute(flag); }
	void fade(int fadeDest, int fadeSteps, int fadeTicks, bool stopAfterFadeFlag, EventHandler *endAction);
	void fadeIn() { fade(127, 5, 10, false, NULL); }
	void fadeOut(Action *action) { fade(0, 5, 10, true, action); }
	void setTimeIndex(uint32 timeIndex) { _sound.setTimeIndex(timeIndex); }
	uint32 getTimeIndex() const { return _sound.getTimeIndex(); }
	void setPri(int v) { _sound.setPri(v); }
	void setLoop(int total) { _sound.setLoop(total); }
	int getPri() const { return _sound.getPri(); }
	int getLoop() { return _sound.getLoop(); }
	void setVol(int volume) { _sound.setVol(volume); }
	int getVol() const { return _sound.getVol(); }
	void holdAt(int v) { _sound.holdAt(v); }
	void release() { _sound.release(); }
	void fadeSound(int soundNum);
};

class ASoundExt: public ASound {
public:
	int _soundNum;

	ASoundExt();
	void fadeOut2(EventHandler *endAction);
	void changeSound(int soundNum);

	Common::String getClassName() override { return "ASoundExt"; }
	void synchronize(Serializer &s) override;
	void signal() override;
};

class PlayStream: public EventHandler {
	class ResFileData {
	public:
		int _fileChunkSize;
		uint _indexSize;
		uint _chunkSize;

		void load(Common::SeekableReadStream &stream);
	};
private:
	Common::File _file;
	ResFileData _resData;
	Audio::QueuingAudioStream *_audioStream;
	Audio::SoundHandle _soundHandle;
	uint16 *_index;
	EventHandler *_endAction;
	int _voiceNum;

	static uint32 getFileOffset(const uint16 *data, int count, int voiceNum);
public:
	PlayStream();
	~PlayStream() override;

	bool setFile(const Common::Path &filename);
	bool play(int voiceNum, EventHandler *endAction);
	void stop();
	bool isPlaying() const;

	void remove() override;
	void dispatch() override;
};

#define ADLIB_CHANNEL_COUNT 9

class AdlibSoundDriver: public SoundDriver {
private:
	GroupData _groupData;
	Audio::Mixer *_mixer;
	OPL::OPL *_opl;
	byte _portContents[256];
	const byte *_patchData;
	int _masterVolume;
	Common::Mutex _queueMutex;
	Common::Queue<RegisterValue> _queue;

	bool _channelVoiced[ADLIB_CHANNEL_COUNT];
	int _channelVolume[ADLIB_CHANNEL_COUNT];
	int _v4405E[ADLIB_CHANNEL_COUNT];
	int _v44067[ADLIB_CHANNEL_COUNT];
	int _v44070[ADLIB_CHANNEL_COUNT];
	int _v44079[ADLIB_CHANNEL_COUNT];
	int _v44082[ADLIB_CHANNEL_COUNT + 1];
	int _pitchBlend[ADLIB_CHANNEL_COUNT];
	int _v4409E[ADLIB_CHANNEL_COUNT];


	void write(byte reg, byte value);
	void flush();
	void updateChannelVolume(int channel);
	void setVoice(int channel);
	void clearVoice(int channel);
	void updateChannel(int channel);
	void setFrequency(int channel);
public:
	AdlibSoundDriver();
	~AdlibSoundDriver() override;

	bool open() override;
	void close() override;
	bool reset() override;
	const GroupData *getGroupData() override;
	void installPatch(const byte *data, int size) override;
	int setMasterVolume(int volume) override;
	void playSound(const byte *channelData, int dataOffset, int program, int channel, int v0, int v1) override;
	void updateVoice(int channel) override;
	void proc38(int channel, int cmd, int value) override;
	void setPitch(int channel, int pitchBlend) override;

private:
	void onTimer();
};

class SoundBlasterDriver: public SoundDriver {
private:
	GroupData _groupData;
	Audio::Mixer *_mixer;
	Audio::SoundHandle _soundHandle;
	Audio::QueuingAudioStream *_audioStream;
	int _sampleRate;

	byte _masterVolume;
	byte _channelVolume;
	const byte *_channelData;
public:
	SoundBlasterDriver();
	~SoundBlasterDriver() override;

	bool open() override;
	void close() override;
	bool reset() override;
	const GroupData *getGroupData() override;
	int setMasterVolume(int volume) override;
	void playSound(const byte *channelData, int dataOffset, int program, int channel, int v0, int v1) override;
	void updateVoice(int channel) override;
	void proc38(int channel, int cmd, int value) override;
	void proc42(int channel, int cmd, int value, int *v1, int *v2) override;
};


} // End of namespace TsAGE

#endif
