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

#include "common/scummsys.h"
#include "common/file.h"
#include "common/mutex.h"
#include "common/queue.h"
#include "audio/audiostream.h"
#include "audio/fmopl.h"
#include "audio/mixer.h"

namespace MADS {

class SoundManager;

namespace Nebular {

/**
 * Represents the data for a channel on the Adlib
 */
class AdlibChannel {
public:
	int _activeCount;
	int _field1;
	int _field2;
	int _field3;
	int _field4;
	int _sampleIndex;
	int _volume;
	int _field7;
	int _field8;
	int _field9;
	int _fieldA;
	uint8 _fieldB;
	int _fieldC;
	int _fieldD;
	int _fieldE;
	byte *_ptr1;
	byte *_pSrc;
	byte *_ptr3;
	byte *_ptr4;
	int _field17;
	int _field19;
	byte *_soundData;
	int _field1D;
	int _field1E;
	int _field1F;

	// TODO: Only used by asound.003. Figure out usage
	byte _field20;
public:
	static bool _channelsEnabled;
public:
	AdlibChannel();

	void reset();
	void enable(int flag);
	void setPtr2(byte *pData);
	void load(byte *pData);
	void check(byte *nullPtr);
};

class AdlibChannelData {
public:
	int _field0;
	int _freqMask;
	int _freqBase;
	int _field6;
};

class AdlibSample {
public:
	int _attackRate;
	int _decayRate;
	int _sustainLevel;
	int _releaseRate;
	bool _egTyp;
	bool _ksr;
	int _totalLevel;
	int _scalingLevel;
	int _waveformSelect;
	int _freqMultiple;
	int _feedback;
	bool _ampMod;
	int _vib;
	int _alg;
	int _fieldE;
	int _freqMask;
	int _freqBase;
	int _field14;

	AdlibSample() {}
	AdlibSample(Common::SeekableReadStream &s);
};

struct RegisterValue {
	uint8 _regNum;
	uint8 _value;

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

#define ADLIB_CHANNEL_COUNT 9
#define ADLIB_CHANNEL_MIDWAY 5
#define CALLBACKS_PER_SECOND 60

/**
 * Base class for the sound player resource files
 */
class ASound : public Audio::AudioStream {
private:
	struct CachedDataEntry {
		int _offset;
		byte *_data;
	};
	Common::List<CachedDataEntry> _dataCache;
	uint16 _randomSeed;

	/**
	 * Does the initial Adlib initialisation
	 */
	void adlibInit();

	/**
	 * Does on-going processing for the Adlib sounds being played
	 */
	void update();

	/**
	 * Polls each of the channels for updates
	 */
	void pollChannels();

	/**
	 * Checks the status of the channels
	 */
	void checkChannels();

	/**
	 * Polls the currently active channel
	 */
	void pollActiveChannel();

	/**
	 * Updates the octave of the currently active channel
	 */
	void updateOctave();

	void updateChannelState();
	void updateActiveChannel();

	/**
	 * Loads up the specified sample
	 */
	void loadSample(int sampleIndex);

	/**
	 * Writes out the data of the selected sample to the Adlib
	 */
	void processSample();

	void updateFNumber();
protected:
	int _commandParam;

	/**
	 * Queue a byte for an Adlib register
	 */
	void write(int reg, int val);

	/**
	 * Queue a byte for an Adlib register, and store it in the _ports array
	 */
	int write2(int state, int reg, int val);

	/**
	 * Flush any pending Adlib register values to the OPL driver
	 */
	void flush();

	/**
	 * Turn a channel on
	 */
	void channelOn(int reg, int volume);

	/**
	 * Turn a channel off
	 */
	void channelOff(int reg);

	/**
	 * Checks for whether a poll result needs to be set
	 */
	void resultCheck();

	/**
	 * Loads a data block from the sound file, caching the result for any future
	 * calls for the same data
	 */
	byte *loadData(int offset, int size);

	/**
	 * Play the specified sound
	 * @param offset	Offset of sound data within sound player data segment
	 * @param size		Size of sound data block
	 */
	void playSound(int offset, int size);

	/**
	 * Play the specified raw sound data
	 * @param pData		Pointer to data block containing sound data
	 * @param startingChannel	Channel to start scan from
	 */
	void playSoundData(byte *pData, int startingChannel = ADLIB_CHANNEL_MIDWAY);

	/**
	 * Checks to see whether the given block of data is already loaded into a channel.
	 */
	bool isSoundActive(byte *pData);

