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

#include "scumm/imuse/drivers/mac_m68k.h"

#include "common/util.h"
#include "common/macresman.h"
#include "common/stream.h"

namespace Scumm {

IMuseDriver_MacM68k::IMuseDriver_MacM68k(Audio::Mixer *mixer)
	: MidiDriver_Emulated(mixer), _mixBuffer(nullptr), _mixBufferLength(0), _volumeTable(nullptr), _lastUsedVoiceChannel(0), _defaultInstrument() {
	memset(_channels, 0, sizeof(_channels));
	memset(_pitchTable, 0, sizeof(_pitchTable));
	memset(_voiceChannels, 0, sizeof(_voiceChannels));
}

IMuseDriver_MacM68k::~IMuseDriver_MacM68k() {
	for (uint i = 0; i < ARRAYSIZE(_channels); ++i)
		delete _channels[i];
}

int IMuseDriver_MacM68k::open() {
	if (_isOpen) {
		return MERR_ALREADY_OPEN;
	}

	const int error = MidiDriver_Emulated::open();
	if (error) {
		return error;
	}

	for (uint i = 0; i < ARRAYSIZE(_channels); ++i) {
		delete _channels[i];
		_channels[i] = new MidiChannel_MacM68k(this, i);
	}

	memset(_voiceChannels, 0, sizeof(_voiceChannels));
	_lastUsedVoiceChannel = 0;

	loadAllInstruments();

	_pitchTable[116] = 1664510;
	_pitchTable[117] = 1763487;
	_pitchTable[118] = 1868350;
	_pitchTable[119] = 1979447;
	_pitchTable[120] = 2097152;
	_pitchTable[121] = 2221855;
	_pitchTable[122] = 2353973;
	_pitchTable[123] = 2493948;
	_pitchTable[124] = 2642246;
	_pitchTable[125] = 2799362;
	_pitchTable[126] = 2965820;
	_pitchTable[127] = 3142177;
	for (int i = 115; i >= 0; --i) {
		_pitchTable[i] = _pitchTable[i + 12] / 2;
	}

	_volumeTable = new byte[8192];
	for (int i = 0; i < 32; ++i) {
		for (int j = 0; j < 256; ++j) {
			_volumeTable[i * 256 + j] = ((-128 + j) * _volumeBaseTable[i]) / 127 - 128;
		}
	}

	_mixBuffer = nullptr;
	_mixBufferLength = 0;

	// We set the output sound type to music here to allow sound volume
	// adjustment. The drawback here is that we can not control the music and
	// sfx separately here. But the AdLib output has the same issue so it
	// should not be that bad.
	_mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);

	return 0;
}

void IMuseDriver_MacM68k::close() {
	if (!_isOpen) {
		return;
	}

	_mixer->stopHandle(_mixerSoundHandle);
	_isOpen = false;
	for (InstrumentMap::iterator i = _instruments.begin(); i != _instruments.end(); ++i) {
		delete[] i->_value.data;
	}
	_instruments.clear();
	delete[] _volumeTable;
	_volumeTable = nullptr;
	delete[] _mixBuffer;
	_mixBuffer = nullptr;
	_mixBufferLength = 0;
}

void IMuseDriver_MacM68k::send(uint32 d) {
	assert(false);
}

MidiChannel *IMuseDriver_MacM68k::allocateChannel() {
	for (uint i = 0; i < ARRAYSIZE(_channels); ++i) {
		if (_channels[i]->allocate()) {
			return _channels[i];
		}
	}

	return nullptr;
}

IMuseDriver_MacM68k::Instrument IMuseDriver_MacM68k::getInstrument(int idx) const {
	InstrumentMap::const_iterator i = _instruments.find(idx);
	if (i != _instruments.end()) {
		return i->_value;
	} else {
		return _defaultInstrument;
	}
}

