/* 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 "groovie/music.h"
#include "groovie/resource.h"

#include "common/config-manager.h"
#include "sound/audiocd.h"

namespace Groovie {

// MusicPlayer

MusicPlayer::MusicPlayer(GroovieEngine *vm) :
	_vm(vm), _isPlaying(false), _backgroundFileRef(0), _gameVolume(100),
	_prevCDtrack(0), _backgroundDelay(0) {
}

MusicPlayer::~MusicPlayer() {
	AudioCD.stop();
}

void MusicPlayer::playSong(uint32 fileref) {
	Common::StackLock lock(_mutex);

	// Set the volumes
	_fadingEndVolume = 100;
	_gameVolume = 100;

	// Play the referenced file once
	play(fileref, false);
}

void MusicPlayer::setBackgroundSong(uint32 fileref) {
	Common::StackLock lock(_mutex);

	debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Changing the background song: %04X", fileref);
	_backgroundFileRef = fileref;
}

void MusicPlayer::frameTick() {
	if (_backgroundDelay > 0) {
		_backgroundDelay--;
		if (_backgroundDelay == 0)
			playSong(_backgroundFileRef);
	}
}

void MusicPlayer::setBackgroundDelay(uint16 delay) {
	_backgroundDelay = delay;
}

void MusicPlayer::playCD(uint8 track) {
	int startms = 0;

	// Stop the MIDI playback
	unload();

	debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Playing CD track %d", track);

	if (track == 3) {
		// This is the credits song, start at 23:20
		startms = 1400000;
		// TODO: If we want to play it directly from the CD, we should decrement
		// the song number (it's track 2 on the 2nd CD)
	} else if ((track == 98) && (_prevCDtrack == 3)) {
		// Track 98 is used as a hack to stop the credits song
		AudioCD.stop();
		return;
	}

	// Save the playing track in order to be able to stop the credits song
	_prevCDtrack = track;

	// Wait until the CD stops playing the current song
	// It was in the original interpreter, but it introduces a big delay
	// in the middle of the introduction, so it's disabled right now
	/*
	AudioCD.updateCD();
	while (AudioCD.isPlaying()) {
		// Wait a bit and try again
		_vm->_system->delayMillis(100);
		AudioCD.updateCD();
	}
	*/

	// Play the track starting at the requested offset (1000ms = 75 frames)
	AudioCD.play(track - 1, 1, startms * 75 / 1000, 0);
}