	/**
	 * Sets the frequency for a given channel.
	 */
	void setFrequency(int channel, int freq);

	/**
	 * Returns a 16-bit random number
	 */
	int getRandomNumber();

	virtual int command0();
	int command1();
	int command2();
	int command3();
	int command4();
	int command5();
	int command6();
	int command7();
	int command8();

	int nullCommand() { return 0; }
public:
	Audio::Mixer *_mixer;
	FM_OPL *_opl;
	Audio::SoundHandle _soundHandle;
	AdlibChannel _channels[ADLIB_CHANNEL_COUNT];
	AdlibChannel *_activeChannelPtr;
	AdlibChannelData _channelData[11];
	Common::Array<AdlibSample> _samples;
	AdlibSample *_samplePtr;
	Common::File _soundFile;
	Common::Queue<RegisterValue> _queue;
	Common::Mutex _driverMutex;
	int _dataOffset;
	int _frameCounter;
	bool _isDisabled;
	int _v1;
	int _v2;
	int _activeChannelNumber;
	int _freqMask1;
	int _freqMask2;
	int _freqBase1;
	int _freqBase2;
	int _channelNum1, _channelNum2;
	int _v7;
	int _v8;
	int _v9;
	int _v10;
	int _pollResult;
	int _resultFlag;
	byte _nullData[2];
	int _ports[256];
	bool _stateFlag;
	int _activeChannelReg;
	int _v11;
	bool _amDep, _vibDep, _splitPoint;
	int _samplesPerCallback;
	int _samplesPerCallbackRemainder;
	int _samplesTillCallback;
	int _samplesTillCallbackRemainder;
public:
	/**
	 * Constructor
	 * @param filename		Specifies the adlib sound player file to use
	 * @param dataOffset	Offset in the file of the data segment
	 */
	ASound(Audio::Mixer *mixer, const Common::String &filename, int dataOffset);

	/**
	 * Destructor
	 */
	virtual ~ASound();

	/**
	 * Execute a player command. Most commands represent sounds to play, but some
	 * low number commands also provide control operations.
	 * @param commandId		Player ommand to execute.
	 * @param param			Optional parameter used by a few commands
	 */
	virtual int command(int commandId, int param) = 0;

	/**
	 * Stop all currently playing sounds
	 */
	int stop();

	/**
	 * Main poll method to allow sounds to progress
	 */
	int poll();

	/**
	 * General noise/note output
	 */
	void noise();

	/**
	 * Return the current frame counter
	 */
	int getFrameCounter() { return _frameCounter; }

	// AudioStream interface
	/**
	 * Main buffer read
	 */
	virtual int readBuffer(int16 *buffer, const int numSamples);

	/**
	 * Mono sound only
	 */
	virtual bool isStereo() const { return false; }

	/**
	 * Data is continuously pushed, so definitive end
	 */
	virtual bool endOfData() const { return false; }

	/**
	 * Return sample rate
	 */
	virtual int getRate() const { return 11025; }
};

class ASound1 : public ASound {
private:
	typedef int (ASound1::*CommandPtr)();
	static const CommandPtr _commandList[42];
	bool _cmd23Toggle;

	int command9();
	int command10();
	int command11();
	int command12();
	int command13();
	int command14();
	int command15();
	int command16();
	int command17();
	int command18();
	int command19();
	int command20();
	int command21();
	int command22();
	int command23();
	int command24();
	int command25();
	int command26();
	int command27();
	int command28();
	int command29();
	int command30();
	int command31();
	int command32();
	int command33();
	int command34();
	int command35();
	int command36();
	int command37();
	int command38();
	int command39();
	int command40();
	int command41();

	void command111213();
	int command2627293032();
public:
	ASound1(Audio::Mixer *mixer);

	virtual int command(int commandId, int param);
};

class ASound2 : public ASound {
private:
	byte _command12Param;
private:
	typedef int (ASound2::*CommandPtr)();
	static const CommandPtr _commandList[44];

	virtual int command0();
	int command9();
	int command10();
	int command11();
	int command12();
	int command13();
	int command14();
	int command15();
	int command16();
	int command17();
	int command18();
	int command19();
	int command20();
	int command21();
	int command22();
	int command23();
	int command24();
	int command25();
	int command26();
	int command27();
	int command28();
	int command29();
	int command30();
	int command31();
	int command32();
	int command33();
	int command34();
	int command35();
	int command36();
	int command37();
	int command38();
	int command39();
	int command40();
	int command41();
	int command42();
	int command43();