void IMuseDriver_MacM68k::generateSamples(int16 *buf, int len) {
	int silentChannels = 0;

	if (_mixBufferLength < len) {
		delete[] _mixBuffer;

		_mixBufferLength = len;
		_mixBuffer = new int[_mixBufferLength];
		assert(_mixBuffer);
	}
	memset(_mixBuffer, 0, sizeof(int) * _mixBufferLength);

	for (int i = 0; i < kChannelCount; ++i) {
		OutputChannel &out = _voiceChannels[i].out;
		if (out.isFinished) {
			++silentChannels;
			continue;
		}

		byte *volumeTable = &_volumeTable[(out.volume / 4) * 256];
		int *buffer = _mixBuffer;

		int samplesLeft = len;
		while (samplesLeft) {
			out.subPos += out.pitchModifier;
			while (out.subPos >= 0x10000) {
				out.subPos -= 0x10000;
				out.instrument++;
			}

			if (out.instrument >= out.end) {
				if (!out.start) {
					break;
				}

				out.instrument = out.start;
				out.subPos = 0;
			}

			*buffer++ += volumeTable[*out.instrument];
			--samplesLeft;
		}

		if (samplesLeft) {
			out.isFinished = true;
			while (samplesLeft--) {
				*buffer++ += 0x80;
			}
		}
	}

	const int *buffer = _mixBuffer;
	const int silenceAdd = silentChannels << 7;
	while (len--) {
		*buf++ = (((*buffer++ + silenceAdd) >> 3) << 8) ^ 0x8000;
	}
}

void IMuseDriver_MacM68k::loadAllInstruments() {
	Common::MacResManager resource;
	if (resource.open("iMUSE Setups")) {
		if (!resource.hasResFork()) {
			error("MacM68kDriver::loadAllInstruments: \"iMUSE Setups\" loaded, but no resource fork present");
		}

		for (int i = 0x3E7; i < 0x468; ++i) {
			Common::SeekableReadStream *stream = resource.getResource(MKTAG('s', 'n', 'd', ' '), i);
			if (stream) {
				addInstrument(i, stream);
				delete stream;
			}
		}

		for (int i = 0x7D0; i < 0x8D0; ++i) {
			Common::SeekableReadStream *stream = resource.getResource(MKTAG('s', 'n', 'd', ' '), i);
			if (stream) {
				addInstrument(i, stream);
				delete stream;
			}
		}

		InstrumentMap::iterator inst = _instruments.find(kDefaultInstrument);
		if (inst != _instruments.end()) {
			_defaultInstrument = inst->_value;
		} else {
			error("MacM68kDriver::loadAllInstruments: Could not load default instrument");
		}
	} else {
		error("MacM68kDriver::loadAllInstruments: Could not load \"iMUSE Setups\"");
	}
}

void IMuseDriver_MacM68k::addInstrument(int idx, Common::SeekableReadStream *data) {
	// We parse the "SND" files manually here, since we need special data
	// from their header and need to work on them raw while mixing.
	data->skip(2);
	int count = data->readUint16BE();
	data->skip(2 * (3 * count));
	count = data->readUint16BE();
	data->skip(2 * (4 * count));

	Instrument inst;
	// Skip (optional) pointer to data
	data->skip(4);
	inst.length        = data->readUint32BE();
	inst.sampleRate    = data->readUint32BE();
	inst.loopStart     = data->readUint32BE();
	inst.loopEnd       = data->readUint32BE();
	// Skip encoding
	data->skip(1);
	inst.baseFrequency = data->readByte();

	inst.data = new byte[inst.length];
	assert(inst.data);
	data->read(inst.data, inst.length);
	_instruments[idx] = inst;
}

void IMuseDriver_MacM68k::setPitch(OutputChannel *out, int frequency) {
	out->frequency = frequency;
	out->isFinished = false;

	const int pitchIdx = (frequency >> 7) + 60 - out->baseFrequency;
	assert(pitchIdx >= 0);

	const int low7Bits = frequency & 0x7F;
	if (low7Bits) {
		out->pitchModifier = _pitchTable[pitchIdx] + (((_pitchTable[pitchIdx + 1] - _pitchTable[pitchIdx]) * low7Bits) >> 7);
	} else {
		out->pitchModifier = _pitchTable[pitchIdx];
	}
}

