A rust port of vgm2mid by Paul Jensen and Valley Bell
use anyhow::Result;
use crate::midi_shim::{MIDIShim, MIDI_VOLUME};
use crate::utils::{hz_to_note, FactoredState};
use crate::config::Config;
use crate::strict;
use midly::num::u4;

const AY_AFINE: u8 = 0x00;
const AY_ACOARSE: u8 = 0x01;
const AY_BFINE: u8 = 0x02;
const AY_BCOARSE: u8 = 0x03;
const AY_CFINE: u8 = 0x04;
const AY_CCOARSE: u8 = 0x05;
const AY_NOISEPER: u8 = 0x06;
const AY_ENABLE: u8 = 0x07;
const AY_AVOL: u8 = 0x08;
const AY_BVOL: u8 = 0x09;
const AY_CVOL: u8 = 0x0A;
const AY_EFINE: u8 = 0x0B;
const AY_ECOARSE: u8 = 0x0C;
const AY_ESHAPE: u8 = 0x0D;

const AY_PORTA: u8 = 0x0E;
const AY_PORTB: u8 = 0x0F;

pub(crate) const MIDI_CHANNEL_PSG_BASE: u4 = u4::new(0x0A);

//Public AY_NoteDelay(0x0..=0x7) As Long	// used for Note On/Off-Detection
//Public AY_LastVol(0x0..=0x7) As Byte

fn hz_ay(fnum: f64, clock: u32) -> f64 {
	if fnum == 0.0 {
		0.0
	} else {
		f64::from(clock / 16) / fnum
	}
}

pub(crate) struct AY8910State {
	attenuation_2: [u8; 7],
	factored: FactoredState,
}

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

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

	//234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
	//0000000111s11111112222222222333333333344444444445555555555666666666666777777778888888888
	pub(crate) fn command_handle(
		&mut self,
		chip_id: u8,
		reg: u8,
		data: u8,
		clock: u32,
		midi: &mut MIDIShim,
	) -> Result<()> {
		/*if Variables_Clear_PSG == 1 {
			Erase FNum_1: Erase FNum_2: Erase Hz_1: Erase Hz_2: Erase Note_1: Erase Note_2
			Erase Attenuation_1: Erase Attenuation_2
			Erase MIDINote: Erase MIDIWheel: Erase NoteOn_1: Erase NoteOn_2: MIDIVolume = 0
			for CH in 0x0..=0x7 {
				state.attenuation_2[channel as usize] = 0xFF
				SN76489_NOTE_DELAY[channel as usize] = 0
				//PSG_LastVol[channel as usize] = 0x0
			}
			Variables_Clear_PSG = 0
		}*/

		//if CH < 0x3 {
		//	if SN76489_CH_DISABLED[channel as usize] = 1 { return }
		//} else { //if CH = 0x3 {
		//	if SN76489_NOISE_DISABLED = 1 { return }
		//}

		match reg {
			AY_AFINE | AY_ACOARSE | AY_BFINE | AY_BCOARSE | AY_CFINE | AY_CCOARSE => {
				process_tuning(reg, chip_id, &mut self.state, data, clock, midi)?;
			},
			AY_AVOL | AY_BVOL | AY_CVOL => {
				process_vol(reg, chip_id, data, &mut self.state, midi);
			},
			AY_ENABLE => {
				process_enable(data, chip_id, &mut self.state, midi)?;
			},
			AY_NOISEPER => (),
			// Noise Frequency
			AY_EFINE | AY_ECOARSE => (),
			AY_ESHAPE => (),
			AY_PORTA | AY_PORTB => (),
			// ignore
			_ => strict!("Invalid Register"),
		}
		Ok(())
	}
}

impl Default for AY8910State {
	fn default() -> Self {
		AY8910State {
			attenuation_2: [0xFF; 7],
			factored: Default::default(),
		}
	}
}

