// https://github.com/WeirdConstructor/HexoDSP/blob/master/examples/cpal_demo_node_api.rs

use crate::game::AudioMsg;

use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use crossbeam_channel::Receiver;
use hexodsp::{matrix_repr::MatrixRepr, *};
use std::io::Read;
use ticktock::Clock;

pub fn setup(event_channel: Receiver<AudioMsg>) {
	#[cfg(not(target_arch = "wasm32"))]
	let matrix_repr: MatrixRepr = {
		let mut buf = String::new();
		std::fs::File::open("assets/init.hxy")
			.unwrap()
			.read_to_string(&mut buf)
			.unwrap();
		MatrixRepr::deserialize(&buf).unwrap()
	};
	#[cfg(target_arch = "wasm32")]
	let matrix_repr: MatrixRepr = MatrixRepr::deserialize(include_str!("../assets/init.hxy")).unwrap();

	let (node_conf, node_exec) = new_node_engine();

	let mut matrix = Matrix::new(node_conf, 64, 64);
	matrix.from_repr(&matrix_repr).unwrap();

	start_backend(node_exec, move || {
		let color_mix = NodeId::Mix3(0);
		let color_mix_r_gain = color_mix.inp_param("gain1").unwrap();
		let color_mix_g_gain = color_mix.inp_param("gain2").unwrap();
		let color_mix_b_gain = color_mix.inp_param("gain3").unwrap();
		let jump_ad = NodeId::Ad(0);
		let jump_ad_trig = jump_ad.inp_param("trig").unwrap();
		let switch_ad = NodeId::Ad(1);
		let switch_ad_trig = switch_ad.inp_param("trig").unwrap();
		let fusion_ad = NodeId::Ad(2);
		let fusion_ad_trig = fusion_ad.inp_param("trig").unwrap();
		let fusion_ad2 = NodeId::Ad(3);
		let fusion_ad2_trig = fusion_ad2.inp_param("trig").unwrap();

		for (_tick, _now) in Clock::framerate(20.0).iter() {
			matrix.set_param(jump_ad_trig, (0.0).into());
			matrix.set_param(switch_ad_trig, (0.0).into());
			matrix.set_param(fusion_ad_trig, (0.0).into());
			matrix.set_param(fusion_ad2_trig, (0.0).into());

			if let Ok(msg) = event_channel.try_recv() {
				match msg {
					AudioMsg::Color([r, g, b]) => {
						matrix.set_param(color_mix_r_gain, r.into());
						matrix.set_param(color_mix_g_gain, g.into());
						matrix.set_param(color_mix_b_gain, b.into());
					}
					AudioMsg::Fusion => {
						matrix.set_param(fusion_ad_trig, (1.0).into());
						matrix.set_param(fusion_ad2_trig, (1.0).into());
					}
					AudioMsg::Jump => matrix.set_param(jump_ad_trig, (1.0).into()),
					AudioMsg::Switch => matrix.set_param(switch_ad_trig, (1.0).into()),
				}
			}
		}
	});
}

pub fn run<T, F: FnMut()>(
	device: &cpal::Device,
	config: &cpal::StreamConfig,
	mut node_exec: NodeExecutor,
	mut frontend_loop: F,
) where
	T: cpal::Sample,
{
	let sample_rate = config.sample_rate.0 as f32;
	let channels = config.channels as usize;

	node_exec.set_sample_rate(sample_rate);

	let input_bufs = [[0.0; hexodsp::dsp::MAX_BLOCK_SIZE]; 2];
	let mut outputbufs = [[0.0; hexodsp::dsp::MAX_BLOCK_SIZE]; 2];

	let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
	let stream = device
		.build_output_stream(
			config,
			move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
				let mut frames_left = data.len() / channels;

				let mut out_iter = data.chunks_mut(channels);

				node_exec.process_graph_updates();

				while frames_left > 0 {
					let cur_nframes = if frames_left >= hexodsp::dsp::MAX_BLOCK_SIZE {
						hexodsp::dsp::MAX_BLOCK_SIZE
					} else {
						frames_left
					};

					let input = &[
						&input_bufs[0][0..cur_nframes],
						&input_bufs[1][0..cur_nframes],
					];

					let split = outputbufs.split_at_mut(1);

					let mut output = [
						&mut ((split.0[0])[0..cur_nframes]),
						&mut ((split.1[0])[0..cur_nframes]),
					];

					let mut context = Context {
						nframes: cur_nframes,
						output: &mut output[..],
						input,
					};

					context.output[0].fill(0.0);
					context.output[1].fill(0.0);

					node_exec.process(&mut context);

					for i in 0..cur_nframes {
						if let Some(frame) = out_iter.next() {
							let mut ctx_chan = 0;
							for sample in frame.iter_mut() {
								let value: T =
									cpal::Sample::from::<f32>(&context.output[ctx_chan][i]);
								*sample = value;

								ctx_chan += 1;
								if ctx_chan > context.output.len() {
									ctx_chan = context.output.len() - 1;
								}
							}
						}
					}

					frames_left -= cur_nframes;
				}
			},
			err_fn,
		)
		.unwrap();
	stream.play().unwrap();

	frontend_loop();
}

fn start_backend<F: FnMut()>(node_exec: NodeExecutor, frontend_loop: F) {
	let host = cpal::default_host();
	let device = host
		.default_output_device()
		.expect("Finding useable audio device");
	let config = device
		.default_output_config()
		.expect("A workable output config");

	match config.sample_format() {
		cpal::SampleFormat::F32 => run::<f32, F>(&device, &config.into(), node_exec, frontend_loop),
		cpal::SampleFormat::I16 => run::<i16, F>(&device, &config.into(), node_exec, frontend_loop),
		cpal::SampleFormat::U16 => run::<u16, F>(&device, &config.into(), node_exec, frontend_loop),
	};
}