use crate::units::DigitalFrequency;
use num::Complex;
use num::Float;
use oxydsp_flowgraph::BlockIO;
use oxydsp_flowgraph::block::Block;
use oxydsp_flowgraph::block::BlockResult;
use oxydsp_flowgraph::io::In;
use oxydsp_flowgraph::io::Out;
use oxydsp_flowgraph::io::stream;

#[derive(BlockIO)]
pub struct OscillatorSource<T: Float + From<f32> + 'static>
{
    nco: crate::synthesis::oscillator::Nco<T>,

    #[output]
    output: Out<Complex<T>>,
}

impl<T: Float + From<f32> + 'static> OscillatorSource<T>
{
    pub fn new(nco: crate::synthesis::oscillator::Nco<T>) -> (Self, In<Complex<T>>)
    {
        let (output, signal) = stream();
        (Self { nco, output }, signal)
    }
}

impl<T: Float + From<f32> + 'static> Block for OscillatorSource<T>
{
    fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
    {
        self.output
            .push_iter((&mut self.nco).map(|x| (x, None).into()));
        BlockResult::Ok
    }
}

#[derive(BlockIO)]
pub struct Nco<T: Float + From<f32> + 'static>
{
    nco: crate::synthesis::oscillator::Nco<T>,

    #[input]
    frequency: In<DigitalFrequency>,

    #[output]
    output: Out<Complex<T>>,
}

impl<T: Float + From<f32> + 'static> Nco<T>
{
    pub fn new(input: In<DigitalFrequency>) -> (Self, In<Complex<T>>)
    {
        Self::with(
            input,
            crate::synthesis::oscillator::Nco::<T>::new(DigitalFrequency(0)),
        )
    }

    pub fn with(
        input: In<DigitalFrequency>,
        nco: crate::synthesis::oscillator::Nco<T>,
    ) -> (Self, In<Complex<T>>)
    {
        let (output, signal) = stream();
        (
            Self {
                nco,
                frequency: input,
                output,
            },
            signal,
        )
    }
}

impl<T: Float + From<f32> + 'static> Block for Nco<T>
{
    fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
    {
        self.output
            .push_iter(&mut self.frequency.iter().map(|f| {
                self.nco.set_frequency(f.0);
                (self.nco.next().unwrap(), f.1).into()
            }));
        BlockResult::Ok
    }
}
