A rust port of vgm2mid by Paul Jensen and Valley Bell
use crate::config::Config;
use crate::midi_shim::{
	db_to_midi_vol, MIDIShim, MIDI_MODULATOR_WHEEL, MIDI_PAN, MIDI_PAN_CENTER, MIDI_PAN_LEFT,
	MIDI_PAN_RIGHT, MIDI_VOLUME,
};
use crate::strict;
use crate::utils::shift;
use crate::utils::FactoredState;
use crate::ym3812::ym3812_vol_to_db;
use anyhow::{bail, Result};
use midly::num::u7;

// YM2151 Register Constants
#[allow(dead_code)]
const YM2151_TEST: u8 = 0x01;
const YM2151_KEY_ON: u8 = 0x08;
const YM2151_NOISE: u8 = 0x0F;
const YM2151_CLK_A_MSB: u8 = 0x10;
const YM2151_CLK_A_LSB: u8 = 0x11;
const YM2151_CLK_B: u8 = 0x12;
const YM2151_CSM_IRQ_RESET_EN_TIMER: u8 = 0x14;
const YM2151_LFO_FREQ: u8 = 0x18;
const YM2151_PMD_AMD: u8 = 0x19;
const YM2151_CT1_CT2_WAVEFORM: u8 = 0x1B;
const YM2151_PAN_FB_CONNECTION: u8 = 0x20;
const YM2151_PAN_FB_CONNECTION_END: u8 = YM2151_PAN_FB_CONNECTION + 0x07;
const YM2151_BLK_FNUM: u8 = 0x28;
const YM2151_BLK_FNUM_END: u8 = YM2151_BLK_FNUM + 0x07;
const YM2151_KF: u8 = 0x30;
const YM2151_KF_END: u8 = YM2151_KF + 0x07;
const YM2151_PMS_AMS: u8 = 0x38;
const YM2151_PMS_AMS_END: u8 = YM2151_PMS_AMS + 0x07;
const YM2151_DT1_MUL: u8 = 0x40;
const YM2151_DT1_MUL_END: u8 = YM2151_DT1_MUL + 0x1F;
const YM2151_TL: u8 = 0x60;
const YM2151_TL_END: u8 = YM2151_TL + 0x1F;
#[allow(dead_code)]
const YM2151_KS_AR: u8 = 0x80;
#[allow(dead_code)]
const YM2151_LFO_AM_EN_D1R: u8 = 0xA0;
#[allow(dead_code)]
const YM2151_DT2_D2R: u8 = 0xC0;
#[allow(dead_code)]
const YM2151_D1L_RR: u8 = 0xE0;

fn ym2151_fnum_to_midi(mut fnum: u8, mut block: u8) -> Result<u7> {
	let mut note_val: u8;

	if fnum > 0x0F {
		strict!("Invalid fnum {}", fnum);
	}

	block = block.clamp(0, 0x07);

	fnum &= 0x0F;
	if (fnum & 0x03) != 0x03 {
		note_val = (fnum & 0xC) >> 2;
		note_val = note_val * 3 + (fnum & 0x3);
		note_val += 61;
	} else {
		note_val = 0xFF
	}

	if note_val == 0xFF {
		Ok(0xFF.into())
	} else {
		Ok((note_val + (block - 0x4) * 12).into())
		//if YM2151FNumToMidi = 0x0 { Stop
	}
}

#[derive(Default)]
pub(crate) struct YM2151State {
	slot: [u8; 8],
	//	DT(5, 3) As Byte, MULTI(5, 3) As Byte
	tl: [[u8; 4]; 8],
	//KF As Byte
	//	KS(5, 3) As Byte, AR(5, 3) As Byte
	//	DR: [u8; 5, 3],
	//	SR: [u8; 5, 3],
	//	SL(5, 3) As Byte, RR(5, 3) As Byte
	block: [u8; 8],
	feedback: [u8; 8],
	connection: [u8; 8],
	factored: FactoredState,
}

pub(crate) struct YM2151<'config> {
	state: YM2151State,
	config: &'config Config,
}