void IMuseDriver_MacM68k::VoiceChannel::off() {
	if (out.start) {
		out.isFinished = true;
	}

	part->removeVoice(this);
	part = nullptr;
}

void IMuseDriver_MacM68k::MidiChannel_MacM68k::release() {
	_allocated = false;
	while (_voice) {
		_voice->off();
	}
}

void IMuseDriver_MacM68k::MidiChannel_MacM68k::send(uint32 b) {
	uint8 type = b & 0xF0;
	uint8 p1 = (b >> 8) & 0xFF;
	uint8 p2 = (b >> 16) & 0xFF;

	switch (type) {
	case 0x80:
		noteOff(p1);
		break;

	case 0x90:
		if (p2) {
			noteOn(p1, p2);
		} else {
			noteOff(p1);
		}
		break;

	case 0xB0:
		controlChange(p1, p2);
		break;

	case 0xE0:
		pitchBend((p1 | (p2 << 7)) - 0x2000);
		break;

	default:
		break;
	}
}

void IMuseDriver_MacM68k::MidiChannel_MacM68k::noteOff(byte note) {
	for (VoiceChannel *i = _voice; i; i = i->next) {
		if (i->note == note) {
			if (_sustain) {
				i->sustainNoteOff = true;
			} else {
				i->off();
			}
		}
	}
}

void IMuseDriver_MacM68k::MidiChannel_MacM68k::noteOn(byte note, byte velocity) {
	// Do not start a not unless there is an instrument set up
	if (!_instrument.data) {
		return;
	}

	// Allocate a voice channel
	VoiceChannel *voice = _owner->allocateVoice(_priority);
	if (!voice) {
		return;
	}
	addVoice(voice);

	voice->note = note;
	// This completly ignores the note's volume, but is in accordance
	// to the original.
	voice->out.volume = _volume;

	// Set up the instrument data
	voice->out.baseFrequency = _instrument.baseFrequency;
	voice->out.soundStart    = _instrument.data;
	voice->out.soundEnd      = _instrument.data + _instrument.length;
	if (_instrument.loopEnd && _instrument.loopEnd - 12 > _instrument.loopStart) {
		voice->out.loopStart = _instrument.data + _instrument.loopStart;
		voice->out.loopEnd   = _instrument.data + _instrument.loopEnd;
	} else {
		voice->out.loopStart = nullptr;
		voice->out.loopEnd   = voice->out.soundEnd;
	}

	voice->out.start = voice->out.loopStart;
	voice->out.end   = voice->out.loopEnd;

	// Set up the pitch
	_owner->setPitch(&voice->out, ((note + _transpose) << 7) + ((_pitchBend * _pitchBendFactor) >> 6) + _detune);

	// Set up the sample position
	voice->out.instrument = voice->out.soundStart;
	voice->out.subPos = 0;
}

void IMuseDriver_MacM68k::MidiChannel_MacM68k::programChange(byte program) {
	_instrument = _owner->getInstrument(program + kProgramChangeBase);
}

void IMuseDriver_MacM68k::MidiChannel_MacM68k::pitchBend(int16 bend) {
	_pitchBend = bend;
	for (VoiceChannel *i = _voice; i; i = i->next) {
		_owner->setPitch(&i->out, ((i->note + _transpose) << 7) + ((_pitchBend * _pitchBendFactor) >> 6) + _detune);
	}
}

void IMuseDriver_MacM68k::MidiChannel_MacM68k::controlChange(byte control, byte value) {
	switch (control) {
	// volume change
	case 7:
		_volume = value;
		for (VoiceChannel *i = _voice; i; i = i->next) {
			i->out.volume = value;
			i->out.isFinished = false;
		}
		break;

	// sustain
	case 64:
		_sustain = value;
		if (!_sustain) {
			for (VoiceChannel *i = _voice; i; i = i->next) {
				if (i->sustainNoteOff) {
					i->off();
				}
			}
		}
		break;

	// all notes off
	case 123:
		for (VoiceChannel *i = _voice; i; i = i->next) {
			i->off();
		}
		break;

	default:
		break;
	}
}

