/* 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.
 *
 * $URL$
 * $Id$
 *
 */

#include "common/file.h"
#include "common/endian.h"

#include "gob/gob.h"
#include "gob/sound/adlib.h"

namespace Gob {

const unsigned char AdLib::_operators[] = {0, 1, 2, 8, 9, 10, 16, 17, 18};
const unsigned char AdLib::_volRegNums[] = {
	3,  4,  5,
	11, 12, 13,
	19, 20, 21
};

AdLib::AdLib(Audio::Mixer &mixer) : _mixer(&mixer) {
	init();
}

AdLib::~AdLib() {
	Common::StackLock slock(_mutex);

	_mixer->stopHandle(_handle);
	OPLDestroy(_opl);
	if (_data && _freeData)
		delete[] _data;
}

void AdLib::init() {
	_index = -1;
	_data = 0;
	_playPos = 0;
	_dataSize = 0;

	_rate = _mixer->getOutputRate();

	_opl = makeAdLibOPL(_rate);

	_first = true;
	_ended = false;
	_playing = false;

	_freeData = false;

	_repCount = -1;
	_samplesTillPoll = 0;

	for (int i = 0; i < 16; i ++)
		_pollNotes[i] = 0;
	setFreqs();

	_mixer->playStream(Audio::Mixer::kMusicSoundType, &_handle,
			this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}

int AdLib::readBuffer(int16 *buffer, const int numSamples) {
	Common::StackLock slock(_mutex);
	int samples;
	int render;

	if (!_playing || (numSamples < 0)) {
		memset(buffer, 0, numSamples * sizeof(int16));
		return numSamples;
	}
	if (_first) {
		memset(buffer, 0, numSamples * sizeof(int16));
		pollMusic();
		return numSamples;
	}

	samples = numSamples;
	while (samples && _playing) {
		if (_samplesTillPoll) {
			render = (samples > _samplesTillPoll) ?  (_samplesTillPoll) : (samples);
			samples -= render;
			_samplesTillPoll -= render;
			YM3812UpdateOne(_opl, buffer, render);
			buffer += render;
		} else {
			pollMusic();
			if (_ended) {
				memset(buffer, 0, samples * sizeof(int16));
				samples = 0;
			}
		}
	}

	if (_ended) {
		_first = true;
		_ended = false;

		rewind();

		_samplesTillPoll = 0;
		if (_repCount == -1) {
			reset();
			setVoices();
		} else if (_repCount > 0) {
			_repCount--;
			reset();
			setVoices();
		}
		else
			_playing = false;
	}
	return numSamples;
}

void AdLib::writeOPL(byte reg, byte val) {
	debugC(6, kDebugSound, "AdLib::writeOPL (%02X, %02X)", reg, val);
	OPLWriteReg(_opl, reg, val);
}

void AdLib::setFreqs() {
	byte lin;
	byte col;
	long val = 0;

	// Run through the 11 channels
	for (lin = 0; lin < 11; lin ++) {
		_notes[lin] = 0;
		_notCol[lin] = 0;
		_notLin[lin] = 0;
		_notOn[lin] = false;
	}

	// Run through the 25 lines
	for (lin = 0; lin < 25; lin ++) {
		// Run through the 12 columns
		for (col = 0; col < 12; col ++) {
			if (!col)
				val = (((0x2710L + lin * 0x18) * 0xCB78 / 0x3D090) << 0xE) *
					9 / 0x1B503;
			_freqs[lin][col] = (short)((val + 4) >> 3);
			val = val * 0x6A / 0x64;
		}
	}
}

void AdLib::reset() {
	_first = true;
	OPLResetChip(_opl);
	_samplesTillPoll = 0;

	setFreqs();
	// Set frequencies and octave to 0; notes off
	for (int i = 0; i < 9; i++) {
		writeOPL(0xA0 | i, 0);
		writeOPL(0xB0 | i, 0);
		writeOPL(0xE0 | _operators[i]     , 0);
		writeOPL(0xE0 |(_operators[i] + 3), 0);
	}

	// Authorize the control of the waveformes
	writeOPL(0x01, 0x20);
}

void AdLib::setKey(byte voice, byte note, bool on, bool spec) {
	short freq = 0;
	short octa = 0;

	// Instruction AX
	if (spec) {
		// 0x7F donne 0x16B;
		//     7F
		// <<   7 =  3F80
		// + E000 = 11F80
		// & FFFF =  1F80
		// *   19 = 31380
		// / 2000 =    18 => Ligne 18h, colonne  0 => freq 16B

		// 0x3A donne 0x2AF;
		//     3A
		// <<   7 =  1D00
		// + E000 =  FD00 negatif
		// *   19 = xB500
		// / 2000 =    -2 => Ligne 17h, colonne -1

		//     2E
		// <<   7 =  1700
		// + E000 =  F700 negatif
		// *   19 = x1F00
		// / 2000 =
		short a;
		short lin;
		short col;

		a = (note << 7) + 0xE000; // Volontairement tronque
		a = (short)((long)a * 25 / 0x2000);
		if (a < 0) {
			col = - ((24 - a) / 25);
			lin = (-a % 25);
			if (lin)
				lin = 25 - lin;
		}
		else {
			col = a / 25;
			lin = a % 25;
		}

		_notCol[voice] = col;
		_notLin[voice] = lin;
		note = _notes[voice];
	}
	// Instructions 0X 9X 8X
	else {
		note -= 12;
		_notOn[voice] = on;
	}

	_notes[voice] = note;
	note += _notCol[voice];
	note = MIN((byte) 0x5F, note);
	octa = note / 12;
	freq = _freqs[_notLin[voice]][note - octa * 12];

	writeOPL(0xA0 + voice,  freq & 0xFF);
	writeOPL(0xB0 + voice, (freq >> 8) | (octa << 2) | 0x20 * on);

	if (!freq)
		warning("AdLib::setKey Voice %d, note %02X unknown", voice, note);
}

void AdLib::setVolume(byte voice, byte volume) {
	debugC(6, kDebugSound, "AdLib::setVolume(%d, %d)", voice, volume);
	//assert(voice >= 0 && voice <= 9);
	volume = 0x3F - ((volume * 0x7E) + 0x7F) / 0xFE;
	writeOPL(0x40 + _volRegNums[voice], volume);
}

void AdLib::pollMusic() {
	if ((_playPos > (_data + _dataSize)) && (_dataSize != 0xFFFFFFFF)) {
		_ended = true;
		return;
	}

	interpret();
}

void AdLib::unload() {
	_playing = false;
	_index = -1;

	if (_data && _freeData)
		delete[] _data;

	_freeData = false;
}

bool AdLib::isPlaying() const {
	return _playing;
}

bool AdLib::getRepeating() const {
	return _repCount != 0;
}

void AdLib::setRepeating(int32 repCount) {
	_repCount = repCount;
}

int AdLib::getIndex() const {
	return _index;
}

void AdLib::startPlay() {
	if (_data) _playing = true;
}

void AdLib::stopPlay() {
	Common::StackLock slock(_mutex);
	_playing = false;
}

ADLPlayer::ADLPlayer(Audio::Mixer &mixer) : AdLib(mixer) {
}

ADLPlayer::~ADLPlayer() {
}

bool ADLPlayer::load(const char *fileName) {
	Common::File song;

	unload();
	song.open(fileName);
	if (!song.isOpen())
		return false;

	_freeData = true;
	_dataSize = song.size();
	_data = new byte[_dataSize];
	song.read(_data, _dataSize);
	song.close();

	reset();
	setVoices();
	_playPos = _data + 3 + (_data[1] + 1) * 0x38;

	return true;
}

bool ADLPlayer::load(byte *data, uint32 size, int index) {
	unload();
	_repCount = 0;

	_dataSize = size;
	_data = data;
	_index = index;

	reset();
	setVoices();
	_playPos = _data + 3 + (_data[1] + 1) * 0x38;

	return true;
}

void ADLPlayer::unload() {
	AdLib::unload();
}

void ADLPlayer::interpret() {
	unsigned char instr;
	byte channel;
	byte note;
	byte volume;
	uint16 tempo;

	// First tempo, we'll ignore it...
	if (_first) {
		tempo = *(_playPos++);
		// Tempo on 2 bytes
		if (tempo & 0x80)
			tempo = ((tempo & 3) << 8) | *(_playPos++);
	}
	_first = false;

	// Instruction
	instr = *(_playPos++);
	channel = instr & 0x0F;

	switch (instr & 0xF0) {
		// Note on + Volume
		case 0x00:
			note = *(_playPos++);
			_pollNotes[channel] = note;
			setVolume(channel, *(_playPos++));
			setKey(channel, note, true, false);
			break;
		// Note on
		case 0x90:
			note = *(_playPos++);
			_pollNotes[channel] = note;
			setKey(channel, note, true, false);
			break;
		// Last note off
		case 0x80:
			note = _pollNotes[channel];
			setKey(channel, note, false, false);
			break;
		// Frequency on/off
		case 0xA0:
			note = *(_playPos++);
			setKey(channel, note, _notOn[channel], true);
			break;
		// Volume
		case 0xB0:
			volume = *(_playPos++);
			setVolume(channel, volume);
			break;
		// Program change
		case 0xC0:
			setVoice(channel, *(_playPos++), false);
			break;
		// Special
		case 0xF0:
			switch (instr & 0x0F) {
			case 0xF: // End instruction
				_ended = true;
				_samplesTillPoll = 0;
				return;
			default:
				warning("ADLPlayer: Unknown special command %X, stopping playback",
						instr & 0x0F);
				_repCount = 0;
				_ended = true;
				break;
			}
			break;
		default:
			warning("ADLPlayer: Unknown command %X, stopping playback",
					instr & 0xF0);
			_repCount = 0;
			_ended = true;
			break;
	}

	// Temporization
	tempo = *(_playPos++);
	// End tempo
	if (tempo == 0xFF) {
		_ended = true;
		return;
	}
	// Tempo on 2 bytes
	if (tempo & 0x80)
		tempo = ((tempo & 3) << 8) | *(_playPos++);
	if (!tempo)
		tempo ++;

	_samplesTillPoll = tempo * (_rate / 1000);
}

void ADLPlayer::reset() {
	AdLib::reset();
}

void ADLPlayer::rewind() {
	_playPos = _data + 3 + (_data[1] + 1) * 0x38;
}

void ADLPlayer::setVoices() {
	// Definitions of the 9 instruments
	for (int i = 0; i < 9; i++)
		setVoice(i, i, true);
}

void ADLPlayer::setVoice(byte voice, byte instr, bool set) {
	uint16 strct[27];
	byte channel;
	byte *dataPtr;

	// i = 0 :  0  1  2  3  4  5  6  7  8  9 10 11 12 26
	// i = 1 : 13 14 15 16 17 18 19 20 21 22 23 24 25 27
	for (int i = 0; i < 2; i++) {
		dataPtr = _data + 3 + instr * 0x38 + i * 0x1A;
		for (int j = 0; j < 27; j++) {
			strct[j] = READ_LE_UINT16(dataPtr);
			dataPtr += 2;
		}
		channel = _operators[voice] + i * 3;
		writeOPL(0xBD, 0x00);
		writeOPL(0x08, 0x00);
		writeOPL(0x40 | channel, ((strct[0] & 3) << 6) | (strct[8] & 0x3F));
		if (!i)
			writeOPL(0xC0 | voice,
					((strct[2] & 7) << 1) | (1 - (strct[12] & 1)));
		writeOPL(0x60 | channel, ((strct[3] & 0xF) << 4) | (strct[6] & 0xF));
		writeOPL(0x80 | channel, ((strct[4] & 0xF) << 4) | (strct[7] & 0xF));
		writeOPL(0x20 | channel, ((strct[9] & 1) << 7) |
			((strct[10] & 1) << 6) | ((strct[5] & 1) << 5) |
			((strct[11] & 1) << 4) |  (strct[1] & 0xF));
		if (!i)
			writeOPL(0xE0 | channel, (strct[26] & 3));
		else
			writeOPL(0xE0 | channel, (strct[14] & 3));
		if (i && set)
			writeOPL(0x40 | channel, 0);
	}
}


MDYPlayer::MDYPlayer(Audio::Mixer &mixer) : AdLib(mixer) {
	init();
}

MDYPlayer::~MDYPlayer() {
}

void MDYPlayer::init() {
	_soundMode = 0;

	_timbres = 0;
	_tbrCount = 0;
	_tbrStart = 0;
	_timbresSize = 0;
}

bool MDYPlayer::loadMDY(Common::SeekableReadStream &stream) {
	unloadMDY();

	_freeData = true;

	byte mdyHeader[70];
	stream.read(mdyHeader, 70);

	_tickBeat = mdyHeader[36];
	_beatMeasure = mdyHeader[37];
	_totalTick = mdyHeader[38] + (mdyHeader[39] << 8) + (mdyHeader[40] << 16) + (mdyHeader[41] << 24);
	_dataSize = mdyHeader[42] + (mdyHeader[43] << 8) + (mdyHeader[44] << 16) + (mdyHeader[45] << 24);
	_nrCommand = mdyHeader[46] + (mdyHeader[47] << 8) + (mdyHeader[48] << 16) + (mdyHeader[49] << 24);
// _soundMode is either 0 (melodic) or 1 (percussive)
	_soundMode = mdyHeader[58];
	assert((_soundMode == 0) || (_soundMode == 1));

	_pitchBendRangeStep = 25*mdyHeader[59];
	_basicTempo = mdyHeader[60] + (mdyHeader[61] << 8);

	if (_pitchBendRangeStep < 25)
		_pitchBendRangeStep = 25;
	else if (_pitchBendRangeStep > 300)
		_pitchBendRangeStep = 300;

	_data = new byte[_dataSize];
	stream.read(_data, _dataSize);

	reset();
	_playPos = _data;

	return true;
}

bool MDYPlayer::loadMDY(const char *fileName) {
	Common::File song;

	song.open(fileName);
	if (!song.isOpen())
		return false;

	bool loaded = loadMDY(song);

	song.close();

	return loaded;
}

bool MDYPlayer::loadTBR(Common::SeekableReadStream &stream) {
	unloadTBR();

	_timbresSize = stream.size();

	_timbres = new byte[_timbresSize];
	stream.read(_timbres, _timbresSize);

	reset();
	setVoices();

	return true;
}

bool MDYPlayer::loadTBR(const char *fileName) {
	Common::File timbres;

	timbres.open(fileName);
	if (!timbres.isOpen())
		return false;

	bool loaded = loadTBR(timbres);

	timbres.close();

	return loaded;
}

void MDYPlayer::unload() {
	unloadTBR();
	unloadMDY();
}

void MDYPlayer::unloadMDY() {
	AdLib::unload();
}

void MDYPlayer::unloadTBR() {
	delete[] _timbres;

	_timbres = 0;
	_timbresSize = 0;
}

void MDYPlayer::interpret() {
	unsigned char instr;
	byte channel;
	byte note;
	byte volume;
	uint8 tempoMult, tempoFrac;
	uint8 ctrlByte1, ctrlByte2;
	uint8 timbre;

// TODO : Verify the loop for percussive mode (11 ?)
	if (_first) {
		for (int i = 0; i < 9; i ++)
			setVolume(i, 0);

//	TODO : Set pitch range

		_tempo = _basicTempo;
		_wait = *(_playPos++);
		_first = false;
	}
	do {
		instr = *_playPos;
		debugC(6, kDebugSound, "MDYPlayer::interpret instr 0x%X", instr);
		switch (instr) {
		case 0xF8:
			_wait = *(_playPos++);
			break;
		case 0xFC:
			_ended = true;
			_samplesTillPoll = 0;
			return;
		case 0xF0:
			_playPos++;
			ctrlByte1 = *(_playPos++);
			ctrlByte2 = *(_playPos++);
			debugC(6, kDebugSound, "MDYPlayer::interpret ctrlBytes 0x%X 0x%X", ctrlByte1, ctrlByte2);
			if (ctrlByte1 != 0x7F || ctrlByte2 != 0) {
				_playPos -= 2;
				while (*(_playPos++) != 0xF7)
					;
			} else {
				tempoMult = *(_playPos++);
				tempoFrac = *(_playPos++);
				_tempo = _basicTempo * tempoMult + (unsigned)(((long)_basicTempo * tempoFrac) >> 7);
				_playPos++;
			}
			_wait = *(_playPos++);
			break;
		default:
			if (instr >= 0x80) {
				_playPos++;
			}
			channel = (int)(instr & 0x0f);

			switch (instr & 0xf0) {
			case 0x90:
				note = *(_playPos++);
				volume = *(_playPos++);
				_pollNotes[channel] = note;
				setVolume(channel, volume);
				setKey(channel, note, true, false);
				break;
			case 0x80:
				_playPos += 2;
				note = _pollNotes[channel];
				setKey(channel, note, false, false);
				break;
			case 0xA0:
				setVolume(channel, *(_playPos++));
				break;
			case 0xC0:
				timbre = *(_playPos++);
				setVoice(channel, timbre, false);
				break;
			case 0xE0:
				warning("MDYPlayer: Pitch bend not yet implemented");

				note = *(_playPos)++;
				note += (unsigned)(*(_playPos++)) << 7;

				setKey(channel, note, _notOn[channel], true);

				break;
			case 0xB0:
				_playPos += 2;
				break;
			case 0xD0:
				_playPos++;
				break;
			default:
				warning("MDYPlayer: Bad MIDI instr byte: 0%X", instr);
				while ((*_playPos) < 0x80)
					_playPos++;
				if (*_playPos != 0xF8)
					_playPos--;
				break;
			} //switch instr & 0xF0
			_wait = *(_playPos++);
			break;
		} //switch instr
	} while (_wait == 0);

	if (_wait == 0xF8) {
		_wait = 0xF0;
		if (*_playPos != 0xF8)
			_wait += *(_playPos++) & 0x0F;
	}
//		_playPos++;
	_samplesTillPoll = _wait * (_rate / 1000);
}

void MDYPlayer::reset() {
	AdLib::reset();

// _soundMode 1 : Percussive mode.
	if (_soundMode == 1) {
		writeOPL(0xA6, 0);
		writeOPL(0xB6, 0);
		writeOPL(0xA7, 0);
		writeOPL(0xB7, 0);
		writeOPL(0xA8, 0);
		writeOPL(0xB8, 0);

// TODO set the correct frequency for the last 4 percussive voices
	}
}

void MDYPlayer::rewind() {
	_playPos = _data;
}

void MDYPlayer::setVoices() {
	byte *timbrePtr;

	timbrePtr = _timbres;
	debugC(6, kDebugSound, "MDYPlayer::setVoices TBR version: %X.%X", timbrePtr[0], timbrePtr[1]);
	timbrePtr += 2;

	_tbrCount = READ_LE_UINT16(timbrePtr);
	debugC(6, kDebugSound, "MDYPlayer::setVoices Timbres counter: %d", _tbrCount);
	timbrePtr += 2;
	_tbrStart = READ_LE_UINT16(timbrePtr);

	timbrePtr += 2;
	for (int i = 0; i < _tbrCount ; i++)
		setVoice(i, i, true);
}

void MDYPlayer::setVoice(byte voice, byte instr, bool set) {
//	uint16 strct[27];
	uint8 strct[27];
	byte channel;
	byte *timbrePtr;
	char timbreName[10];

	timbreName[9] = '\0';
	for (int j = 0; j < 9; j++)
		timbreName[j] = _timbres[6 + j + (instr * 9)];
	debugC(6, kDebugSound, "MDYPlayer::setVoice Loading timbre %s", timbreName);

	// i = 0 :  0  1  2  3  4  5  6  7  8  9 10 11 12 26
	// i = 1 : 13 14 15 16 17 18 19 20 21 22 23 24 25 27
	for (int i = 0; i < 2; i++) {
		timbrePtr = _timbres + _tbrStart + instr * 0x38 + i * 0x1A;
		for (int j = 0; j < 27; j++) {
			if (timbrePtr >= (_timbres + _timbresSize)) {
				warning("MDYPlayer: Instrument %d out of range (%d, %d)", instr,
						(uint32) (timbrePtr - _timbres), _timbresSize);
				strct[j] = 0;
			} else
				//strct[j] = READ_LE_UINT16(timbrePtr);
				strct[j] = timbrePtr[0];
			//timbrePtr += 2;
			timbrePtr++;
		}
		channel = _operators[voice] + i * 3;
		writeOPL(0xBD, 0x00);
		writeOPL(0x08, 0x00);
		writeOPL(0x40 | channel, ((strct[0] & 3) << 6) | (strct[8] & 0x3F));
		if (!i)
			writeOPL(0xC0 | voice,
					((strct[2] & 7) << 1) | (1 - (strct[12] & 1)));
		writeOPL(0x60 | channel, ((strct[3] & 0xF) << 4) | (strct[6] & 0xF));
		writeOPL(0x80 | channel, ((strct[4] & 0xF) << 4) | (strct[7] & 0xF));
		writeOPL(0x20 | channel, ((strct[9] & 1) << 7) |
			((strct[10] & 1) << 6) | ((strct[5] & 1) << 5) |
			((strct[11] & 1) << 4) |  (strct[1] & 0xF));
		if (!i)
			writeOPL(0xE0 | channel, (strct[26] & 3));
		else {
			writeOPL(0xE0 | channel, (strct[14] & 3));
			writeOPL(0x40 | channel, 0);
		}
	}
}

} // End of namespace Gob