impl<'config> YM2151<'config> {
	pub(crate) fn new<'c: 'config>(config: &'c Config, opt_state: Option<YM2151State>) -> Self {
		YM2151 {
			state: opt_state.unwrap_or_default(),
			config,
		}
	}

	//FIXME: This subroutine used a lot of static state. It needs to be re-written to something more sane.
	pub(crate) fn command_handle(
		&mut self,
		register: u8,
		data: u8,
		midi: &mut MIDIShim,
	) -> Result<()> {

		match register {
			YM2151_KEY_ON => {
				process_key_on(data, self.config, &mut self.state, midi)?;
			},
			YM2151_NOISE => (), // NOISE: NE, NFRQ
			//nen = if(Data & 0x80, 1, 0)
			//nfrq = Data & 0x31
			YM2151_CLK_A_MSB => (),
			YM2151_CLK_A_LSB => (),
			YM2151_CLK_B => (),
			YM2151_CSM_IRQ_RESET_EN_TIMER => (),
			YM2151_LFO_FREQ => (),
			YM2151_PMD_AMD => (),
			//depth = Data & 0x7F
			//ispm = if(Data & 0x80, 1, 0)
			YM2151_CT1_CT2_WAVEFORM => (),
			//w = Data & 0x3	// W: 0 - ramp, 1 - sq, 2 - tri, 3 - noise
			YM2151_PAN_FB_CONNECTION..=YM2151_PAN_FB_CONNECTION_END => {
				process_pan_feedback(
					register,
					self.config,
					data,
					&mut self.state,
					midi,
				)?;
			},
			YM2151_BLK_FNUM..=YM2151_BLK_FNUM_END => {
				process_fnum(register, self.config, &mut self.state, data, midi)?;
			},
			YM2151_KF..=YM2151_KF_END => {
				process_kf(register, self.config, &mut self.state, data, midi)?;
			},
			YM2151_PMS_AMS..=YM2151_PMS_AMS_END => {
				process_modulation(register, data, midi);
			},
			YM2151_DT1_MUL..=YM2151_DT1_MUL_END => {
				//let channel = (register & 0x3F) / 0x8;
				//DT1 = Fix(Data / 0x10) & 0x7
				//MUl = Data & 0xF
			},
			YM2151_TL..=YM2151_TL_END => {
				process_total_level(
					register,
					&mut self.state,
					data,
					self.config,
					midi,
				);
			},
			/*YM2151_KS_AR..=YM2151_KS_AR + 0x1F
				channel = Fix((register & 0x3F) / 0x8)
				//KS = Fix(Data / 0x40) & 0x3
				//AR = Data & 0x1F
			YM2151_LFO_AM_EN_D1R..=YM2151_LFO_AM_EN_D1R + 0x1F
				channel = Fix((register & 0x3F) / 0x8)
				//AMS_EN = if(Data & 0x80, 1, 0)
				//D1R = Data & 0x1F
			YM2151_DT2_D2R..=YM2151_DT2_D2R + 0x1F
				channel = Fix((register & 0x3F) / 0x8)
				//DT2 = Fix(Data / 0x40) & 0x3
				//D2R = Data & 0x1F
			YM2151_D1L_RR..=YM2151_D1L_RR + 0x1F
				channel = Fix((register & 0x1F) / 0x4)
				//D1L = Fix(Data / 0x10) & 0xF
				//RR = Data & 0xF
				//Abort("YM2151 ADSR - SL " & D1L & " RR " & RR);*/
			_ => strict!(),
		}
		Ok(())
	}
}

