use itertools::izip;

use crate::{graph::DspProcessor, prelude::*};

pub struct OscillatorProcessor {
    phase: f64,
    wavetable: Vec<f64>,
}

impl OscillatorProcessor {
    pub fn new(wavetable: Vec<f64>) -> Self {
        Self {
            phase: 0.0,
            wavetable,
        }
    }

    fn increment_phase(&mut self, frequency: f32, sample_rate: usize) {
        self.phase += (frequency as f64) / (sample_rate as f64);
        while self.phase > 1.0 {
            self.phase -= 1.0;
        }
    }

    fn get_value(&self) -> f64 {
        let offset = self.phase * self.wavetable.len() as f64;

        let offset_before = offset.floor() as usize;
        let offset_after = offset.ceil() as usize;

        let value_before = self.wavetable[offset_before];
        let value_after = if offset_after < self.wavetable.len() {
            self.wavetable[offset_after]
        } else {
            0.0
        };

        let weighting = offset - offset.floor();
        interpolate(value_before, value_after, weighting)
    }
}

fn interpolate(a: f64, b: f64, amount_of_b: f64) -> f64 {
    (1.0 - amount_of_b) * a + amount_of_b * b
}

impl DspProcessor for OscillatorProcessor {
    fn process_audio(&mut self, context: &mut crate::ProcessContext) {
        let sample_rate = context.output_buffer.sample_rate();

        let frequency_values = context
            .parameters
            .get_parameter_values("frequency", context.output_buffer.frame_count());
        let gain_values = context
            .parameters
            .get_parameter_values("gain", context.output_buffer.frame_count());

        let channel_count = context.output_buffer.channel_count();

        let location = SampleLocation::channel(0);
        let channel_data = context.output_buffer.get_channel_data_mut(location);

        for (sample, frequency) in izip!(channel_data.iter_mut(), frequency_values.iter()) {
            self.increment_phase(*frequency, sample_rate);
            *sample = self.get_value() as f32;
        }

        context.output_buffer.apply_gain(gain_values);

        (1..channel_count).for_each(|channel| {
            let location = SampleLocation::channel(0);
            context.output_buffer.duplicate_channel(
                location,
                channel,
                context.output_buffer.frame_count(),
            );
        });
    }
}
