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

#include "common/md5.h"
#include "xeen/music.h"
#include "xeen/xeen.h"
#include "xeen/files.h"

namespace Xeen {

#define CALLBACKS_PER_SECOND 73

/*------------------------------------------------------------------------*/

MusicDriver::MusicDriver() : _musicPlaying(false), _fxPlaying(false),
		_musCountdownTimer(0), _fxCountdownTimer(0), _musDataPtr(nullptr),
		_fxDataPtr(nullptr), _fxStartPtr(nullptr), _musStartPtr(nullptr),
		_exclude7(false), _frameCtr(0) {
	_channels.resize(CHANNEL_COUNT);
}

MusicDriver::~MusicDriver() {
	_musicPlaying = _fxPlaying = false;
	_musCountdownTimer = _fxCountdownTimer = 0;
}

void MusicDriver::execute() {
	bool isFX = false;
	const byte *srcP = nullptr;
	const byte *startP = nullptr;

	// Single iteration loop to avoid use of GOTO
	do {
		if (_musicPlaying) {
			startP = _musStartPtr;
			srcP = _musDataPtr;
			isFX = false;
			if (_musCountdownTimer == 0 || --_musCountdownTimer == 0)
				break;
		}

		if (_fxPlaying) {
			startP = _fxStartPtr;
			srcP = _fxDataPtr;
			isFX = true;
			if (_fxCountdownTimer == 0 || --_fxCountdownTimer == 0)
				break;
		}

		pausePostProcess();
		return;
	} while (0);

	++_frameCtr;
	debugC(3, kDebugSound, "\nMusicDriver frame - #%x", _frameCtr);

	// Main loop
	bool breakFlag = false;
	while (!breakFlag) {
		debugCN(3, kDebugSound, "MUSCODE %.4x - %.2x  ", (uint)(srcP - startP), (uint)*srcP);
		byte nextByte = *srcP++;
		int cmd = (nextByte >> 4) & 15;
		int param = (nextByte & 15);

		CommandFn fn = isFX ? FX_COMMANDS[cmd] : MUSIC_COMMANDS[cmd];
		breakFlag = (this->*fn)(srcP, param);
	}
}


bool MusicDriver::musCallSubroutine(const byte *&srcP, byte param) {
	debugC(3, kDebugSound, "musCallSubroutine");
	if (_musSubroutines.size() < 16) {
		const byte *returnP = srcP + 2;
		srcP = _musStartPtr + READ_LE_UINT16(srcP);

		_musSubroutines.push(Subroutine(returnP, srcP));
	}

	return false;
}

bool MusicDriver::musSetCountdown(const byte *&srcP, byte param) {
	// Set the countdown timer
	if (!param)
		param = *srcP++;
	_musCountdownTimer = param;
	_musDataPtr = srcP;
	debugC(3, kDebugSound, "musSetCountdown %d", param);

	// Do paused handling and break out of processing loop
	pausePostProcess();
	return true;
}

bool MusicDriver::cmdNoOperation(const byte *&srcP, byte param) {
	debugC(3, kDebugSound, "cmdNoOperation");
	return false;
}

bool MusicDriver::musSkipWord(const byte *&srcP, byte param) {
	debugC(3, kDebugSound, "musSkipWord");
	srcP += 2;
	return false;
}

bool MusicDriver::cmdFreezeFrequency(const byte *&srcP, byte param) {
	debugC(3, kDebugSound, "cmdFreezeFrequency %d", param);
	_channels[param]._changeFrequency = false;
	return false;
}

bool MusicDriver::cmdChangeFrequency(const byte *&srcP, byte param) {
	debugC(3, kDebugSound, "cmdChangeFrequency %d", param);

	if (param != 7 || !_exclude7) {
		_channels[param]._freqCtrChange = (int8)*srcP++;
		_channels[param]._freqCtr = 0xFF;
		_channels[param]._changeFrequency = true;
		_channels[param]._freqChange = (int16)READ_BE_UINT16(srcP);
		srcP += 2;
	} else {
		srcP += 3;
	}

	return true;
}

bool MusicDriver::musEndSubroutine(const byte *&srcP, byte param) {
	debugC(3, kDebugSound, "musEndSubroutine %d", param);

	if (param != 15) {
		// Music has ended, so flag it stopped
		_musicPlaying = false;
		return true;
	}

	// Returning from subroutine, or looping back to start of music
	srcP = _musSubroutines.empty() ? _musStartPtr : _musSubroutines.pop()._returnP;
	return false;
}

bool MusicDriver::fxCallSubroutine(const byte *&srcP, byte param) {
	debugC(3, kDebugSound, "fxCallSubroutine");

	if (_fxSubroutines.size() < 16) {
		const byte *startP = srcP + 2;
		srcP = _musStartPtr + READ_LE_UINT16(srcP);

		_fxSubroutines.push(Subroutine(startP, srcP));
	}

	return false;
}

bool MusicDriver::fxSetCountdown(const byte *&srcP, byte param) {
	// Set the countdown timer
	if (!param)
		param = *srcP++;
	_fxCountdownTimer = param;
	_fxDataPtr = srcP;
	debugC(3, kDebugSound, "fxSetCountdown %d", param);

	// Do paused handling and break out of processing loop
	pausePostProcess();
	return true;
}

bool MusicDriver::fxEndSubroutine(const byte *&srcP, byte param) {
	debugC(3, kDebugSound, "fxEndSubroutine %d", param);

	if (param != 15) {
		// FX has ended, so flag it stopped
		_fxPlaying = false;
		return true;
	}

	srcP = _fxSubroutines.empty() ? _fxStartPtr : _fxSubroutines.pop()._returnP;
	return false;
}

void MusicDriver::playFX(uint effectId, const byte *data) {
	if (!_fxPlaying || effectId < 7 || effectId >= 11) {
		_musStartPtr = nullptr;
		_fxDataPtr = _fxStartPtr = data;
		_fxCountdownTimer = 0;
		_channels[7]._changeFrequency = _channels[8]._changeFrequency = false;
		resetFX();
		_fxPlaying = true;
	}

	debugC(1, kDebugSound, "Starting FX %d", effectId);
}

void MusicDriver::stopFX() {
	resetFX();
	_fxPlaying = false;
	_fxStartPtr = _fxDataPtr = nullptr;
}

void MusicDriver::playSong(const byte *data) {
	_musDataPtr = _musStartPtr = data;
	_musSubroutines.clear();
	_musCountdownTimer = 0;
	_musicPlaying = true;
	debugC(1, kDebugSound, "Starting song");
}

int MusicDriver::songCommand(uint commandId, byte volume) {
	if (commandId == STOP_SONG) {
		_musicPlaying = false;
	} else if (commandId == RESTART_SONG) {
		_musicPlaying = true;
		_musDataPtr = nullptr;
		_musSubroutines.clear();
	}

	return 0;
}

const CommandFn MusicDriver::MUSIC_COMMANDS[16] = {
	&MusicDriver::musCallSubroutine,   &MusicDriver::musSetCountdown,
	&MusicDriver::musSetInstrument,    &MusicDriver::cmdNoOperation,
	&MusicDriver::musSetPitchWheel,    &MusicDriver::musSkipWord,
	&MusicDriver::musSetPanning,       &MusicDriver::cmdNoOperation,
	&MusicDriver::musFade,             &MusicDriver::musStartNote,
	&MusicDriver::musSetVolume,        &MusicDriver::musInjectMidi,
	&MusicDriver::musPlayInstrument,   &MusicDriver::cmdFreezeFrequency,
	&MusicDriver::cmdChangeFrequency,  &MusicDriver::musEndSubroutine
};

const CommandFn MusicDriver::FX_COMMANDS[16] = {
	&MusicDriver::fxCallSubroutine,    &MusicDriver::fxSetCountdown,
	&MusicDriver::fxSetInstrument,     &MusicDriver::fxSetVolume,
	&MusicDriver::fxMidiReset,         &MusicDriver::fxMidiDword,
	&MusicDriver::fxSetPanning,        &MusicDriver::fxChannelOff,
	&MusicDriver::fxFade,              &MusicDriver::fxStartNote,
	&MusicDriver::cmdNoOperation,      &MusicDriver::fxInjectMidi,
	&MusicDriver::fxPlayInstrument,    &MusicDriver::cmdFreezeFrequency,
	&MusicDriver::cmdChangeFrequency,  &MusicDriver::fxEndSubroutine
};

/*------------------------------------------------------------------------*/

AdlibMusicDriver::AdlibMusicDriver() : _field180(0), _field181(0), _field182(0),
		_volume(127) {
	Common::fill(&_musInstrumentPtrs[0], &_musInstrumentPtrs[16], (const byte *)nullptr);
	Common::fill(&_fxInstrumentPtrs[0], &_fxInstrumentPtrs[16], (const byte *)nullptr);

	_opl = OPL::Config::create();
	_opl->init();
	_opl->start(new Common::Functor0Mem<void, AdlibMusicDriver>(this, &AdlibMusicDriver::onTimer), CALLBACKS_PER_SECOND);
	initialize();
}

AdlibMusicDriver::~AdlibMusicDriver() {
	_opl->stop();
	delete _opl;
}

void AdlibMusicDriver::onTimer() {
	Common::StackLock slock(_driverMutex);
	execute();
	flush();
}

void AdlibMusicDriver::initialize() {
	write(1, 0x20);
	write(8, 0);
	write(0xBD, 0);

	resetFrequencies();
	AdlibMusicDriver::resetFX();
}

void AdlibMusicDriver::playFX(uint effectId, const byte *data) {
	Common::StackLock slock(_driverMutex);
	MusicDriver::playFX(effectId, data);
}

void AdlibMusicDriver::playSong(const byte *data) {
	Common::StackLock slock(_driverMutex);
	MusicDriver::playSong(data);
	_field180 = 0;
	resetFrequencies();
}

int AdlibMusicDriver::songCommand(uint commandId, byte volume) {
	Common::StackLock slock(_driverMutex);
	MusicDriver::songCommand(commandId, volume);

	if (commandId == STOP_SONG) {
		_field180 = 0;
		resetFrequencies();
	} else if (commandId == RESTART_SONG) {
		_field180 = 0;
		_musicPlaying = true;
	} else if (commandId < 0x100) {
		if (_musicPlaying) {
			_field180 = commandId;
			_field182 = 63;
		}
	} else if (commandId == SET_VOLUME) {
		_volume = volume;
	} else if (commandId == GET_STATUS) {
		return _field180;
	}

	return 0;
}

void AdlibMusicDriver::write(int reg, int val) {
	_queue.push(RegisterValue(reg, val));
	debugC(9, kDebugSound, "%.2x %.2x", reg, val);
}

void AdlibMusicDriver::flush() {
	Common::StackLock slock(_driverMutex);

	while (!_queue.empty()) {
		RegisterValue v = _queue.pop();
		_opl->writeReg(v._regNum, v._value);
	}
}

void AdlibMusicDriver::pausePostProcess() {
	if (_field180 && ((_field181 += _field180) < 0)) {
		if (--_field182 < 0) {
			_musicPlaying = false;
			_field180 = 0;
			resetFrequencies();
		} else {
			for (int channelNum = 6; channelNum >= 0; --channelNum) {
				if (_channels[channelNum]._volume < 63)
					setOutputLevel(channelNum, ++_channels[channelNum]._volume);
			}
		}
	}

	for (int channelNum = 8; channelNum != 6 || (channelNum == 7 && _exclude7); --channelNum) {
		Channel &chan = _channels[channelNum];
		if (!chan._changeFrequency || (chan._freqCtr += chan._freqCtrChange) >= 0)
			continue;

		uint freq = chan._frequency & 0x3FF;
		uint val = chan._frequency >> 8;
		byte val1 = val & 0x20;
		byte val2 = val & 0x1C;

		freq += chan._freqChange;
		if (chan._freqChange < 0) {
			if (freq <= 388) {
				freq <<= 1;
				if (!(freq & 0x3FF))
					--freq;
			}

			val2 = (val2 - 4) & 0x1C;
		} else {
			if (freq >= 734) {
				freq >>= 1;
				if (!(freq & 0x3FF))
					++freq;
			}

			val2 = (val2 + 4) & 0x1C;
		}

		freq &= 0x3FF;
		freq |= (val2 << 8);
		freq |= val1;
		chan._frequency = freq;
		setFrequency(channelNum, freq);
	}
}

void AdlibMusicDriver::resetFX() {
	if (!_exclude7) {
		_channels[7]._frequency = 0;
		setFrequency(7, 0);
		_channels[7]._volume = 63;
		setOutputLevel(7, 63);
	}

	_channels[8]._frequency = 0;
	setFrequency(8, 0);
	_channels[8]._volume = 63;
	setOutputLevel(8, 63);
}

void AdlibMusicDriver::resetFrequencies() {
	for (int opNum = 6; opNum >= 0; --opNum) {
		_channels[opNum]._frequency = 0;
		setFrequency(opNum, 0);
	}
}

void AdlibMusicDriver::setFrequency(byte operatorNum, uint frequency) {
	write(0xA0 + operatorNum, frequency & 0xff);
	write(0xB0 + operatorNum, (frequency >> 8));
}

uint AdlibMusicDriver::calcFrequency(byte note) {
	return WAVEFORMS[note & 0x1F] + ((note & 0xE0) << 5);
}

void AdlibMusicDriver::setOutputLevel(byte channelNum, uint level) {
	write(0x40 + OPERATOR2_INDEXES[channelNum], level |
		(_channels[channelNum]._scalingValue & 0xC0));
}

void AdlibMusicDriver::playInstrument(byte channelNum, const byte *data) {
	byte op1 = OPERATOR1_INDEXES[channelNum];
	byte op2 = OPERATOR2_INDEXES[channelNum];
	debugC(2, kDebugSound, "---START-playInstrument - %d", channelNum);
	write(0x20 + op1, *data++);
	write(0x40 + op1, *data++);
	write(0x60 + op1, *data++);
	write(0x80 + op1, *data++);
	write(0xE0 + op1, *data++);
	write(0x20 + op2, *data++);

	int scalingVal = *data++;
	_channels[channelNum]._scalingValue = scalingVal;
	scalingVal += (127 - _volume) / 2;

	if (scalingVal > 63) {
		scalingVal = 63;
		if (_field180)
			scalingVal = (scalingVal & 0xC0) | _field182;
	}
	write(0x40 + op2, scalingVal);

	write(0x60 + op2, *data++);
	write(0x80 + op2, *data++);
	write(0xE0 + op2, *data++);
	write(0xC0 + channelNum, *data++);

	debugC(2, kDebugSound, "---END-playInstrument");
}

bool AdlibMusicDriver::musSetInstrument(const byte *&srcP, byte param) {
	debugC(3, kDebugSound, "musSetInstrument %d", param);
	_musInstrumentPtrs[param] = srcP;
	srcP += 26;

	return false;
}

bool AdlibMusicDriver::musSetPitchWheel(const byte *&srcP, byte param) {
	// Adlib does not support this
	debugC(3, kDebugSound, "musSetPitchWheel");
	srcP += 2;
	return false;
}

bool AdlibMusicDriver::musSetPanning(const byte *&srcP, byte param) {
	// Adlib does not support this
	debugC(3, kDebugSound, "musSetPanning");
	++srcP;
	return false;
}

bool AdlibMusicDriver::musFade(const byte *&srcP, byte param) {
	++srcP;
	if (param < 7)
		setFrequency(param, _channels[param]._frequency);
	debugC(3, kDebugSound, "musFade");

	return false;
}

bool AdlibMusicDriver::musStartNote(const byte *&srcP, byte param) {
	if (param < 7) {
		byte note = *srcP++;
		++srcP;		// Second byte is fade, which is unused by Adlib
		uint freq = calcFrequency(note);
		debugC(3, kDebugSound, "musStartNote %x -> %x", note, freq);

		setFrequency(param, freq);
		freq |= 0x2000;
		_channels[param]._frequency = freq;
		setFrequency(param, freq);
	} else {
		srcP += 2;
		debugC(3, kDebugSound, "musStartNote skipped");
	}

	return false;
}

bool AdlibMusicDriver::musSetVolume(const byte *&srcP, byte param) {
	debugC(3, kDebugSound, "musSetVolume %d", (int)*srcP);

	if (*srcP++ == 5 && !_field180) {
		_channels[param]._volume = *srcP;
		setOutputLevel(param, *srcP);
	}

	++srcP;
	return false;
}

bool AdlibMusicDriver::musInjectMidi(const byte *&srcP, byte param) {
	// Adlib does not support MIDI. So simply keep skipping over bytes
	// until an 'F7' byte is found that flags the end of the MIDI data
	debugC(3, kDebugSound, "musInjectMidi");
	while (*srcP++ != 0xF7)
		;

	return false;
}

bool AdlibMusicDriver::musPlayInstrument(const byte *&srcP, byte param) {
	byte instrument = *srcP++;
	debugC(3, kDebugSound, "musPlayInstrument %d, %d", param, instrument);

	if (param < 7)
		playInstrument(param, _musInstrumentPtrs[instrument]);

	return false;
}

bool AdlibMusicDriver::fxSetInstrument(const byte *&srcP, byte param) {
	debugC(3, kDebugSound, "fxSetInstrument %d", param);
	_fxInstrumentPtrs[param] = srcP;
	srcP += 11;

	return false;
}

bool AdlibMusicDriver::fxSetVolume(const byte *&srcP, byte param) {
	debugC(3, kDebugSound, "fxSetVolume %d", (int)*srcP);

	if (!_field180 && (!_exclude7 || param != 7)) {
		_channels[param]._volume = *srcP;
		setOutputLevel(param, *srcP);
	}

	++srcP;
	return false;
}

bool AdlibMusicDriver::fxMidiReset(const byte *&srcP, byte param) {
	debugC(3, kDebugSound, "fxMidiReset");
	return false;
}

bool AdlibMusicDriver::fxMidiDword(const byte *&srcP, byte param) {
	debugC(3, kDebugSound, "fxMidiDword");
	return false;
}

bool AdlibMusicDriver::fxSetPanning(const byte *&srcP, byte param) {
	byte note = *srcP++;
	debugC(3, kDebugSound, "fxSetPanning - %x", note);

	if (!_exclude7 || param != 7) {
		uint freq = calcFrequency(note);
		setFrequency(param, freq);
		_channels[param]._frequency = freq;
	}

	return false;
}

bool AdlibMusicDriver::fxChannelOff(const byte *&srcP, byte param) {
	debugC(3, kDebugSound, "fxChannelOff %d", param);
	_channels[param]._frequency &= ~0x2000;
	write(0xB0 + param, _channels[param]._frequency);
	return false;
}

bool AdlibMusicDriver::fxFade(const byte *&srcP, byte param) {
	uint freq = calcFrequency(*srcP++);
	debugC(3, kDebugSound, "fxFade %d %x", param, freq);

	if (!_exclude7 || param != 7) {
		_channels[param]._frequency = freq;
		setFrequency(param, freq);
	}

	return false;
}

bool AdlibMusicDriver::fxStartNote(const byte *&srcP, byte param) {
	if (!_exclude7 || param != 7) {
		byte note = *srcP++;
		uint freq = calcFrequency(note);
		debugC(3, kDebugSound, "fxStartNote %x -> %x", note, freq);

		setFrequency(param, freq);
		freq |= 0x2000;
		_channels[param]._frequency = freq;
		setFrequency(param, freq);
	} else {
		++srcP;
		debugC(3, kDebugSound, "fxStartNote skipped");
	}

	return false;
}

bool AdlibMusicDriver::fxInjectMidi(const byte *&srcP, byte param) {
	// Surpringly, unlike the musInjectMidi, this version doesn't have
	// any logic to skip over following MIDI data. Which must mean the opcode
	// and/or it's data aren't present in the admus driver file
	debugC(3, kDebugSound, "fxInjectMidi");
	return false;
}

bool AdlibMusicDriver::fxPlayInstrument(const byte *&srcP, byte param) {
	byte instrument = *srcP++;
	debugC(3, kDebugSound, "fxPlayInstrument %d, %d", param, instrument);

	if (!_exclude7 || param != 7)
		playInstrument(param, _fxInstrumentPtrs[instrument]);

	return false;
}


const byte AdlibMusicDriver::OPERATOR1_INDEXES[CHANNEL_COUNT] = {
	0, 1, 2, 8, 9, 0xA, 0x10, 0x11, 0x12
};

const byte AdlibMusicDriver::OPERATOR2_INDEXES[CHANNEL_COUNT] = {
	3, 4, 5, 0xB, 0xC, 0xD, 0x13, 0x14, 0x15
};

const uint AdlibMusicDriver::WAVEFORMS[24] = {
	0, 347, 388, 436, 462, 519, 582, 646,
	0, 362, 406, 455, 484, 542, 607, 680,
	0, 327, 367, 412, 436, 489, 549, 618
};

/*------------------------------------------------------------------------*/

Music::Music() : _musicDriver(nullptr), _songData(nullptr),
		_archiveType(ANY_ARCHIVE), _effectsData(nullptr), _musicOn(true) {
	_musicDriver = new AdlibMusicDriver();
}

Music::~Music() {
	stopSong();
	delete _musicDriver;
	delete[] _effectsData;
	delete[] _songData;
}

void Music::loadEffectsData() {
	// Check whether it's the first load, or switching from intro to game data
	if (_effectsData && !(_archiveType == INTRO_ARCHIVE && File::_currentArchive != INTRO_ARCHIVE))
		return;

	// Stop any prior FX
	stopFX();
	delete[] _effectsData;
	_archiveType = File::_currentArchive;

	// Load in an entire driver so we have quick access to the effects data
	// that's hardcoded within it
	File file("blastmus");
	byte *effectsData = new byte[file.size()];
	file.seek(0);
	file.read(effectsData, file.size());
	file.close();
	_effectsData = effectsData;

	// Locate the playFX routine
	const byte *fx = effectsData + READ_LE_UINT16(effectsData + 10) + 12;
	assert(READ_BE_UINT16(fx + 28) == 0x81FB);
	uint numEffects = READ_LE_UINT16(fx + 30);

	assert(READ_BE_UINT16(fx + 36) == 0x8B87);
	const byte *table = effectsData + READ_LE_UINT16(fx + 38);

	// Extract the effects offsets
	_effectsOffsets.resize(numEffects);
	for (uint idx = 0; idx < numEffects; ++idx)
		_effectsOffsets[idx] = READ_LE_UINT16(&table[idx * 2]);
}

void Music::playFX(uint effectId) {
	stopFX();
	loadEffectsData();

	if (effectId < _effectsOffsets.size()) {
		const byte *dataP = &_effectsData[_effectsOffsets[effectId]];
		_musicDriver->playFX(effectId, dataP);
	}
}

void Music::stopFX() {
	_musicDriver->stopFX();
}

int Music::songCommand(uint commandId, byte volume) {
	int result = _musicDriver->songCommand(commandId, volume);
	if (commandId == STOP_SONG) {
		delete[] _songData;
		_songData = nullptr;
	}

	return result;
}

void Music::playSong(Common::SeekableReadStream &stream) {
	stopSong();

	byte *songData = new byte[stream.size()];
	stream.seek(0);
	stream.read(songData, stream.size());
	_songData = songData;

	_musicDriver->playSong(_songData);
}

void Music::playSong(const Common::String &name, int param) {
	File f(name);
	playSong(f);
}

} // End of namespace Xeen