fn process_total_level(
	register: u8,
	state: &mut YM2151State,
	data: u8,
	config: &Config,
	midi: &mut MIDIShim,
) {
	let channel = register & 0x7;
	let op = (register & 0x1F) / 0x8;
	state.tl[channel as usize][op as usize] = data & 0x7F;
	if op < 3 {
		return;
	}
	if config.ym2151_vol_disabled[channel as usize] {
		return;
	}
	let temp_vol = db_to_midi_vol(ym3812_vol_to_db(state.tl[channel as usize][3]) / 4.0);

	//match state.connection[channel as usize]
	//0, 1, 2, 3
	//4
	//	TempLng = (CLng(state.tl[channel as usize][1]) + state.tl[channel as usize][3]) / 2
	//5, 6
	//	TempLng = (CLng(state.tl[channel as usize][1]) + state.tl[channel as usize][2] + state.tl[channel as usize][3]) / 3
	//7
	//	TempLng = (CLng(state.tl[channel as usize][0]) + state.tl[channel as usize][1] + state.tl[channel as usize][2] + state.tl[channel as usize][3]) / 4
	//}

	if temp_vol != state.factored.midi_volume[channel as usize] {
		state.factored.midi_volume[channel as usize] = temp_vol;
		midi.controller_write(
			channel.into(),
			MIDI_VOLUME,
			state.factored.midi_volume[channel as usize],
		);
	}
}

fn process_modulation(register: u8, data: u8, midi: &mut MIDIShim) {
	let channel = register & 0x7;
	//PMS = Fix(Data / 0x10) & 0x7
	//AMS = Data & 0x3
	let temp_double = ((data & 0x70) as f32) / (0x70 as f32);
	let temp = (temp_double * temp_double * (0x7F as f32)) as u8;
	midi.controller_write(channel.into(), MIDI_MODULATOR_WHEEL, temp.into());
}

fn process_kf(
	register: u8,
	config: &Config,
	state: &mut YM2151State,
	data: u8,
	midi: &mut MIDIShim,
) -> Result<()> {
	let channel = register & 0x7;
	if config.ym2151_ch_disabled[channel as usize] {
		return Ok(());
	}
	state.factored.fnum_lsb[channel as usize] = (data >> 2) & 0x3F;
	state.factored.note_1[channel as usize] = state.factored.note_2[channel as usize];
	state.factored.note_2[channel as usize] =
		ym2151_fnum_to_midi(
			state.factored.fnum_2[channel as usize] as u8,
			state.block[channel as usize],
		)?
		.as_int() as f64 + (state.factored.fnum_lsb[channel as usize] as f64 / 0x40 as f64);

	// Key Fraction goes from 0x00 to 0xFC
	// 0x00 is no change, 0x100 would be a full semitone

	//TempLng = MIDI_PITCHWHEEL_CENTER + state.factored.fnum_lsb[channel as usize] * 0x40	 //(0x1000 / 0x40)
	//if TempLng != state.factored.midi_wheel[channel as usize] {
	//	state.factored.midi_wheel[channel as usize] = TempLng
	//	midi.event_write(Midi { channel: channel, message: PitchBend { bend: state.factored.midi_wheel[channel as usize] }});
	//}
	if state.factored.note_on_2[channel as usize] {
		midi.do_note_on(
			state.factored.note_1[channel as usize],
			state.factored.note_2[channel as usize],
			channel.into(),
			&mut state.factored.midi_note[channel as usize],
			&mut state.factored.midi_wheel[channel as usize],
			None,
			None,
		)?;
	}
	Ok(())
}

