A rust port of vgm2mid by Paul Jensen and Valley Bell
use crate::midi_shim::MIDIShim;
use crate::midi_shim::{
	MIDI_DATA_ENTRY_MSB, MIDI_NRPN_LSB, MIDI_NRPN_MSB, MIDI_PITCHWHEEL_CENTER, NRPN_DRUM_PAN,
};
use crate::strict;
use anyhow::{bail, Result};

pub(crate) struct SegaPCMState {
	ram: [u8; 0x800],
	chn_addr: [u32; 0x10],
	chn_vol_l: [u8; 0x10],
	chn_vol_r: [u8; 0x10],
	chn_note: [u8; 0x10],
	last_pan: [u8; 0x10],
}

impl Default for SegaPCMState {
	fn default() -> Self {
		SegaPCMState {
			ram: [0; 0x800],
			chn_addr: [0; 0x10],
			chn_vol_l: [0; 0x10],
			chn_vol_r: [0; 0x10],
			chn_note: [0; 0x10],
			last_pan: [0; 0x10],
		}
	}
}

pub(crate) struct SegaPCM {
	state: SegaPCMState,
}

impl SegaPCM {
	pub(crate) fn new(opt_state: Option<SegaPCMState>) -> Self {
		Self {
			state: opt_state.unwrap_or_default(),
		}
	}

	pub(crate) fn mem_write(
		&mut self,
		offset: u16,
		data: u8,
		midi: &mut MIDIShim,
	) -> Result<()> {
		if offset > 0x7FF {
			strict!("sega_pcm_mem_write offset is larger than 0x7FF");
		}
		let masked_offset = offset & 0x7FF;
		let channel = ((masked_offset / 0x8) & 0xF) as u8;
		let rel_offset = masked_offset & (!0x78);
		self.state.ram[masked_offset as usize] = data;

		match rel_offset {
			0x86 => {
				channel_on_off(data, midi, &mut self.state, channel);
			},
			0x4 => {
				set_audio_address_low(&mut self.state, channel, data);
			},
			0x5 => {
				set_audio_address_time(&mut self.state, channel, data, midi)?;
			},
			0x84 => (), // Set Loop Address Low
			0x85 => (), // Set Loop Address High
			0x6 => (),  // Set End Address
			0x7 => {
				set_sample_delta_time(midi);
			},
			0x2 => {
				set_volume_l(&mut self.state, channel, data);
			},
			0x3 => {
				set_volume_r(&mut self.state, channel, data);
			},
			_ => (), // Write Offset ##, Data ##
		}
		Ok(())
	}

	pub(crate) fn dump(&mut self) -> Result<()> {
		match std::fs::read("SegaPCM.dmp") {
			Ok(mut vec) => {
				vec.resize(self.state.ram.len(), 0);
				self.state.ram.copy_from_slice(vec.as_slice());
				Ok(())
			},
			Err(_) => bail!("Failed when attempting to read SegaPCM dump."), //FIXME: error handling
		}
	}
}

fn set_volume_r(state: &mut SegaPCMState, channel: u8, data: u8) {
	// Set Volume R
	state.chn_vol_r[channel as usize] = data
	//midi.event_write(Midi { channel: Channel, message: Controller { controller: MIDI_VOLUME | 0x20, value: TempByt }});
}

fn set_volume_l(state: &mut SegaPCMState, channel: u8, data: u8) {
	// Set Volume L
	state.chn_vol_l[channel as usize] = data
	//midi.event_write(Midi { channel: Channel, message: Controller { controller: MIDI_VOLUME, value: TempByt }});
}

fn set_sample_delta_time(midi: &mut MIDIShim) {
	// Set Sample Delta Time
	midi.pitch_bend_write(0x09.into(), MIDI_PITCHWHEEL_CENTER);
}

