/* 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 "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/files/u6_lzw.h"
#include "ultima/nuvie/sound/adplug/u6m.h"

namespace Ultima {
namespace Nuvie {

Cu6mPlayer::~Cu6mPlayer() {
	if (song_data)
		free(song_data);
}

CPlayer *Cu6mPlayer::factory(Copl *newopl) {
	return new Cu6mPlayer(newopl);
}

bool Cu6mPlayer::load(const Common::Path &filename) {
	uint32 decompressed_filesize;
	U6Lzw lzw;

	song_data = lzw.decompress_file(filename, decompressed_filesize);

	rewind(0);
	return true;
}


bool Cu6mPlayer::update() {
	if (!driver_active) {
		driver_active = true;
		dec_clip(read_delay);
		if (read_delay == 0) {
			command_loop();
		}

		// on all Adlib channels: freq slide/vibrato, mute factor slide
		for (int i = 0; i < 9; i++) {
			if (channel_freq_signed_delta[i] != 0)
				// frequency slide + mute factor slide
			{
				// freq slide
				freq_slide(i);

				// mute factor slide
				if (carrier_mf_signed_delta[i] != 0) {
					mf_slide(i);
				}
			} else
				// vibrato + mute factor slide
			{
				// vibrato
				if ((vb_multiplier[i] != 0) && ((channel_freq[i].hi & 0x20) == 0x20)) {
					vibrato(i);
				}

				// mute factor slide
				if (carrier_mf_signed_delta[i] != 0) {
					mf_slide(i);
				}
			}
		}

		driver_active = false;
	}

	return !songend;
}


void Cu6mPlayer::rewind(int subsong) {
	played_ticks = 0;
	songend = false;

	// set the driver's internal variables
	byte_pair freq_word = {0, 0};

	driver_active = false;
	song_pos = 0;
	loop_position = 0;   // position of the loop point
	read_delay = 0;      // delay (in timer ticks) before further song data is read

	for (int i = 0; i < 9; i++) {
		// frequency
		channel_freq_signed_delta[i] = 0;
		channel_freq[i] = freq_word;  // Adlib freq settings for each channel

		// vibrato ("vb")
		vb_current_value[i] = 0;
		vb_double_amplitude[i] = 0;
		vb_multiplier[i] = 0;
		vb_direction_flag[i] = 0;

		// mute factor ("mf") == ~(volume)
		carrier_mf[i] = 0;
		carrier_mf_signed_delta[i] = 0;
		carrier_mf_mod_delay_backup[i] = 0;
		carrier_mf_mod_delay[i] = 0;
	}

	while (!subsong_stack.empty())      // empty subsong stack
		subsong_stack.pop();

	opl->init();
	out_adlib(1, 32);   // go to OPL2 mode
}


float Cu6mPlayer::getrefresh() {
	return ((float)60);   // the Ultima 6 music driver expects to be called at 60 Hz
}

// ============================================================================================
//
//
//    Functions called by update()
//
//
// ============================================================================================


// This function reads the song data and executes the embedded commands.
void Cu6mPlayer::command_loop() {
	unsigned char command_byte;   // current command byte
	int command_nibble_hi;        // command byte, bits 4-7
	int command_nibble_lo;        // command byte, bite 0-3
	bool repeat_loop = true;      //

	do {
		// extract low and high command nibbles
		command_byte = read_song_byte();   // implicitly increments song_pos
		command_nibble_hi = command_byte >> 4;
		command_nibble_lo = command_byte & 0xf;

		switch (command_nibble_hi) {
		case 0x0:
			command_0(command_nibble_lo);
			break;
		case 0x1:
			command_1(command_nibble_lo);
			break;
		case 0x2:
			command_2(command_nibble_lo);
			break;
		case 0x3:
			command_3(command_nibble_lo);
			break;
		case 0x4:
			command_4(command_nibble_lo);
			break;
		case 0x5:
			command_5(command_nibble_lo);
			break;
		case 0x6:
			command_6(command_nibble_lo);
			break;
		case 0x7:
			command_7(command_nibble_lo);
			break;
		case 0x8:
			switch (command_nibble_lo) {
			case 1:
				command_81();
				break;
			case 2:
				command_82();
				repeat_loop = false;
				break;
			case 3:
				command_83();
				break;
			case 5:
				command_85();
				break;
			case 6:
				command_86();
				break;
			default:
				break; // maybe generate an error?
			}
			break;
		case 0xE:
			command_E();
			break;
		case 0xF:
			command_F();
			break;
		default:
			break; // maybe generate an error?
		}

	} while (repeat_loop);
}


// --------------------------------------------------------
//    The commands supported by the U6 music file format
// --------------------------------------------------------

// ----------------------------------------
// Set octave and frequency, note off
// Format: 0c nn
// c = channel, nn = packed Adlib frequency
// ----------------------------------------
void Cu6mPlayer::command_0(int channel) {
	unsigned char freq_byte;
	byte_pair freq_word;

	freq_byte = read_song_byte();
	freq_word = expand_freq_byte(freq_byte);
	set_adlib_freq(channel, freq_word);
}


// ---------------------------------------------------
// Set octave and frequency, old note off, new note on
// Format: 1c nn
// c = channel, nn = packed Adlib frequency
// ---------------------------------------------------
void Cu6mPlayer::command_1(int channel) {
	unsigned char freq_byte;
	byte_pair freq_word;

	vb_direction_flag[channel] = 0;
	vb_current_value[channel] = 0;

	freq_byte = read_song_byte();
	freq_word = expand_freq_byte(freq_byte);
	set_adlib_freq(channel, freq_word);

	freq_word.hi = freq_word.hi | 0x20; // note on
	set_adlib_freq(channel, freq_word);
}


// ----------------------------------------
// Set octave and frequency, note on
// Format: 2c nn
// c = channel, nn = packed Adlib frequency
// ----------------------------------------
void Cu6mPlayer::command_2(int channel) {
	unsigned char freq_byte;
	byte_pair freq_word;

	freq_byte = read_song_byte();
	freq_word = expand_freq_byte(freq_byte);
	freq_word.hi = freq_word.hi | 0x20; // note on
	set_adlib_freq(channel, freq_word);
}


// --------------------------------------
// Set "carrier mute factor"==not(volume)
// Format: 3c nn
// c = channel, nn = mute factor
// --------------------------------------
void Cu6mPlayer::command_3(int channel) {
	unsigned char mf_byte;

	carrier_mf_signed_delta[channel] = 0;
	mf_byte = read_song_byte();
	set_carrier_mf(channel, mf_byte);
}


// ----------------------------------------
// set "modulator mute factor"==not(volume)
// Format: 4c nn
// c = channel, nn = mute factor
// ----------------------------------------
void Cu6mPlayer::command_4(int channel) {
	unsigned char mf_byte;

	mf_byte = read_song_byte();
	set_modulator_mf(channel, mf_byte);
}


// --------------------------------------------
// Set portamento (pitch slide)
// Format: 5c nn
// c = channel, nn = signed channel pitch delta
// --------------------------------------------
void Cu6mPlayer::command_5(int channel) {
	channel_freq_signed_delta[channel] = read_signed_song_byte();
}


// --------------------------------------------
// Set vibrato parameters
// Format: 6c mn
// c = channel
// m = vibrato double amplitude
// n = vibrato multiplier
// --------------------------------------------
void Cu6mPlayer::command_6(int channel) {
	unsigned char vb_parameters;

	vb_parameters = read_song_byte();
	vb_double_amplitude[channel] = vb_parameters >> 4; // high nibble
	vb_multiplier[channel] = vb_parameters & 0xF; // low nibble
}


// ----------------------------------------
// Assign Adlib instrument to Adlib channel
// Format: 7c nn
// c = channel, nn = instrument number
// ----------------------------------------
void Cu6mPlayer::command_7(int channel) {
	int instrument_offset = instrument_offsets[read_song_byte()];
	out_adlib_opcell(channel, false, 0x20, *(song_data + instrument_offset + 0));
	out_adlib_opcell(channel, false, 0x40, *(song_data + instrument_offset + 1));
	out_adlib_opcell(channel, false, 0x60, *(song_data + instrument_offset + 2));
	out_adlib_opcell(channel, false, 0x80, *(song_data + instrument_offset + 3));
	out_adlib_opcell(channel, false, 0xE0, *(song_data + instrument_offset + 4));
	out_adlib_opcell(channel, true, 0x20, *(song_data + instrument_offset + 5));
	out_adlib_opcell(channel, true, 0x40, *(song_data + instrument_offset + 6));
	out_adlib_opcell(channel, true, 0x60, *(song_data + instrument_offset + 7));
	out_adlib_opcell(channel, true, 0x80, *(song_data + instrument_offset + 8));
	out_adlib_opcell(channel, true, 0xE0, *(song_data + instrument_offset + 9));
	out_adlib(0xC0 + channel, *(song_data + instrument_offset + 10));
}


// -------------------------------------------
// Branch to a new subsong
// Format: 81 nn aa bb
// nn == number of times to repeat the subsong
// aa == subsong offset (low byte)
// bb == subsong offset (high byte)
// -------------------------------------------
void Cu6mPlayer::command_81() {
	subsong_info new_ss_info;

	new_ss_info.subsong_repetitions = read_song_byte();
	new_ss_info.subsong_start = read_song_byte();
	new_ss_info.subsong_start += read_song_byte() << 8;
	new_ss_info.continue_pos = song_pos;

	subsong_stack.push(new_ss_info);
	song_pos = new_ss_info.subsong_start;
}


// ------------------------------------------------------------
// Stop interpreting commands for this timer tick
// Format: 82 nn
// nn == delay (in timer ticks) until further data will be read
// ------------------------------------------------------------
void Cu6mPlayer::command_82() {
	read_delay = read_song_byte();
}


// -----------------------------
// Adlib instrument data follows
// Format: 83 nn <11 bytes>
// nn == instrument number
// -----------------------------
void Cu6mPlayer::command_83() {
	unsigned char instrument_number = read_song_byte();
	instrument_offsets[instrument_number] = song_pos;
	song_pos += 11;
}


// ----------------------------------------------
// Set -1 mute factor slide (upward volume slide)
// Format: 85 cn
// c == channel
// n == slide delay
// ----------------------------------------------
void Cu6mPlayer::command_85() {
	unsigned char data_byte = read_song_byte();
	int channel = data_byte >> 4; // high nibble
	unsigned char slide_delay = data_byte & 0xF; // low nibble
	carrier_mf_signed_delta[channel] = +1;
	carrier_mf_mod_delay[channel] = slide_delay + 1;
	carrier_mf_mod_delay_backup[channel] = slide_delay + 1;
}


// ------------------------------------------------
// Set +1 mute factor slide (downward volume slide)
// Format: 86 cn
// c == channel
// n == slide speed
// ------------------------------------------------
void Cu6mPlayer::command_86() {
	unsigned char data_byte = read_song_byte();
	int channel = data_byte >> 4; // high nibble
	unsigned char slide_delay = data_byte & 0xF; // low nibble
	carrier_mf_signed_delta[channel] = -1;
	carrier_mf_mod_delay[channel] = slide_delay + 1;
	carrier_mf_mod_delay_backup[channel] = slide_delay + 1;
}


// --------------
// Set loop point
// Format: E?
// --------------
void Cu6mPlayer::command_E() {
	loop_position = song_pos;
}


// ---------------------------
// Return from current subsong
// Format: F?
// ---------------------------
void Cu6mPlayer::command_F() {
	if (!subsong_stack.empty()) {
		subsong_info temp = subsong_stack.top();
		subsong_stack.pop();
		temp.subsong_repetitions--;
		if (temp.subsong_repetitions == 0) {
			song_pos = temp.continue_pos;
		} else {
			song_pos = temp.subsong_start;
			subsong_stack.push(temp);
		}
	} else {
		song_pos = loop_position;
		songend = true;
	}
}


// --------------------
// Additional functions
// --------------------

// This function decrements its argument, without allowing it to become negative.
void Cu6mPlayer::dec_clip(int &param) {
	param--;
	if (param < 0) {
		param = 0;
	}
}


// Returns the byte at the current song position.
// Side effect: increments song_pos.
unsigned char Cu6mPlayer::read_song_byte() {
	unsigned char song_byte;
	song_byte = song_data[song_pos];
	song_pos++;
	return song_byte;
}


// Same as read_song_byte(), except that it returns a signed byte
signed char Cu6mPlayer::read_signed_song_byte() {
	unsigned char song_byte;
	int signed_value;
	song_byte = *(song_data + song_pos);
	song_pos++;
	if (song_byte <= 127) {
		signed_value = song_byte;
	} else {
		signed_value = (int)song_byte - 0x100;
	}
	return ((signed char)signed_value);
}


Cu6mPlayer::byte_pair Cu6mPlayer::expand_freq_byte(unsigned char freq_byte) {
	const byte_pair freq_table[24] = {
		{0x00, 0x00}, {0x58, 0x01}, {0x82, 0x01}, {0xB0, 0x01},
		{0xCC, 0x01}, {0x03, 0x02}, {0x41, 0x02}, {0x86, 0x02},
		{0x00, 0x00}, {0x6A, 0x01}, {0x96, 0x01}, {0xC7, 0x01},
		{0xE4, 0x01}, {0x1E, 0x02}, {0x5F, 0x02}, {0xA8, 0x02},
		{0x00, 0x00}, {0x47, 0x01}, {0x6E, 0x01}, {0x9A, 0x01},
		{0xB5, 0x01}, {0xE9, 0x01}, {0x24, 0x02}, {0x66, 0x02}
	};

	int packed_freq;
	int octave;
	byte_pair freq_word;

	packed_freq = freq_byte & 0x1F;
	octave = freq_byte >> 5;

	// range check (not present in the original U6 music driver)
	if (packed_freq >= 24) {
		packed_freq = 0;
	}

	freq_word.hi = freq_table[packed_freq].hi + (octave << 2);
	freq_word.lo = freq_table[packed_freq].lo;

	return freq_word;
}


void Cu6mPlayer::set_adlib_freq(int channel, Cu6mPlayer::byte_pair freq_word) {
	out_adlib(0xA0 + channel, freq_word.lo);
	out_adlib(0xB0 + channel, freq_word.hi);
	// update the Adlib register backups
	channel_freq[channel] = freq_word;
}


// this function sets the Adlib frequency, but does not update the register backups
void Cu6mPlayer::set_adlib_freq_no_update(int channel, Cu6mPlayer::byte_pair freq_word) {
	out_adlib(0xA0 + channel, freq_word.lo);
	out_adlib(0xB0 + channel, freq_word.hi);
}


void Cu6mPlayer::set_carrier_mf(int channel, unsigned char mute_factor) {
	out_adlib_opcell(channel, true, 0x40, mute_factor);
	carrier_mf[channel] = mute_factor;
}


void Cu6mPlayer::set_modulator_mf(int channel, unsigned char mute_factor) {
	out_adlib_opcell(channel, false, 0x40, mute_factor);
}


void Cu6mPlayer::freq_slide(int channel) {
	byte_pair freq = channel_freq[channel];

	long freq_word = freq.lo + (freq.hi << 8) + channel_freq_signed_delta[channel];
	if (freq_word < 0) {
		freq_word += 0x10000;
	}
	if (freq_word > 0xFFFF) {
		freq_word -= 0x10000;
	}

	freq.lo = freq_word & 0xFF;
	freq.hi = (freq_word >> 8) & 0xFF;
	set_adlib_freq(channel, freq);
}


void Cu6mPlayer::vibrato(int channel) {
	byte_pair freq;

	if (vb_current_value[channel] >= vb_double_amplitude[channel]) {
		vb_direction_flag[channel] = 1;
	} else if (vb_current_value[channel] <= 0) {
		vb_direction_flag[channel] = 0;
	}

	if (vb_direction_flag[channel] == 0) {
		vb_current_value[channel]++;
	} else {
		vb_current_value[channel]--;
	}

	long freq_word = channel_freq[channel].lo + (channel_freq[channel].hi << 8);
	freq_word += (vb_current_value[channel] - (vb_double_amplitude[channel] >> 1))
	             * vb_multiplier[channel];
	if (freq_word < 0) {
		freq_word += 0x10000;
	}
	if (freq_word > 0xFFFF) {
		freq_word -= 0x10000;
	}

	freq.lo = freq_word & 0xFF;
	freq.hi = (freq_word >> 8) & 0xFF;
	set_adlib_freq_no_update(channel, freq);
}


void Cu6mPlayer::mf_slide(int channel) {
	carrier_mf_mod_delay[channel]--;
	if (carrier_mf_mod_delay[channel] == 0) {
		carrier_mf_mod_delay[channel] = carrier_mf_mod_delay_backup[channel];
		int current_mf = carrier_mf[channel] + carrier_mf_signed_delta[channel];
		if (current_mf > 0x3F) {
			current_mf = 0x3F;
			carrier_mf_signed_delta[channel] = 0;
		} else if (current_mf < 0) {
			current_mf = 0;
			carrier_mf_signed_delta[channel] = 0;
		}

		set_carrier_mf(channel, (unsigned char)current_mf);
	}
}


void Cu6mPlayer::out_adlib(unsigned char adlib_register, unsigned char adlib_data) {
	opl->write(adlib_register, adlib_data);
}


void Cu6mPlayer::out_adlib_opcell(int channel, bool carrier, unsigned char adlib_register, unsigned char out_byte) {
	const unsigned char adlib_channel_to_carrier_offset[9] =
	{0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15};
	const unsigned char adlib_channel_to_modulator_offset[9] =
	{0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12};

	if (carrier) {
		out_adlib(adlib_register + adlib_channel_to_carrier_offset[channel], out_byte);
	} else {
		out_adlib(adlib_register + adlib_channel_to_modulator_offset[channel], out_byte);
	}
}

} // End of namespace Nuvie
} // End of namespace Ultima