void IMuseDriver_MacM68k::MidiChannel_MacM68k::pitchBendFactor(byte value) {
	_pitchBendFactor = value;
}


void IMuseDriver_MacM68k::MidiChannel_MacM68k::transpose(int8 value) {
	_transpose = value;
	for (VoiceChannel *i = _voice; i; i = i->next) {
		_owner->setPitch(&i->out, ((i->note + _transpose) << 7) + ((_pitchBend * _pitchBendFactor) >> 6) + _detune);
	}
}

void IMuseDriver_MacM68k::MidiChannel_MacM68k::detune(int16 value) {
	_detune = (int8)value;
	for (VoiceChannel *i = _voice; i; i = i->next) {
		_owner->setPitch(&i->out, ((i->note + _transpose) << 7) + ((_pitchBend * _pitchBendFactor) >> 6) + _detune);
	}
}

void IMuseDriver_MacM68k::MidiChannel_MacM68k::priority(byte value) {
	_priority = value;
}

void IMuseDriver_MacM68k::MidiChannel_MacM68k::sysEx_customInstrument(uint32 type, const byte *instr, uint32 dataSize) {
	assert(instr);
	if (type == 'MAC ' && dataSize == sizeof(byte)) 
		_instrument = _owner->getInstrument(*instr + kSysExBase);
	else if (type != 'MAC ')
		warning("MidiChannel_MacM68k: Receiving '%c%c%c%c' instrument data. Probably loading a savegame with that sound setting", (type >> 24) & 0xFF, (type >> 16) & 0xFF, (type >> 8) & 0xFF, type & 0xFF);
}

bool IMuseDriver_MacM68k::MidiChannel_MacM68k::allocate() {
	if (_allocated) {
		return false;
	}

	_allocated = true;
	_voice = nullptr;
	_priority = 0;
	_instrument.reset();
	_pitchBend = 0;
	_pitchBendFactor = 2;
	_volume = 0;
	return true;
}

void IMuseDriver_MacM68k::MidiChannel_MacM68k::addVoice(VoiceChannel *voice) {
	voice->next = _voice;
	voice->prev = nullptr;
	voice->part = this;
	if (_voice) {
		_voice->prev = voice;
	}
	_voice = voice;
}

void IMuseDriver_MacM68k::MidiChannel_MacM68k::removeVoice(VoiceChannel *voice) {
	VoiceChannel *i = _voice;
	while (i && i != voice) {
		i = i->next;
	}

	if (i) {
		if (i->next) {
			i->next->prev = i->prev;
		}

		if (i->prev) {
			i->prev->next = i->next;
		} else {
			_voice = i->next;
		}
	}
}

IMuseDriver_MacM68k::VoiceChannel *IMuseDriver_MacM68k::allocateVoice(int priority) {
	VoiceChannel *channel = nullptr;
	for (int i = 0; i < kChannelCount; ++i) {
		if (++_lastUsedVoiceChannel == kChannelCount) {
			_lastUsedVoiceChannel = 0;
		}

		VoiceChannel *cur = &_voiceChannels[_lastUsedVoiceChannel];
		if (!cur->part) {
			memset(cur, 0, sizeof(*cur));
			return cur;
		} else if (!cur->next) {
			if (cur->part->_priority <= priority) {
				priority = cur->part->_priority;
				channel = cur;
			}
		}
	}

	if (channel) {
		channel->off();
		memset(channel, 0, sizeof(*channel));
	}

	return channel;
}

const int IMuseDriver_MacM68k::_volumeBaseTable[32] = {
	  0,   0,   1,   1,   2,   3,   5,   6,
	  8,  11,  13,  16,  19,  22,  26,  30,
	 34,  38,  43,  48,  53,  58,  64,  70,
	 76,  83,  89,  96, 104, 111, 119, 127
};

} // End of namespace Scumm