	void command9Randomize();
	void command9Apply(byte *data, int val, int incr);
public:
	ASound2(Audio::Mixer *mixer);

	virtual int command(int commandId, int param);
};

class ASound3 : public ASound {
private:
	bool _command39Flag;

	typedef int (ASound3::*CommandPtr)();
	static const CommandPtr _commandList[61];

	int command9();
	int command10();
	int command11();
	int command13();
	int command14();
	int command15();
	int command16();
	int command17();
	int command18();
	int command19();
	int command20();
	int command21();
	int command22();
	int command23();
	int command24();
	int command25();
	int command26();
	int command27();
	int command28();
	int command29();
	int command30();
	int command31();
	int command32();
	int command33();
	int command34();
	int command35();
	int command36();
	int command37();
	int command38();
	int command39();
	int command40();
	int command41();
	int command42();
	int command43();
	int command44();
	int command45();
	int command46();
	int command47();
	int command49();
	int command50();
	int command51();
	int command57();
	int command59();
	int command60();

	void command9Randomize();
	void command9Apply(byte *data, int val, int incr);
public:
	ASound3(Audio::Mixer *mixer);

	virtual int command(int commandId, int param);
};

class ASound4 : public ASound {
private:
	typedef int (ASound4::*CommandPtr)();
	static const CommandPtr _commandList[61];

	int command10();
	int command12();
	int command19();
	int command20();
	int command21();
	int command24();
	int command27();
	int command30();
	int command32();
	int command33();
	int command34();
	int command35();
	int command36();
	int command37();
	int command38();
	int command43();
	int command52();
	int command53();
	int command54();
	int command55();
	int command56();
	int command57();
	int command58();
	int command59();
	int command60();

	void method1();
public:
	ASound4(Audio::Mixer *mixer);

	virtual int command(int commandId, int param);
};

class ASound5 : public ASound {
private:
	typedef int (ASound5::*CommandPtr)();
	static const CommandPtr _commandList[42];

	int command9();
	int command10();
	int command11();
	int command12();
	int command13();
	int command14();
	int command15();
	int command16();
	int command17();
	int command18();
	int command19();
	int command20();
	int command21();
	int command22();
	int command23();
	int command24();
	int command25();
	int command26();
	int command27();
	int command28();
	int command29();
	int command30();
	int command31();
	int command32();
	int command33();
	int command34();
	int command35();
	int command36();
	int command37();
	int command38();
	int command39();
	int command40();
	int command41();
	int command42();
	int command43();
public:
	ASound5(Audio::Mixer *mixer);

	virtual int command(int commandId, int param);
};

class ASound6 : public ASound {
private:
	typedef int (ASound6::*CommandPtr)();
	static const CommandPtr _commandList[30];

	int command9();
	int command10();
	int command11();
	int command12();
	int command13();
	int command14();
	int command15();
	int command16();
	int command17();
	int command18();
	int command19();
	int command20();
	int command21();
	int command22();
	int command23();
	int command24();
	int command25();
	int command29();
public:
	ASound6(Audio::Mixer *mixer);

	virtual int command(int commandId, int param);
};

class ASound7 : public ASound {
private:
	typedef int (ASound7::*CommandPtr)();
	static const CommandPtr _commandList[38];

	int command9();
	int command15();
	int command16();
	int command18();
	int command19();
	int command20();
	int command21();
	int command22();
	int command23();
	int command24();
	int command25();
	int command26();
	int command27();
	int command28();
	int command30();
	int command32();
	int command33();
	int command34();
	int command35();
	int command36();
	int command37();
public:
	ASound7(Audio::Mixer *mixer);

	virtual int command(int commandId, int param);
};

class ASound8 : public ASound {
private:
	typedef int (ASound8::*CommandPtr)();
	static const CommandPtr _commandList[38];

	int command9();
	int command10();
	int command11();
	int command12();
	int command13();
	int command14();
	int command15();
	int command16();
	int command17();
	int command18();
	int command19();
	int command20();
	int command21();
	int command22();
	int command23();
	int command24();
	int command25();
	int command26();
	int command27();
	int command28();
	int command29();
	int command30();
	int command31();
	int command32();
	int command33();
	int command34();
	int command35();
	int command36();
	int command37();

	void method1(byte *pData);
	void adjustRange(byte *pData, byte v, int incr);
public:
	ASound8(Audio::Mixer *mixer);

	virtual int command(int commandId, int param);
};

} // End of namespace Nebular

} // End of namespace MADS

#endif /* MADS_SOUND_NEBULAR_H */
