use std::fmt::Debug;

use num::Complex;
use num::Float;
use oxydsp_flowgraph::BlockIO;
use oxydsp_flowgraph::block::Block;
use oxydsp_flowgraph::io::In;
use oxydsp_flowgraph::io::Out;
use rustfft::FftNum;

use crate::filtering::fir::Fir;
use crate::filtering::fir::FirFilter;
use crate::synthesis::oscillator::Nco;

#[derive(BlockIO)]
pub struct ZeroIf<T: std::clone::Clone + num::Num + Float + From<f32> + 'static>
{
    #[input]
    input: In<T>,

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

    local_oscillator: Nco<T>,
    filter: FirFilter<Complex<T>, Complex<T>, Complex<T>>,
}

impl<T> ZeroIf<T>
where
    T: std::clone::Clone
        + num::Num
        + FftNum
        + From<f32>
        + 'static
        + num::Float
        + num::traits::FloatConst,
{
    pub fn new(input: In<T>, lo: Nco<T>) -> (Self, In<Complex<T>>)
    {
        let (output, port) = oxydsp_flowgraph::io::stream();
        (
            Self {
                input,
                output,
                local_oscillator: lo,
                filter: FirFilter::new(Fir::lowpass(lo.frequency(), 100).normalized_len()),
            },
            port,
        )
    }

    pub fn set_fir(&mut self, fir: Fir<Complex<T>>)
    {
        self.filter = FirFilter::new(fir);
    }
}

impl<T> Block for ZeroIf<T>
where
    T: std::clone::Clone + num::Num + Float + From<f32> + 'static + num::Float,
{
    fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
    {
        self.output.push_iter(self.input.iter().map(|input| {
            let (data, tag) = input.into();
            // Mix
            let lo_sample = self.local_oscillator.next().unwrap();
            let iq = Complex::new(data * lo_sample.re, data * lo_sample.im);

            (self.filter.next(iq), tag).into()
        }));
        oxydsp_flowgraph::block::BlockResult::Ok
    }
}