fn process_fnum(
	register: u8,
	config: &Config,
	state: &mut YM2151State,
	data: u8,
	midi: &mut MIDIShim,
) -> Result<()> {
	let channel = register & 0x7;
	if config.ym2151_ch_disabled[channel as usize] {
		return Ok(());
	}
	state.block[channel as usize] = (data / 0x10) & 0x7;
	state.factored.fnum_msb[channel as usize] = data & 0xF;

	state.factored.fnum_1[channel as usize] = shift(
		&mut state.factored.fnum_2[channel as usize],
		state.factored.fnum_msb[channel as usize] as u32,
	);

	state.factored.note_1[channel as usize] = state.factored.note_2[channel as usize];
	state.factored.note_2[channel as usize] =
		((ym2151_fnum_to_midi(
			state.factored.fnum_2[channel as usize] as u8,
			state.block[channel as usize],
		)? + state.factored.fnum_lsb[channel as usize].into())
		.as_int() as f64) / (0x40 as f64);

	//FIXME: fnum_2 really shouldn't be a long
	//if state.factored.note_on_2[channel as usize] = 1 | NOTE_ON_MODE & 0x2 != 0 {
	//	if state.factored.note_2[channel as usize] != state.factored.note_1[channel as usize] | (NOTE_ON_MODE & 0x2) = 0x2 {
	//		midi.event_write(Midi { channel: channel, message: NoteOff { key: state.factored.note_1[channel as usize], vel: 0x0 }});
	//		midi.event_write(Midi { channel: channel, message: NoteOn { key: state.factored.note_2[channel as usize], vel: 0x7F }});
	//	}
	//}
	if state.factored.note_on_2[channel as usize] {
		midi.do_note_on(
			state.factored.note_1[channel as usize],
			state.factored.note_2[channel as usize],
			channel.into(),
			&mut state.factored.midi_note[channel as usize],
			&mut state.factored.midi_wheel[channel as usize],
			None,
			None,
		)?;
	}
	Ok(())
}

fn process_pan_feedback(
	register: u8,
	config: &Config,
	data: u8,
	state: &mut YM2151State,
	midi: &mut MIDIShim,
) -> Result<()> {
	let channel = register & 0x7;
	if config.ym2151_ch_disabled[channel as usize] {
		return Ok(());
	}
	if !config.ym2151_pan_disabled[channel as usize] {
		state.factored.midi_pan[channel as usize] = match (data / 0x40) & 0x3 {
			0x1 => MIDI_PAN_LEFT,
			0x2 => MIDI_PAN_RIGHT,
			0x3 | 0x0 => MIDI_PAN_CENTER,
			_ => bail!("Invalid pan data"),
		};
		midi.controller_write(
			channel.into(),
			MIDI_PAN,
			state.factored.midi_pan[channel as usize],
		);
	}
	state.feedback[channel as usize] = (data / 0x8) & 0x7;
	state.connection[channel as usize] = data & 0x7;

	if !config.ym2151_prog_disabled[channel as usize] {
		state.factored.midi_instrument[channel as usize] = (data & 0x3F).into();
		midi.program_change_write(
			channel.into(),
			state.factored.midi_instrument[channel as usize],
		);
	}
	Ok(())
}

fn process_key_on(
	data: u8,
	config: &Config,
	state: &mut YM2151State,
	midi: &mut MIDIShim,
) -> Result<()> {
	let channel = data & 0x7;
	if config.ym2151_ch_disabled[channel as usize] {
		return Ok(());
	}
	state.slot[channel as usize] = (data / 0x8) & 0xF;
	state.factored.note_on_1[channel as usize] = state.factored.note_on_2[channel as usize];
	state.factored.note_on_2[channel as usize] = state.slot[channel as usize] > 0;

	if
	/*NOTE_ON_MODE & 0x1*/
	true {
		//FIXME: wtf?
		// This is a const. wtf?
		if (state.factored.note_on_2[channel as usize]
			!= state.factored.note_on_1[channel as usize])
			&& (state.factored.note_2[channel as usize] != 0.0)
		{
			if !state.factored.note_on_2[channel as usize] {
				//midi.event_write(Midi { channel: channel, message: NoteOff { key: state.factored.note_2[channel as usize], vel: 0x0 }});
				midi.do_note_on(
					state.factored.note_1[channel as usize],
					0xFF as f64,
					channel.into(),
					&mut state.factored.midi_note[channel as usize],
					&mut state.factored.midi_wheel[channel as usize],
					Some(255),
					None,
				)?;
			} else {
				//midi.event_write(Midi { channel: channel, message: NoteOn { key: state.factored.note_2[channel as usize], vel: 0x7F }});
				midi.do_note_on(
					state.factored.note_1[channel as usize],
					state.factored.note_2[channel as usize],
					channel.into(),
					&mut state.factored.midi_note[channel as usize],
					&mut state.factored.midi_wheel[channel as usize],
					Some(255),
					None,
				)?;
			}
		}
	}
	Ok(())
}