/*
 *  OriginFXAdLibDriver.cpp
 *  Nuvie
 *
 *  Created by Eric Fry on Sun May 26 2013.
 *  Copyright (c) 2013. All rights reserved.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

#include "nuvieDefs.h"
#include "Configuration.h"
#include "U6misc.h"
#include "U6Lib_n.h"
#include "opl.h"
#include "OriginFXAdLibDriver.h"
#include <string.h>
#include <stdlib.h>

const uint8 adlib_BD_cmd_tbl[] ={ 0, 1, 0, 1, 0, 1, 16, 8, 4, 2, 1 };


OriginFXAdLibDriver::OriginFXAdLibDriver(Configuration *cfg, Copl *newopl)
{

	const uint8 byte_73_init[] = {1, 2, 3, 4, 5, 6, 7, 8, 0xB, 0xFF, 0xFF, 0, 0xC};

	config = cfg;
	opl = newopl;
	adlib_tim_data = NULL;
	adlib_num_active_channels = 9;
	memset(midi_chan_tim_ptr, 0, sizeof(midi_chan_tim_ptr));
	memset(midi_chan_pitch, 0, sizeof(midi_chan_pitch));
	memset(midi_chan_tim_off_10, 0, sizeof(midi_chan_tim_off_10));
	memset(midi_chan_tim_off_11, 0, sizeof(midi_chan_tim_off_11));

	adlib_bd_status = 0;
	memcpy(byte_73, byte_73_init, sizeof(byte_73_init));

	for(int i=0;i<29;i++)
	{
		midi_chan_volume[i] = 0x100;
	}

	memset(adlib_ins, 0, sizeof(adlib_ins));

	for(int i=0;i<11;i++)
	{
		adlib_ins[i].note = -1;
		adlib_ins[i].channel = -1;
		adlib_ins[i].byte_68 = 1;
	}

	load_tim_file();

    init();
}

OriginFXAdLibDriver::~OriginFXAdLibDriver()
{
	if(adlib_tim_data)
		delete [] adlib_tim_data;
}

void OriginFXAdLibDriver::init()
{
    opl->init();

    for (int i=0; i<256; i++)
    {
        midi_write_adlib(i,0);
    }

    midi_write_adlib(0x01, 0x20);
    midi_write_adlib(0xBD,0);
    midi_write_adlib(0x8,0);
}

sint16 OriginFXAdLibDriver::read_sint16(unsigned char *buf)
{
	return (buf[1] << 8) | buf[0];
}

void OriginFXAdLibDriver::load_tim_file()
{
	U6Lib_n f;
	std::string filename;

	nuvie_game_t game_type = get_game_type(config);
	if(game_type == NUVIE_GAME_SE)
	{
		config_get_path(config,"savage.tim",filename);
	}
	else // game_type == NUVIE_GAME_MD
	{
		config_get_path(config,"md.tim",filename);
	}

	f.open(filename, 4, game_type);
	unsigned char *buf = f.get_item(1);
	adlib_tim_data = new unsigned char [f.get_item_size(1)-1];
	num_tim_records = buf[0];
	memcpy(adlib_tim_data, &buf[1], f.get_item_size(1)-1);
	free(buf);

	for(int i=0;i<32;i++)
	{
		midi_chan_tim_ptr[i] = adlib_tim_data;
	}

	program_change(0x9 ,0x80);
	program_change(0xa ,0x72);
	program_change(0xb ,0x83);
	program_change(0xc ,0x71);
	program_change(0xd ,0x86);
	program_change(0xe ,0x87);
	program_change(0xf ,0x85);
	program_change(0x10 ,0x84);
	program_change(0x11 ,0x81);
	program_change(0x12 ,0x88);
	program_change(0x13 ,0x8D);
	program_change(0x14 ,0x8F);
	program_change(0x15 ,0x90);
	program_change(0x16 ,0x91);
	program_change(0x17 ,0x93);
	program_change(0x18 ,0x8C);
	program_change(0x19 ,0x8B);
}

unsigned char *OriginFXAdLibDriver::get_tim_data(uint8 program_number)
{
	for(int i=0;i<num_tim_records;i++)
	{
		if(adlib_tim_data[i*48+0x2f] == program_number)
		{
			return &adlib_tim_data[i*48];
		}
	}

	return adlib_tim_data;
}

void OriginFXAdLibDriver::midi_write_adlib(unsigned int r, unsigned char v)
{
  opl->write(r,v);
}

void OriginFXAdLibDriver::program_change(sint8 channel, uint8 program_number)
{
	unsigned char *tim_data = get_tim_data(program_number);
	int i,j;

	printf("Program change channel: %d program: %d\n", channel, program_number);
	for(i=0;i<11;i++)
	{
		if(adlib_ins[i].channel == channel)
		{
			play_note(channel, adlib_ins[i].note, 0); //note off.
			adlib_ins[i].channel = -1;
			adlib_ins[i].tim_data = NULL;

		}
	}

	midi_chan_tim_ptr[channel] = tim_data;
	midi_chan_tim_off_10[channel] = tim_data[0x10];
	midi_chan_tim_off_11[channel] = tim_data[0x11];

	if(tim_data[0xb] != 0 && adlib_num_active_channels == 9)
	{
		midi_write_adlib(0xa6, 0);
		midi_write_adlib(0xb6, 0);
		midi_write_adlib(0xa7, 0);
		midi_write_adlib(0xb7, 0xa);
		midi_write_adlib(0xa8, 0x54);
		midi_write_adlib(0xb8, 0x9);

		adlib_num_active_channels = 6;
		for(i=6;i<9;i++)
		{
			for(j=0;j<13;j++)
			{
				if(byte_73[j] == i)
				{
					byte_73[j] = byte_73[i];
					byte_73[i] = -1;
					break;
				}
			}
		}
		adlib_bd_status = 0x20;
		midi_write_adlib(0xbd, adlib_bd_status);
	}
}

void OriginFXAdLibDriver::pitch_bend(uint8 channel, uint8 pitch_lsb, uint8 pitch_msb)
{
	unsigned char *cur_tim_ptr = midi_chan_tim_ptr[channel];

	midi_chan_pitch[channel] = ((sint16)((pitch_msb << 7) + pitch_lsb - 8192) * cur_tim_ptr[0xe]) / 256;
	printf("pitch_bend: c=%d, pitch=%d %d,%d,%d\n", channel, midi_chan_pitch[channel], pitch_msb, pitch_lsb, cur_tim_ptr[0xe]);

	for(int i=0;i<adlib_num_active_channels;i++)
	{
		if(adlib_ins[i].byte_68 > 1 && adlib_ins[i].channel == channel)
		{
			sint16 var_4 = 0;

			if(adlib_ins[i].tim_data != NULL)
			{
				var_4 = read_sint16(&adlib_ins[i].tim_data[0x24]);
			}

			uint16 var_2 = sub_60D(adlib_ins[i].word_3c + midi_chan_pitch[channel] + adlib_ins[i].word_cb + adlib_ins[i].word_121 + var_4);
			var_2 += 0x2000;
			midi_write_adlib(0xa0 + i, var_2 & 0xff);
			midi_write_adlib(0xb0 + i, var_2 >> 8);
		}

	}
}

void OriginFXAdLibDriver::control_mode_change(uint8 channel, uint8 function, uint8 value)
{
	uint8 c = channel;
	printf("control_mode_change: c=%d, func=%2x, value=%d\n",channel, function, value);
	if(c == 9)
	{
		c++;
		do
		{
			control_mode_change(c, function, value);
			c++;
		} while(c<=25);
		c = 9;
	}

	if(function == 1)
	{
		 midi_chan_tim_off_11[channel] = ((((sint16)midi_chan_tim_ptr[channel][0xf]) * value) / 128) + (sint16)midi_chan_tim_ptr[channel][0x11];
	}
	else if(function == 7)
	{
		midi_chan_volume[c] = value + 128;
	}
	else if(function == 0x7b)
	{
		bool var_6 = false;
		for(int i=0;i<0xb;i++)
		{
			if(adlib_ins[i].byte_68 > 1)
			{
				if(adlib_ins[i].channel == channel)
				{
					play_note(channel, adlib_ins[i].note, 0); //note off
				}
				else if(i >= adlib_num_active_channels)
				{
					var_6 = true;
				}
			}
		}

		if(var_6 && adlib_num_active_channels < 9)
		{
			midi_write_adlib(0xbd, 0);
			adlib_num_active_channels = 9;
			byte_73[6] = 7;
			byte_73[7] = 8;
			byte_73[8] = byte_73[0xb];
			byte_73[0xb] = 6;
		}

	}
	else if(function == 0x79)
	{
		control_mode_change(channel, 1, 0);
		control_mode_change(channel, 7, 0x7f);
		pitch_bend(channel, 0, 0x40);
	}
}
void OriginFXAdLibDriver::play_note(uint8 channel, sint8 note, uint8 velocity)
{
	unsigned char *cur_tim_ptr = midi_chan_tim_ptr[channel];
	for(;cur_tim_ptr!=NULL;cur_tim_ptr+=48)
	{
		sint8 voice = sub_4BF(channel, note, velocity, cur_tim_ptr);
		sint16 var_4 = voice;
		if(voice > 8)
		{
			var_4 = 0x11 - voice;
		}

		if(voice >= 0)
		{
			sint16 var_a = read_sint16(&cur_tim_ptr[0x24]);

			if(velocity != 0)
			{
				adlib_ins[voice].word_121 = 0;
				adlib_ins[voice].byte_137 = 0;
				adlib_ins[voice].word_cb = read_sint16(&cur_tim_ptr[0x12]);
			}

			sint8 cl = cur_tim_ptr[0x27];
			if(cl < 0)
			{
				adlib_ins[voice].word_3c = (-((sint16)(note - 60)*256) / (1<<-(cl+1))) + 0x3c00;
			}
			else
			{
				adlib_ins[voice].word_3c = (((sint16)(note - 60)*256) / (1<<cl)) + 0x3c00;
			}

			uint16 var_2 = sub_60D(adlib_ins[voice].word_3c + midi_chan_pitch[channel] + adlib_ins[voice].word_cb + adlib_ins[voice].word_121 + var_a);
			if(velocity == 0)
			{
				if(voice < adlib_num_active_channels || voice <= 6)
				{
					midi_write_adlib(0xa0 + var_4, var_2 & 0xff);
					midi_write_adlib(0xb0 + var_4, var_2 >> 8);
				}
				else
				{
					adlib_bd_status &= ~adlib_BD_cmd_tbl[voice];
				}
			}
			else
			{
				uint16 var_6 = cur_tim_ptr[6];
				if(cur_tim_ptr[0xc] != 0 || midi_chan_volume[channel] < 0x100)
				{
					sint16 di = 0x3f - ((midi_chan_volume[channel] * (0x3f - ( ((sint16)(63 - velocity) / (sint16)(1<<(7 - cur_tim_ptr[0xc]))) + (var_6 & 0x3f)))) >> 8); //fixme this was 0x14 in dosbox var_6 = 8

					di = 63 - velocity;
					di = di / (1<<(7 - cur_tim_ptr[0xc]));
					di += var_6 & 0x3f;

					sint16 ax = (0x3f - di) * midi_chan_volume[channel];
					ax = ax / 256;
					di = 0x3f - ax;

					if(di > 0x3f)
					{
						di = 0x3f;
					}
					if(di < 0)
					{
						di = 0;
					}

					midi_write_adlib(0x40 + adlib_voice_op1(voice), (var_6 & 0xc0) + di);
				}

				var_6 = cur_tim_ptr[1];
				if(cur_tim_ptr[0xd] != 0)
				{
					sint16 di = (0x3f - velocity) / (sint16)(1<<(7 - cur_tim_ptr[0xd])) + (var_6 & 0x3f);
					if(di > 0x3f)
					{
						di = 0x3f;
					}
					if(di < 0)
					{
						di = 0;
					}

					midi_write_adlib(0x40 + adlib_voice_op(voice), (var_6 & 0xc0) + di);
				}

				if(cur_tim_ptr[0xb] == 0 || voice == 6)
				{
					if(cur_tim_ptr[0xb] == 0)
					{
						var_2 += 0x2000;
					}
					midi_write_adlib(0xa0 + var_4, var_2 & 0xff);
					midi_write_adlib(0xb0 + var_4, var_2 >> 8);
				}

				if(cur_tim_ptr[0xb] != 0)
				{
					adlib_bd_status |= adlib_BD_cmd_tbl[voice];
				}
			}

			if(cur_tim_ptr[0xb] != 0)
			{
				midi_write_adlib(0xbd, adlib_bd_status);
			}
		}

		if(cur_tim_ptr[0x26]==0)
			break;
	}
}

uint16 OriginFXAdLibDriver::sub_60D(sint16 val)
{
	const uint16 word_20f[] = {0x1E5, 0x202, 0x220, 0x241, 0x263, 0x287, 0x2AE, 0x2D7, 0x302, 0x330, 0x360, 0x393, 0x3CA};

	sint16 var_2 = val / 256;

	sint16 si = ((var_2 + 6) / 0xc) - 2;
	if(si > 7)
	{
		si = 7;
	}
	if(si < 0)
	{
		si = 0;
	}

	uint16 di = word_20f[(var_2 + 6) % 0xc];
	if((val & 0xff) != 0)
	{
		di += ((word_20f[((var_2 - 18) % 0xc) + 1] - di) * (val & 0xff)) / 256;
	}

	return (si << 10) + di;
}

uint16 OriginFXAdLibDriver::sub_4BF(uint8 channel, uint8 note, uint8 velocity, unsigned char *cur_tim_ptr)
{
	sint16 si = -1;

	if(adlib_num_active_channels >= 9 || cur_tim_ptr[0xb] == 0)
	{
		if(velocity == 0)
		{
			for(si=0;si<adlib_num_active_channels;si++)
			{
				if(adlib_ins[si].byte_68 > 1 && adlib_ins[si].note == note && adlib_ins[si].channel == channel && adlib_ins[si].tim_data == cur_tim_ptr)
				{
					adlib_ins[si].byte_68 = 0;
					sub_45E(si);
					sub_48E(si, 0xb);
					break;
				}
			}
			if(si == adlib_num_active_channels)
			{
				si = -1;
			}
		}
		else
		{
			if(byte_73[0xb] == 0xb)
			{
				if(midi_chan_tim_ptr[channel] == cur_tim_ptr)
				{
					si = byte_73[0xc];
					byte_73[0xc] = byte_73[si];
					sub_48E(si, 0xc);
					midi_write_adlib(0xa0+si, 0);
					midi_write_adlib(0xb0+si, 0);
				}
			}
			else
			{
				si = byte_73[0xb];
				byte_73[0xb] = byte_73[si];
				sub_48E(si, 0xc);
			}

			if(si >= 0)
			{
				adlib_ins[si].byte_68 = 2;
				adlib_ins[si].note = note;
			}
		}
	}
	else
	{
		si = cur_tim_ptr[0xb];
		adlib_bd_status &= ~adlib_BD_cmd_tbl[cur_tim_ptr[0xb]];
		midi_write_adlib(0xbd, adlib_bd_status);
	}

	if(si >= 0)
	{
		if(adlib_ins[si].channel != channel || adlib_ins[si].tim_data != cur_tim_ptr) //changing instruments
		{
			write_adlib_instrument(si, cur_tim_ptr);
			adlib_ins[si].channel = channel;
			adlib_ins[si].tim_data = cur_tim_ptr;
		}
	}

	return si;
}

void OriginFXAdLibDriver::sub_45E(sint16 voice)
{
	for(int i=0;i<0xd;i++)
	{
		if(byte_73[i] == voice)
		{
			byte_73[i] = byte_73[voice];
			byte_73[voice] = voice;
		}
	}
}

void OriginFXAdLibDriver::sub_48E(sint16 voice, uint8 val)
{
	for(int i=0;i<0xd;i++)
	{
		if(byte_73[i] == val)
		{
			byte_73[i] = voice;
			byte_73[voice] = val;
			break;
		}
	}
}

uint8 OriginFXAdLibDriver::adlib_voice_op(sint8 voice)
{
	const uint8 opp_tbl[] = {0, 1, 2, 8, 9, 0xA, 0x10, 0x11, 0x12, 0, 1, 2, 8, 9, 0xA, 0x10, 0x14, 0x12, 0x15, 0x11};
	return opp_tbl[adlib_num_active_channels < 9 ? voice + 9 : voice];
}

uint8 OriginFXAdLibDriver::adlib_voice_op1(sint8 voice)
{
	const uint8 opp1_tbl[] = {3, 4, 5, 0xB, 0xC, 0xD, 0x13, 0x14, 0x15, 3, 4, 5, 0xB, 0xC, 0xD, 0x13, 0x14, 0x12, 0x15, 0x11};
	return opp1_tbl[adlib_num_active_channels < 9 ? voice + 9 : voice];
}

void OriginFXAdLibDriver::write_adlib_instrument(sint8 voice, unsigned char *tim_data)
{
	uint8 opadd = adlib_voice_op(voice);
	uint8 opadd1 = adlib_voice_op1(voice);
	unsigned char *cur_tim_ptr = tim_data;

	midi_write_adlib(0x20+opadd,*cur_tim_ptr++);
	midi_write_adlib(0x40+opadd,*cur_tim_ptr++);
	midi_write_adlib(0x60+opadd,*cur_tim_ptr++);
	midi_write_adlib(0x80+opadd,*cur_tim_ptr++);
	midi_write_adlib(0xe0+opadd,*cur_tim_ptr++);

	if(adlib_num_active_channels == 9 || tim_data[0xb] < 7)
	{
		midi_write_adlib(0x20+opadd1,*cur_tim_ptr++);
		midi_write_adlib(0x40+opadd1,*cur_tim_ptr++);
		midi_write_adlib(0x60+opadd1,*cur_tim_ptr++);
		midi_write_adlib(0x80+opadd1,*cur_tim_ptr++);
		midi_write_adlib(0xe0+opadd1,*cur_tim_ptr++);
		midi_write_adlib(0xc0+voice,*cur_tim_ptr++);
	}

}

void OriginFXAdLibDriver::interrupt_vector()
{
	const uint8 byte_229[] = {24, 0, 18, 20, 22, 0, 0, 0};

	for(int i=0;i<adlib_num_active_channels;i++)
	{
		unsigned char *cur_tim_data = NULL;
		bool update_adlib = false;
		sint8 channel = adlib_ins[i].channel;
		if(channel < 0 || channel >= 32)
		{
		  continue;
		}
		uint8 var_8 = byte_229[adlib_ins[i].byte_68];
		sint16 var_10 = 0;
		if(adlib_ins[i].tim_data == NULL)
		{
			cur_tim_data = adlib_tim_data;
		}
		else
		{
			cur_tim_data = adlib_ins[i].tim_data;
			var_10 = read_sint16(&cur_tim_data[0x24]);
		}

		if(var_8 != 0)
		{
			sint16 var_a = read_sint16(&cur_tim_data[var_8*2 - 16]);
			sint16 var_c = read_sint16(&cur_tim_data[(var_8+1)*2 - 16]);

			sint16 tmp = (var_c > adlib_ins[i].word_cb) ? var_c - adlib_ins[i].word_cb : adlib_ins[i].word_cb - var_c;
			if(tmp >= var_a)
			{
				if(adlib_ins[i].word_cb >= var_c)
				{
					adlib_ins[i].word_cb -= var_a;
				}
				else
				{
					adlib_ins[i].word_cb += var_a;
				}
			}
			else
			{
				adlib_ins[i].word_cb = var_c;
				adlib_ins[i].byte_68++;
			}

			update_adlib = true;
		}

		if(midi_chan_tim_off_10[channel] != 0)
		{
			adlib_ins[i].byte_137 += midi_chan_tim_off_10[channel];
			sint8 var_11 = adlib_ins[i].byte_137;
			if(var_11 > 63 || var_11 < -64)
			{
				var_11 = -128 - var_11;
			}

			adlib_ins[i].word_121 = (midi_chan_tim_off_11[channel] * var_11) / 16;
			update_adlib = true;
		}

		if(update_adlib || var_10 != 0)
		{
			uint16 adlib_cmd_data = sub_60D(adlib_ins[i].word_3c + midi_chan_pitch[channel] + adlib_ins[i].word_cb + adlib_ins[i].word_121 + var_10);
			if(adlib_ins[i].byte_68 > 1)
			{
				adlib_cmd_data += 0x2000;
			}
			midi_write_adlib(0xa0 + i, adlib_cmd_data & 0xff);
			midi_write_adlib(0xb0 + i, adlib_cmd_data >> 8);
		}
	}
}
