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_VOLUME};
use crate::strict;
use crate::utils::hz_to_note;
use crate::utils::FactoredState;
use crate::vgm2mid::CHN_DAC;
use crate::ym3812::toggle_drum;
use anyhow::Result;
use midly::num::u7;

// YM2413 Register Constants
#[allow(dead_code)]
pub(crate) const YM2413_REG_USER: u8 = 0x00;

pub(crate) const YM2413_REG_RHYTHM: u8 = 0x0E;
pub(crate) const YM2413_REG_RHYTHM_MODE: u8 = 0x20;
pub(crate) const YM2413_REG_BD: u8 = 0x10;
pub(crate) const YM2413_REG_SD: u8 = 0x08;
pub(crate) const YM2413_REG_TOM: u8 = 0x04;
pub(crate) const YM2413_REG_TCT: u8 = 0x02;
pub(crate) const YM2413_REG_HH: u8 = 0x01;

#[allow(dead_code)]
pub(crate) const YM2413_REG_TEST: u8 = 0x0F;
pub(crate) const YM2413_REG_FNUM_LSB: u8 = 0x10;
pub(crate) const YM2413_REG_FNUM_LSB_END: u8 = YM2413_REG_FNUM_LSB + 8;

pub(crate) const YM2413_REG_SUS_KEY_BLOCK_FNUM_MSB: u8 = 0x20;
pub(crate) const YM2413_REG_SUS_KEY_BLOCK_FNUM_MSB_END: u8 = YM2413_REG_SUS_KEY_BLOCK_FNUM_MSB + 8;
pub(crate) const YM2413_REG_SUS: u8 = 0x20;
pub(crate) const YM2413_REG_KEY: u8 = 0x10;
pub(crate) const YM2413_REG_BLOCK: u8 = 0x0E;
pub(crate) const YM2413_REG_FNUM_MSB: u8 = 0x01;

pub(crate) const YM2413_REG_INST_VOL: u8 = 0x30;
pub(crate) const YM2413_REG_INST_VOL_5: u8 = YM2413_REG_INST_VOL + 5;
pub(crate) const YM2413_REG_INST_VOL_6: u8 = YM2413_REG_INST_VOL + 6;
pub(crate) const YM2413_REG_INST_VOL_8: u8 = YM2413_REG_INST_VOL + 8;
pub(crate) const YM2413_REG_INST: u8 = 0xF0;
pub(crate) const YM2413_REG_VOL: u8 = 0x0F;

// Tone Data (INST) Constants
pub(crate) const INST_ORIGINAL: u8 = 0x00;
pub(crate) const INST_VIOLIN: u8 = 0x01;
pub(crate) const INST_GUITAR: u8 = 0x02;
pub(crate) const INST_PIANO: u8 = 0x03;
pub(crate) const INST_FLUTE: u8 = 0x04;
pub(crate) const INST_CLARINET: u8 = 0x05;
pub(crate) const INST_OBOE: u8 = 0x06;
pub(crate) const INST_TRUMPET: u8 = 0x07;
pub(crate) const INST_ORGAN: u8 = 0x08;
pub(crate) const INST_HORN: u8 = 0x09;
pub(crate) const INST_SYNTHESIZER: u8 = 0x0A;
pub(crate) const INST_HARPSICHORD: u8 = 0x0B;
pub(crate) const INST_VIBRAPHONE: u8 = 0x0C;
pub(crate) const INST_BASS_SYNTH: u8 = 0x0D;
pub(crate) const INST_BASS_ACOUSTIC: u8 = 0x0E;
pub(crate) const INST_GUITAR_ELECTRIC: u8 = 0x0F;

pub(crate) fn hz_ym2413(fnum: f64, opt_block: Option<u8>, clock: u32) -> f64 {
	let block = opt_block.unwrap_or(0);

	if block == 0 && fnum == 0.0 {
		0.0
	} else {
		fnum * (f64::from(clock) / 72.0) * 2.0_f64.powi(block as i32 - 19)
	}
}

pub(crate) struct YM2413State {
	sus_on: [bool; 8],
	key_on: [bool; 8],
	block: [u8; 8],
	ins_vol: [u8; 8],
	instrument: [u8; 8],
	volume: [u8; 8],
	bd_vol: u7,
	hh_vol: u7,
	sd_vol: u7,
	tom_vol: u7,
	tct_vol: u7,
	percussion_on: [bool; 5],
	factored: FactoredState,
}