fn process_enable(data: u8, chip_id: u8, state: &mut AY8910State, midi: &mut MIDIShim) -> Result<()> {
	let mut temp_byte = !data;
	for byte_channel in 0x0..=0x5 {
		let channel = u4::from(byte_channel);
		let channel_ptr = (chip_id * 0x6 + channel.as_int()) as usize;
		state.factored.note_on_1[channel_ptr] =
			state.factored.note_on_2[channel_ptr];
		state.factored.note_on_2[channel_ptr] = (temp_byte & 0x1) != 0;
		temp_byte /= 0x2;

		if state.factored.note_on_1[channel_ptr]
			!= state.factored.note_on_2[channel_ptr]
		{
			let midi_channel = if chip_id != 0 {
				u4::new(0x00)
			} else {
				MIDI_CHANNEL_PSG_BASE
			} + channel;
			if state.factored.note_on_2[channel_ptr] {
				midi.do_note_on(
					state.factored.note_2[channel_ptr],
					state.factored.note_2[channel_ptr],
					midi_channel,
					&mut state.factored.midi_note[channel_ptr],
					&mut state.factored.midi_wheel[channel_ptr],
					Some(255),
					None,
				)?;
			} else if state.factored.midi_note[channel_ptr] != 0xFF {
				midi.note_off_write(
					midi_channel,
					state.factored.midi_note[channel_ptr],
					0x00.into(),
				);
				state.factored.midi_note[channel_ptr] = 0xFF.into();
			}
		}
	}
	Ok(())
}

fn process_vol(reg: u8, chip_id: u8, data: u8, state: &mut AY8910State, midi: &mut MIDIShim) {
	let channel = u4::from(reg - AY_AVOL);
	let channel_ptr = (chip_id * 0x3 + channel.as_int()) as usize;
	let midi_channel = if chip_id != 0 {
		u4::from(0x00)
	} else {
		MIDI_CHANNEL_PSG_BASE
	} + channel;

	if data & 0x10 != 0 {
		// use Envelope data
		state.attenuation_2[channel_ptr] = 0xFF;
		state.factored.midi_volume[0] = 0x7F.into();
	} else {
		state.attenuation_2[channel_ptr] = data & 0xF;
		state.factored.midi_volume[0] = (state.attenuation_2[channel_ptr] << 3).into();
	}
	midi.controller_write(
		midi_channel,
		MIDI_VOLUME,
		state.factored.midi_volume[0],
	);
}

fn process_tuning(
	reg: u8,
	chip_id: u8,
	state: &mut AY8910State,
	data: u8,
	clock: u32,
	midi: &mut MIDIShim,
) -> Result<()> {
	let channel = u4::from(reg >> 1);
	let channel_ptr = (chip_id * 0x3 + channel.as_int()) as usize;
	let midi_channel = if chip_id != 0 {
		u4::from(0x00)
	} else {
		MIDI_CHANNEL_PSG_BASE
	} + channel;

	state.factored.fnum_1[channel.as_int() as usize] =
		state.factored.fnum_2[channel.as_int() as usize];
	if reg & 0x1 != 0 {
		// AY_xCOARSE
		state.factored.fnum_2[channel_ptr] =
			(state.factored.fnum_2[channel_ptr] & 0xFF)
				| ((data as u32 & 0xF) << 8);
	} else {
		// AY_xFINE
		state.factored.fnum_2[channel_ptr] = (state.factored.fnum_2
			[channel_ptr]
			& 0xF00) | data as u32;
	}

	state.factored.hz_1[channel_ptr] =
		state.factored.hz_2[channel_ptr];
	state.factored.hz_2[channel_ptr] = hz_ay(
		state.factored.fnum_2[channel_ptr].into(),
		clock,
	);

	state.factored.note_1[channel_ptr] =
		state.factored.note_2[channel_ptr];
	let mut temp_note = hz_to_note(state.factored.hz_2[channel_ptr]);
	if temp_note >= 0x80.into() {
		//TempNote = 0x7F - state.factored.fnum_2[channel as usize]
		temp_note = 0x7F.into()
	}
	state.factored.note_2[channel_ptr] = temp_note;

	if state.factored.note_1[channel_ptr]
		!= state.factored.note_2[channel_ptr]
	{
		midi.do_note_on(
			state.factored.note_1[channel_ptr],
			state.factored.note_2[channel_ptr],
			midi_channel,
			&mut state.factored.midi_note[channel_ptr],
			&mut state.factored.midi_wheel[channel_ptr],
			None,
			None,
		)?;
	}
	Ok(())
}

pub(crate) fn ay_vol_to_db(tl: u8) -> f32 {
	if tl > 0x0 {
		(tl - 0xF).into()
	} else {
		-400.0 // results in volume 0
	}
}