use approx::assert_relative_eq;
use itertools::izip;
use rawdio::{prelude::*, Mixer};

struct Fixture {
    channel_count: usize,
    sample_rate: usize,
    context: Box<dyn Context>,
    audio_process: Box<dyn AudioProcess>,
    mixer: Mixer,
}

impl Fixture {
    fn process(&mut self, input: &dyn AudioBuffer) -> OwnedAudioBuffer {
        let mut output_buffer =
            OwnedAudioBuffer::new(input.frame_count(), self.channel_count, self.sample_rate);

        self.audio_process.process(input, &mut output_buffer);

        output_buffer
    }

    fn new(channel_count: usize) -> Self {
        let sample_rate = 44100;

        let (mut context, process) =
            create_engine_with_options(EngineOptions::default().with_sample_rate(sample_rate));

        let mixer = Mixer::new(context.as_ref(), channel_count, channel_count);

        connect_nodes!("input" => mixer => "output");

        context.start();

        Self {
            channel_count,
            sample_rate,
            context,
            audio_process: process,
            mixer,
        }
    }
}

impl Default for Fixture {
    fn default() -> Self {
        let channel_count = 2;
        Self::new(channel_count)
    }
}

impl Drop for Fixture {
    fn drop(&mut self) {
        self.context.stop();
    }
}

#[test]
fn test_phase_invert() {
    let channel_count = 1;
    let mut fixture = Fixture::new(channel_count);
    fixture.mixer.set_level(0, 0, Level::from_linear(-1.0));

    let frame_count = 1_024;
    let sample_rate = 48_000;
    let input_signal = OwnedAudioBuffer::white_noise(frame_count, channel_count, sample_rate);

    let output_signal = fixture.process(&input_signal);

    let input_data = input_signal.get_channel_data(SampleLocation::channel(0));
    let output_data = output_signal.get_channel_data(SampleLocation::channel(0));

    for (input_sample, output_sample) in izip!(input_data.iter(), output_data.iter()) {
        assert_relative_eq!(*output_sample, -1.0 * *input_sample);
    }
}

#[test]
fn test_channel_flip() {
    let channel_count = 2;
    let mut fixture = Fixture::new(channel_count);
    fixture.mixer.set_level(0, 0, Level::zero());
    fixture.mixer.set_level(0, 1, Level::unity());
    fixture.mixer.set_level(1, 0, Level::unity());
    fixture.mixer.set_level(1, 1, Level::zero());

    let frame_count = 1_024;
    let sample_rate = 48_000;
    let input_signal = OwnedAudioBuffer::white_noise(frame_count, channel_count, sample_rate);

    let output_signal = fixture.process(&input_signal);

    let input_0 = input_signal.get_channel_data(SampleLocation::channel(0));
    let input_1 = input_signal.get_channel_data(SampleLocation::channel(1));
    let output_0 = output_signal.get_channel_data(SampleLocation::channel(0));
    let output_1 = output_signal.get_channel_data(SampleLocation::channel(1));

    for (input_sample, output_sample) in izip!(input_0.iter(), output_1.iter()) {
        assert_relative_eq!(*output_sample, *input_sample);
    }

    for (input_sample, output_sample) in izip!(input_1.iter(), output_0.iter()) {
        assert_relative_eq!(*output_sample, *input_sample);
    }
}

#[test]
fn test_stereo_to_mono() {
    let channel_count = 2;
    let mut fixture = Fixture::new(channel_count);
    fixture.mixer.set_level(0, 0, Level::from_linear(0.5));
    fixture.mixer.set_level(1, 0, Level::from_linear(0.5));

    let frame_count = 1_024;
    let sample_rate = 48_000;
    let input_signal = OwnedAudioBuffer::white_noise(frame_count, channel_count, sample_rate);

    let output_signal = fixture.process(&input_signal);

    let input_0 = input_signal.get_channel_data(SampleLocation::channel(0));
    let input_1 = input_signal.get_channel_data(SampleLocation::channel(1));
    let output_0 = output_signal.get_channel_data(SampleLocation::channel(0));

    assert!(output_signal.channel_is_silent(1));

    for (input_sample_0, input_sample_1, output_sample) in
        izip!(input_0.iter(), input_1.iter(), output_0.iter())
    {
        let expected_sample = 0.5 * (*input_sample_0 + *input_sample_1);
        assert_relative_eq!(*output_sample, expected_sample);
    }
}