fn set_audio_address_time(
	state: &mut SegaPCMState,
	channel: u8,
	data: u8,
	midi: &mut MIDIShim,
) -> Result<()> {
	// Set Audio Address High
	state.chn_addr[channel as usize] =
		(state.chn_addr[channel as usize] & 0xFF) | (data as u32) << 8;

	let nrpn_data = match state.chn_addr[channel as usize] {
		0x0 => 0x0,
		0x437C => 0x38,
		0x49DE => 0x1C,
		0x302F => 0x2A,
		0x17C0 | 0x172F => 0x2E,
		0x90 => 0x26,
		0xF0 => 0x2D,
		0x29AB => 0x24,
		0x5830 => 0x27,
		0x1C03 => 0x30,
		0x951 => 0x2B,
		0x3BE6 => 0x31,
		0x4DA0 => 0x45,
		0xC1D => 0x28,
		0x6002 => 0x33,
		//FIXME: why was this commented 0x6700 => 0x7F,
		0x82 => 0x60,
		0x2600 => 0x61,
		0x4A => 0x62,
		0x5600 => 0x63,
		0x4000 => 0x65,
		0x2D6 => 0x67,
		0x158A => 0x68,
		0xC04 => 0x69,
		0x1CD0 => 0x6A,
		0x218E => 0x6B,
		0x1E34 => 0x6C,
		0xE31 => 0x6D,
		0x89B => 0x6E,
		0x27B => 0x6F,
		0x4EA => 0x70,
		0x3600 => 0x71,
		0x1800 => 0x72,
		0x18BA => 0x73,
		0x173 => 0x74,
		0x204F => 0x75,
		0xAB => 0x76,
		0x201D => 0x77,
		0x20AB => 0x78,
		0x2000 => 0x79,
		0x2 => 0x7A,
		0x1D => 0x7B,
		0x2F => 0x7C,
		0x26AB => 0x7D,
		0x262F => 0x7E,
		_ => {
			strict!(
				"Bad SegaPCM channel address for channel {}: {}",
				channel,
				state.chn_addr[channel as usize]
			);
			0x7F
		},
	};

	midi.note_off_write(0x09.into(), state.chn_note[channel as usize].into(), 0x00.into());
	let mut note_vol = state.chn_vol_l[channel as usize] + state.chn_vol_r[channel as usize];
	let temp_byte = if note_vol > 0 {
		(state.chn_vol_r[channel as usize] / (note_vol / 2)) * 0x40
	} else {
		0x40
	};

	if state.last_pan[channel as usize] != temp_byte {
		midi.controller_write(0x09.into(), MIDI_NRPN_MSB, NRPN_DRUM_PAN);
		midi.controller_write(0x09.into(), MIDI_NRPN_LSB, nrpn_data.into());
		midi.controller_write(0x09.into(), MIDI_DATA_ENTRY_MSB, temp_byte.into());
		state.last_pan[channel as usize] = temp_byte;
	}
	//midi.event_write(Midi { channel: 0x9, message: Controller { controller: 0x20, value: Channel }});
	if note_vol == 0x0 {
		note_vol = 0x1
	}
	//Data = Data - Channel
	if note_vol <= 0x40 {
		note_vol = 0x40 + note_vol / 2
	}
	midi.note_on_write(0x09.into(), data.into(), note_vol.into());
	//midi.event_write(Midi { channel: 0x9, message: NoteOff { key: Data, vel: 0x0 }});

	state.chn_note[channel as usize] = data;

	Ok(())
}

fn set_audio_address_low(state: &mut SegaPCMState, channel: u8, data: u8) {
	// Set Audio Address Low
	state.chn_addr[channel as usize] =
		(state.chn_addr[channel as usize] & 0xFF00) | data as u32;
}

fn channel_on_off(data: u8, midi: &mut MIDIShim, state: &mut SegaPCMState, channel: u8) {
	// Channel On/Off
	//OnOff = Data & 0x1
	if data & 0x1 != 0 {
		midi.note_off_write(0x09.into(), state.chn_note[channel as usize].into(), 0x00.into());
		state.chn_note[channel as usize] = 0x0;
	}
}