void MusicPlayer::startBackground() {
	debugC(3, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: startBackground()");
	if (!_isPlaying && _backgroundFileRef) {
		debugC(3, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Starting the background song (0x%4X)", _backgroundFileRef);
		play(_backgroundFileRef, true);
	}
}

void MusicPlayer::setUserVolume(uint16 volume) {
	Common::StackLock lock(_mutex);

	// Save the new user volume
	_userVolume = volume;
	if (_userVolume > 0x100)
		_userVolume = 0x100;

	// Apply it
	updateVolume();
}

void MusicPlayer::setGameVolume(uint16 volume, uint16 time) {
	Common::StackLock lock(_mutex);

	debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Setting game volume from %d to %d in %dms", _gameVolume, volume, time);

	// Save the start parameters of the fade
	_fadingStartTime = _vm->_system->getMillis();
	_fadingStartVolume = _gameVolume;
	_fadingDuration = time;

	// Save the new game volume
	_fadingEndVolume = volume;
	if (_fadingEndVolume > 100)
		_fadingEndVolume = 100;
}

bool MusicPlayer::play(uint32 fileref, bool loop) {
	// Unload the previous song
	unload();

	// Set the new state
	_isPlaying = true;

	// Load the new file
	return load(fileref, loop);
}

void MusicPlayer::applyFading() {
	debugC(6, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: applyFading() _fadingStartTime = %d, _fadingDuration = %d, _fadingStartVolume = %d, _fadingEndVolume = %d", _fadingStartTime, _fadingDuration, _fadingStartVolume, _fadingEndVolume);
	Common::StackLock lock(_mutex);

	// Calculate the passed time
	uint32 time = _vm->_system->getMillis() - _fadingStartTime;
	debugC(6, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: time = %d, _gameVolume = %d", time, _gameVolume);
	if (time >= _fadingDuration) {
		// Set the end volume
		_gameVolume = _fadingEndVolume;
	} else {
		// Calculate the interpolated volume for the current time
		_gameVolume = (_fadingStartVolume * (_fadingDuration - time) +
			_fadingEndVolume * time) / _fadingDuration;
	}
	if (_gameVolume == _fadingEndVolume) {
		// If we were fading to 0, stop the playback and restore the volume
		if (_fadingEndVolume == 0) {
			debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Faded to zero: end of song. _fadingEndVolume set to 100");
			unload();
		}
	}

	// Apply it
	updateVolume();
}

void MusicPlayer::onTimer(void *refCon) {
	debugC(9, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: onTimer()");
	MusicPlayer *music = (MusicPlayer *)refCon;
	Common::StackLock lock(music->_mutex);

	// Apply the game volume fading
	if (music->_gameVolume != music->_fadingEndVolume) {
		// Apply the next step of the fading
		music->applyFading();
	}

	// Handle internal timed events
	music->onTimerInternal();
}

void MusicPlayer::unload() {
	debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Stopping the playback");

	// Set the new state
	_isPlaying = false;
}


// MusicPlayerMidi

MusicPlayerMidi::MusicPlayerMidi(GroovieEngine *vm) :
	MusicPlayer(vm), _midiParser(NULL), _data(NULL), _driver(NULL) {
	// Initialize the channel volumes
	for (int i = 0; i < 0x10; i++) {
		_chanVolumes[i] = 0x7F;
	}
}

MusicPlayerMidi::~MusicPlayerMidi() {
	// Stop the callback
	if (_driver)
		_driver->setTimerCallback(NULL, NULL);

	Common::StackLock lock(_mutex);

	// Unload the parser
	unload();
	delete _midiParser;

	// Unload the MIDI Driver
	if (_driver)
		_driver->close();
	delete _driver;
}

int MusicPlayerMidi::open() {
	// Don't ever call open without first setting the output driver!
	if (!_driver)
		return 255;

	int ret = _driver->open();
	if (ret)
		return ret;

	return 0;
}

void MusicPlayerMidi::close() {}

void MusicPlayerMidi::send(uint32 b) {
	if ((b & 0xFFF0) == 0x07B0) { // Volume change
		// Save the specific channel volume
		byte chan = b & 0xF;
		_chanVolumes[chan] = (b >> 16) & 0x7F;

		// Send the updated value
		updateChanVolume(chan);

		return;
	}
	if (_driver)
		_driver->send(b);
}

void MusicPlayerMidi::metaEvent(byte type, byte *data, uint16 length) {
	switch (type) {
	case 0x2F:
		// End of Track, play the background song
		endTrack();
		break;
	default:
		if (_driver)
			_driver->metaEvent(type, data, length);
		break;
	}
}

void MusicPlayerMidi::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
	if (_driver)
		_driver->setTimerCallback(timer_param, timer_proc);
}

uint32 MusicPlayerMidi::getBaseTempo() {
	if (_driver)
		return _driver->getBaseTempo();
	else
		return 0;
}

MidiChannel *MusicPlayerMidi::allocateChannel() {
	if (_driver)
		return _driver->allocateChannel();
	else
		return 0;
}

MidiChannel *MusicPlayerMidi::getPercussionChannel() {
	if (_driver)
		return _driver->getPercussionChannel();
	else
		return 0;
}

void MusicPlayerMidi::updateChanVolume(byte channel) {
	// Generate a MIDI Control change message for the volume
	uint32 b = 0x7B0;

	// Specify the channel
	b |= (channel & 0xF);

	// Scale by the user and game volumes
	uint32 val = (_chanVolumes[channel] * _userVolume * _gameVolume) / 0x100 / 100;
	val &= 0x7F;

	// Send it to the driver
	if (_driver)
		_driver->send(b | (val << 16));
}

void MusicPlayerMidi::endTrack() {
	debugC(3, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: endTrack()");
	unload();
}

void MusicPlayerMidi::onTimerInternal() {
	// TODO: We really only need to call this while music is playing.
	if (_midiParser)
		_midiParser->onTimer();
}

void MusicPlayerMidi::updateVolume() {
	// Apply it to all the channels
	for (int i = 0; i < 0x10; i++) {
		updateChanVolume(i);
	}
}

void MusicPlayerMidi::unload() {
	MusicPlayer::unload();

	// Unload the parser data
	if (_midiParser)
		_midiParser->unloadMusic();

	// Unload the data
	delete[] _data;
	_data = NULL;
}

bool MusicPlayerMidi::loadParser(Common::SeekableReadStream *stream, bool loop) {
	if (!_midiParser)
		return false;

	// Read the whole file to memory
	int length = stream->size();
	_data = new byte[length];
	stream->read(_data, length);
	delete stream;

	// Set the looping option
	_midiParser->property(MidiParser::mpAutoLoop, loop);

	// Start parsing the data
	if (!_midiParser->loadMusic(_data, length)) {
		error("Groovie::Music: Couldn't parse the data");
		return false;
	}

	// Activate the timer source
	if (_driver)
		_driver->setTimerCallback(this, &onTimer);

	return true;
}


// MusicPlayerXMI

MusicPlayerXMI::MusicPlayerXMI(GroovieEngine *vm, const Common::String &gtlName) :
	MusicPlayerMidi(vm) {
	// Create the parser
	_midiParser = MidiParser::createParser_XMIDI();

	// Create the driver
	MidiDriverType driver = detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI);
	_driver = createMidi(driver);
	this->open();

	// Set the parser's driver
	_midiParser->setMidiDriver(this);

	// Set the timer rate
	_midiParser->setTimerRate(_driver->getBaseTempo());

	// Initialize the channel banks
	for (int i = 0; i < 0x10; i++) {
		_chanBanks[i] = 0;
	}

	// Load the Global Timbre Library
	if (driver == MD_ADLIB) {
		// MIDI through AdLib
		_musicType = MD_ADLIB;
		loadTimbres(gtlName + ".ad");

		// Setup the percussion channel
		for (unsigned int i = 0; i < _timbres.size(); i++) {
			if (_timbres[i].bank == 0x7F)
				setTimbreAD(9, _timbres[i]);
		}
	} else if ((driver == MD_MT32) || ConfMan.getBool("native_mt32")) {
		// MT-32
		_musicType = MD_MT32;
		loadTimbres(gtlName + ".mt");
	} else {
		// GM
		_musicType = 0;
	}
}

MusicPlayerXMI::~MusicPlayerXMI() {
	//~MusicPlayer();

	// Unload the timbres
	clearTimbres();
}

void MusicPlayerXMI::send(uint32 b) {
	if ((b & 0xFFF0) == 0x72B0) { // XMIDI Patch Bank Select 114
		// From AIL2's documentation: XMIDI Patch Bank Select controller (114)
		// selects a bank to be used when searching the next patches
		byte chan = b & 0xF;
		byte bank = (b >> 16) & 0xFF;

		debugC(5, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Selecting bank %X for channel %X", bank, chan);
		_chanBanks[chan] = bank;
		return;
	} else if ((b & 0xF0) == 0xC0) { // Program change
		// We intercept the program change when using AdLib or MT32 drivers,
		// since we have custom timbres for them.  The command is sent
		// unchanged to GM drivers.
		if (_musicType != 0) {
			byte chan = b & 0xF;
			byte patch = (b >> 8) & 0xFF;

			debugC(5, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Setting custom patch %X from bank %X to channel %X", patch, _chanBanks[chan], chan);

			// Try to find the requested patch from the previously
			// specified bank
			int numTimbres = _timbres.size();
			for (int i = 0; i < numTimbres; i++) {
				if ((_timbres[i].bank == _chanBanks[chan]) &&
					(_timbres[i].patch == patch)) {
					if (_musicType == MD_ADLIB) {
						setTimbreAD(chan, _timbres[i]);
					} else if (_musicType == MD_MT32) {
						setTimbreMT(chan, _timbres[i]);
					}
					return;
				}
			}

			// If we got here we couldn't find the patch, and the
			// received message will be sent unchanged.
		}
	}
	MusicPlayerMidi::send(b);
}

bool MusicPlayerXMI::load(uint32 fileref, bool loop) {
	debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Starting the playback of song: %04X", fileref);

	// Open the song resource
	Common::SeekableReadStream *file = _vm->_resMan->open(fileref);
	if (!file) {
		error("Groovie::Music: Couldn't find resource 0x%04X", fileref);
		return false;
	}

	return loadParser(file, loop);
}

void MusicPlayerXMI::loadTimbres(const Common::String &filename) {
	// Load the Global Timbre Library format as documented in AIL2
	debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Loading the GTL file %s", filename.c_str());

	// Does it exist?
	if (!Common::File::exists(filename)) {
		error("Groovie::Music: %s not found", filename.c_str());
		return;
	}

	// Open the GTL
	Common::File *gtl = new Common::File();
	if (!gtl->open(filename.c_str())) {
		delete gtl;
		error("Groovie::Music: Couldn't open %s", filename.c_str());
		return;
	}

	// Clear the old timbres before loading the new ones
	clearTimbres();

	// Get the list of timbres
	while (true) {
		Timbre t;
		t.patch = gtl->readByte();
		t.bank = gtl->readByte();
		if ((t.patch == 0xFF) && (t.bank == 0xFF)) {
			// End of list
			break;
		}
		// We temporarily use the size field to store the offset
		t.size = gtl->readUint32LE();

		// Add it to the list
		_timbres.push_back(t);
	}

	// Read the timbres
	for (unsigned int i = 0; i < _timbres.size(); i++) {
		// Seek to the start of the timbre
		gtl->seek(_timbres[i].size);

		// Read the size
		_timbres[i].size = gtl->readUint16LE() - 2;

		// Allocate memory for the timbre data
		_timbres[i].data = new byte[_timbres[i].size];

		// Read the timbre data
		gtl->read(_timbres[i].data, _timbres[i].size);
		debugC(5, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Loaded patch %x in bank %x with size %d",
			_timbres[i].patch, _timbres[i].bank, _timbres[i].size);
	}

	// Close the file
	delete gtl;
}

void MusicPlayerXMI::clearTimbres() {
	// Delete the allocated data
	int num = _timbres.size();
	for (int i = 0; i < num; i++) {
		delete[] _timbres[i].data;
	}

	// Erase the array entries
	_timbres.clear();
}

void MusicPlayerXMI::setTimbreAD(byte channel, const Timbre &timbre) {
	// Verify the timbre size
	if (timbre.size != 12) {
		error("Groovie::Music: Invalid size for an AdLib timbre: %d", timbre.size);
	}

	// Prepare the AdLib Instrument array from the GTL entry
	//
	// struct AdlibInstrument used by our AdLib MIDI synth is 30 bytes.
	// Since we pass data + 2 for non percussion instruments we need to
	// have a buffer of size 32, so there are no invalid memory reads,
	// when setting up an AdLib instrument.
	byte data[32];
	memset(data, 0, sizeof(data));

	data[2] = timbre.data[1];        // mod_characteristic
	data[3] = timbre.data[2] ^ 0x3F; // mod_scalingOutputLevel
	data[4] = ~timbre.data[3];       // mod_attackDecay
	data[5] = ~timbre.data[4];       // mod_sustainRelease
	data[6] = timbre.data[5];        // mod_waveformSelect
	data[7] = timbre.data[7];        // car_characteristic
	data[8] = timbre.data[8] ^ 0x3F; // car_scalingOutputLevel
	data[9] = ~timbre.data[9];       // car_attackDecay
	data[10] = ~timbre.data[10];     // car_sustainRelease
	data[11] = timbre.data[11];      // car_waveformSelect
	data[12] = timbre.data[6];       // feedback

	// Send the instrument to the driver
	if (timbre.bank == 0x7F) {
		// This is a Percussion instrument, this will always be set on the same note
		data[0] = timbre.patch;

		// From AIL2's documentation: If the instrument is to be played in MIDI
		// channel 10, num specifies its desired absolute MIDI note number.
		data[1] = timbre.data[0];

		_driver->getPercussionChannel()->sysEx_customInstrument('ADLP', data);
	} else {
		// Some tweaks for non-percussion instruments
		byte mult1 = timbre.data[1] & 0xF;
		if (mult1 < 4)
			mult1 = 1 << mult1;
		data[2] = (timbre.data[1] & 0xF0) + (mult1 & 0xF);
		byte mult2 = timbre.data[7] & 0xF;
		if (mult2 < 4)
			mult2 = 1 << mult2;
		data[7] = (timbre.data[7] & 0xF0) + (mult2 & 0xF);
		// TODO: Fix CHARACTERISTIC: 0xF0: pitch_vib, amp_vib, sustain_sound, env_scaling  0xF: freq_mult
		// TODO: Fix KSL_TL: 0xC: key_scale_lvl  0x3F: out_lvl

		// From AIL2's documentation: num specifies the number of semitones
		// by which to transpose notes played with the instrument.
		if (timbre.data[0] != 0)
			warning("Groovie::Music: AdLib instrument's transposing not supported");

		_driver->sysEx_customInstrument(channel, 'ADL ', data + 2);
	}
}

void MusicPlayerXMI::setTimbreMT(byte channel, const Timbre &timbre) {
	// Verify the timbre size
	if (timbre.size != 0xF6) {
		error("Groovie::Music: Invalid size for an MT-32 timbre: %d", timbre.size);
	}

	// Show the timbre name as extra debug information
	Common::String name((char*)timbre.data, 10);
	debugC(5, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Using MT32 timbre: %s", name.c_str());

	// TODO: Support MT-32 custom instruments
	warning("Groovie::Music: Setting MT32 custom instruments isn't supported yet");
}


// MusicPlayerMac

MusicPlayerMac::MusicPlayerMac(GroovieEngine *vm) :
	MusicPlayerMidi(vm) {
}

bool MusicPlayerMac::load(uint32 fileref, bool loop) {
	debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Starting the playback of song: %04X", fileref);
	debug("Groovie::Music: Starting the playback of song: %04X %d", fileref, fileref);

	// Open the song resource
	/*
	Common::SeekableReadStream *file = _vm->_resMan->open(fileref);
	if (!file) {
		error("Groovie::Music: Couldn't find resource 0x%04X", fileref);
		return false;
	}
	*/

	//return loadParser(file, loop);
	return false;
}

} // End of Groovie namespace