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);
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,
		}
	}
			pub(crate) fn command_handle(
		&mut self,
		chip_id: u8,
		reg: u8,
		data: u8,
		clock: u32,
		midi: &mut MIDIShim,
	) -> Result<()> {
		
										
		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 => (),
						AY_EFINE | AY_ECOARSE => (),
			AY_ESHAPE => (),
			AY_PORTA | AY_PORTB => (),
						_ => 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 {
				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 {
				state.factored.fnum_2[channel_ptr] =
			(state.factored.fnum_2[channel_ptr] & 0xFF)
				| ((data as u32 & 0xF) << 8);
	} else {
				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() {
				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 	}
}