impl Default for YM2413State {
	fn default() -> Self {
		YM2413State {
			sus_on: [false; 8],
			key_on: [false; 8],
			block: [0; 8],
			ins_vol: [0; 8],
			instrument: [0xFF; 8],
			volume: [0xFF; 8],
			bd_vol: 0.into(),
			hh_vol: 0.into(),
			sd_vol: 0.into(),
			tom_vol: 0.into(),
			tct_vol: 0.into(),
			percussion_on: [false; 5],
			factored: Default::default(),
		}
	}
}

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

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

	pub(crate) fn command_handle(
		&mut self,
		register: u8,
		data: u8,
		clock: u32,
		midi: &mut MIDIShim,
	) -> Result<()> {
		/*if Variables_Clear_YM2413 = 1 {
			CH = 0
			Erase state.FNum_MSB
			Erase state.SUS_ON: Erase state.KEY_ON:  Erase block: Erase state.FNum_LSB: Erase state.FNum_1: Erase state.FNum_2: Erase state.Hz_1: Erase state.Hz_2: Erase state.Note_1: Erase state.Note_2
			Erase state.InsVol: Erase state.Instrument: Erase state.Volume: state.BD_VOL = 0x7F: state.HH_VOL = 0x7F: state.SD_VOL = 0x7F: state.TOM_VOL = 0x7F: state.TCT_VOL = 0x7F
			Erase state.MIDINote: Erase state.MIDIWheel: Erase state.factored.midi_volume[0]: Erase state.NoteOn_1: Erase state.NoteOn_2: Erase state.Percussion_on
			For CH = 0x0..=0x8
				state.InsVol[CH as usize] = 0x0
				state.Instrument[CH as usize] = 0xFF
				state.Volume[CH as usize] = 0xFF
				state.MIDINote[CH as usize] = 0xFF
				state.MIDIWheel[CH as usize] = 0x8000
				state.factored.midi_volume[CH as usize] = 0xFF
			Next CH
			Variables_Clear_YM2413 = 0
		}*/

		match register {
			//	Case YM2413_REG_USER..=(YM2413_REG_USER + 7)
			YM2413_REG_RHYTHM => {
				self.rhythm(data, midi);
			},
			YM2413_REG_FNUM_LSB..=YM2413_REG_FNUM_LSB_END
			| YM2413_REG_SUS_KEY_BLOCK_FNUM_MSB
				..=YM2413_REG_SUS_KEY_BLOCK_FNUM_MSB_END => {
				self.fnum(register, data, clock, midi)?;
			},
			YM2413_REG_INST_VOL..=YM2413_REG_INST_VOL_5 => {
				self.instrument_volume_1_to_6(register, data, midi)?;
			},
			YM2413_REG_INST_VOL_6..=YM2413_REG_INST_VOL_8 => {
				self.instrument_volume_6_to_8(register, data)?;
			},
			_ => strict!(),
		}
		Ok(())
	}

	fn instrument_volume_6_to_8(&mut self, register: u8, data: u8) -> Result<()> {
		if self.config.ym2413_percussion_disabled {
			return Ok(());
		}

		match register - YM2413_REG_INST_VOL {
			6 => {
				////self.state.BD_VOL = (MIDI_VOLUME_MAX - ((Data & 15) * 8)) * 0.8
				//self.state.BD_VOL = (MIDI_VOLUME_MAX - ((Data & 0xF) * 0x8)) + 1
				//if self.state.BD_VOL = 0x80 { self.state.BD_VOL = 0x7F
				self.state.bd_vol = db_to_midi_vol(ym2413_vol_to_db(data & 0xF));
			},
			7 => {
				//self.state.HH_VOL = MIDI_VOLUME_MAX - (((Data & 0xF0) / 0x10) * 8) + 1
				//if self.state.HH_VOL = 0x80 { self.state.HH_VOL = 0x7F
				self.state.hh_vol =
					db_to_midi_vol(ym2413_vol_to_db((data & 0xF0) / 0x10));
				////self.state.SD_VOL = (MIDI_VOLUME_MAX - ((Data & 15) * 8)) * 0.8
				//self.state.SD_VOL = (MIDI_VOLUME_MAX - ((Data & 0xF) * 0x8)) + 1
				//if self.state.SD_VOL = 0x80 { self.state.SD_VOL = 0x7F
				self.state.sd_vol = db_to_midi_vol(ym2413_vol_to_db(data & 0xF));
			},
			8 => {
				////self.state.TOM_VOL = (MIDI_VOLUME_MAX - (((Data & 240) / 16) * 8)) * 0.6
				//self.state.TOM_VOL = (MIDI_VOLUME_MAX - (((Data & 0xF0) / 0x10) * 8)) + 1
				//if self.state.TOM_VOL = 0x80 { self.state.TOM_VOL = 0x7F
				self.state.tom_vol =
					db_to_midi_vol(ym2413_vol_to_db((data & 0xF0) / 0x10));
				//self.state.TCT_VOL = MIDI_VOLUME_MAX - ((Data & 0xF) * 0x8) + 1
				//if self.state.TCT_VOL = 0x80 { self.state.TCT_VOL = 0x7F
				self.state.tct_vol = db_to_midi_vol(ym2413_vol_to_db(data & 0xF));
			},
			_ => strict!(),
		}
		Ok(())
	}

	fn instrument_volume_1_to_6(
		&mut self,
		register: u8,
		data: u8,
		midi: &mut MIDIShim,
	) -> Result<()> {
		let channel = register - YM2413_REG_INST_VOL;

		let mut temp_byte = self.state.instrument[channel as usize];
		self.state.instrument[channel as usize] = (data & YM2413_REG_INST) / 16;
		if !self.config.ym2413_prog_disabled[channel as usize]
			&& (self.state.ins_vol[channel as usize] == data
				|| temp_byte != self.state.instrument[channel as usize])
		{
			match self.state.instrument[channel as usize] {
				INST_ORIGINAL => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[0].into(),
					);
				},
				INST_VIOLIN => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[1].into(),
					);
				},
				INST_GUITAR => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[2].into(),
					);
				},
				INST_PIANO => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[3].into(),
					);
				},
				INST_FLUTE => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[4].into(),
					);
				},
				INST_CLARINET => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[5].into(),
					);
				},
				INST_OBOE => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[6].into(),
					);
				},
				INST_TRUMPET => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[7].into(),
					);
				},
				INST_ORGAN => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[8].into(),
					);
				},
				INST_HORN => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[9].into(),
					);
				},
				INST_SYNTHESIZER => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[10].into(),
					);
				},
				INST_HARPSICHORD => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[11].into(),
					);
				},
				INST_VIBRAPHONE => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[12].into(),
					);
				},
				INST_BASS_SYNTH => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[13].into(),
					);
				},
				INST_BASS_ACOUSTIC => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[14].into(),
					);
				},
				INST_GUITAR_ELECTRIC => {
					midi.program_change_write(
						channel.into(),
						self.config.ym2413_midi_patch[15].into(),
					);
				},
				_ => strict!(),
			}
		}

		temp_byte = self.state.volume[channel as usize];
		self.state.volume[channel as usize] = data & YM2413_REG_VOL;
		if self.config.ym2413_vol_disabled[channel as usize]
			&& (self.state.ins_vol[channel as usize] == data
				|| temp_byte != self.state.volume[channel as usize])
		{
			//self.state.Volume[CH as usize] = MIDI_VOLUME_MAX - ((Data & YM2413_REG_VOL) * 8) + 1
			//if self.state.Volume[CH as usize] = 0x80 { self.state.Volume[CH as usize] = 0x7F
			self.state.factored.midi_volume[channel as usize] = db_to_midi_vol(
				ym2413_vol_to_db(self.state.volume[channel as usize]),
			);
			midi.controller_write(
				channel.into(),
				MIDI_VOLUME,
				self.state.factored.midi_volume[channel as usize],
			);
		}
		self.state.ins_vol[channel as usize] = data;
		Ok(())
	}

	fn fnum(&mut self, register: u8, data: u8, clock: u32, midi: &mut MIDIShim) -> Result<()> {
		let channel = register & 0xF;
		if self.config.ym2413_ch_disabled[channel as usize] {
			return Ok(());
		}
		if (register & 0xF0) == YM2413_REG_FNUM_LSB {
			self.state.factored.fnum_lsb[channel as usize] = data;
			if !self.config.ym2413_optimized_vgms {
				return Ok(());
			}
		} else if (register & 0xF0) == YM2413_REG_SUS_KEY_BLOCK_FNUM_MSB {
			self.state.sus_on[channel as usize] = data & YM2413_REG_SUS == 0;
			self.state.key_on[channel as usize] = data & YM2413_REG_KEY == 0;
			self.state.block[channel as usize] = (data & YM2413_REG_BLOCK) / 2;
			self.state.factored.fnum_msb[channel as usize] = data & YM2413_REG_FNUM_MSB;
		}
		self.state.factored.fnum_1[channel as usize] =
			self.state.factored.fnum_2[channel as usize];
		self.state.factored.fnum_2[channel as usize] =
			(self.state.factored.fnum_msb[channel as usize] as u32 * 256)
				+ self.state.factored.fnum_lsb[channel as usize] as u32;
		self.state.factored.hz_1[channel as usize] =
			self.state.factored.hz_2[channel as usize];
		self.state.factored.hz_2[channel as usize] = hz_ym2413(
			self.state.factored.fnum_2[channel as usize] as f64,
			Some(self.state.block[channel as usize]),
			clock,
		);
		self.state.factored.note_1[channel as usize] =
			self.state.factored.note_2[channel as usize];
		self.state.factored.note_2[channel as usize] =
			hz_to_note(self.state.factored.hz_2[channel as usize]);
		self.state.factored.note_on_1[channel as usize] =
			self.state.factored.note_on_2[channel as usize];
		if (register & 0xF0) == YM2413_REG_SUS_KEY_BLOCK_FNUM_MSB {
			self.state.factored.note_on_2[channel as usize] =
				self.state.key_on[channel as usize];
		}
		//CH = Register - YM2413_REG_SUS_KEY_BLOCK_self.state.FNum_MSB

		if !self.state.key_on[channel as usize] {
			if self.state.factored.note_on_1[channel as usize]
				&& self.state.factored.note_1[channel as usize] != 0.0
			{
				midi.note_off_write(
					channel.into(),
					self.state.factored.midi_note[channel as usize],
					0x00.into(),
				);
				self.state.factored.midi_note[channel as usize] = 0xFF.into();
			}
		} else if self.state.key_on[channel as usize] {
			if self.state.factored.note_on_2[channel as usize]
				!= self.state.factored.note_on_1[channel as usize]
			{
				midi.do_note_on(
					self.state.factored.note_1[channel as usize],
					self.state.factored.note_2[channel as usize],
					channel.into(),
					&mut self.state.factored.midi_note[channel as usize],
					&mut self.state.factored.midi_wheel[channel as usize],
					Some(255),
					None,
				)?;
			} else {
				//if (Register & 0xF0) = YM2413_REG_SUS_KEY_BLOCK_self.state.FNum_MSB & _
				//self.state.Note_1[CH as usize] != self.state.Note_2[CH as usize] {
				midi.do_note_on(
					self.state.factored.note_1[channel as usize],
					self.state.factored.note_2[channel as usize],
					channel.into(),
					&mut self.state.factored.midi_note[channel as usize],
					&mut self.state.factored.midi_wheel[channel as usize],
					None,
					None,
				)?;
			}
		}
		Ok(())
	}

	fn rhythm(&mut self, mut data: u8, midi: &mut MIDIShim) {
		if self.config.ym2413_percussion_disabled {
			return;
		}

		let channel = CHN_DAC;

		if (data & YM2413_REG_RHYTHM_MODE) == 0 {
			data = 0x0;
		}

		//TODO: Move this into utils or something. Also, fix magic key numbers
		toggle_drum(
			data,
			YM2413_REG_BD,
			&mut self.state.percussion_on[0],
			midi,
			channel,
			0x23.into(),
			self.state.bd_vol,
		);
		toggle_drum(
			data,
			YM2413_REG_SD,
			&mut self.state.percussion_on[1],
			midi,
			channel,
			0x26.into(),
			self.state.sd_vol,
		);
		toggle_drum(
			data,
			YM2413_REG_TOM,
			&mut self.state.percussion_on[2],
			midi,
			channel,
			0x2D.into(),
			self.state.tom_vol,
		);
		toggle_drum(
			data,
			YM2413_REG_TCT,
			&mut self.state.percussion_on[3],
			midi,
			channel,
			0x2E.into(),
			self.state.tct_vol,
		);
		toggle_drum(
			data,
			YM2413_REG_HH,
			&mut self.state.percussion_on[4],
			midi,
			channel,
			0x2A.into(),
			self.state.hh_vol,
		);

		// Note Value 46 (Open HiHat) replaced with 51 (Ride Cymbal 1),
		// because REG_TCT is often used with REG_HH at the same time and
		// prevents the Cymbal from playing

		//	Case YM2413_REG_TEST

		//Case YM2413_REG_self.state.FNum_LSB..=(YM2413_REG_self.state.FNum_LSB + 8)
		//CH = Register - YM2413_REG_self.state.FNum_LSB
		//
		//if self.config.ym2413_ch_disabled[CH as usize] = 1 { return }
		//
		//self.state.FNum_LSB[CH as usize] = Data

		//Case YM2413_REG_SUS_KEY_BLOCK_self.state.FNum_MSB..=(YM2413_REG_SUS_KEY_BLOCK_self.state.FNum_MSB + 8)
	}
}

pub(crate) fn ym2413_vol_to_db(tl: u8) -> f64 {
	-f64::from(tl) * 3.0
}