use cpal::Stream;
use cpal::traits::DeviceTrait;
use cpal::traits::HostTrait;
use num::Complex;
use oxydsp_dsp::blocks::filtering::fir::FirFilter;
use oxydsp_dsp::blocks::math::basic::Multiplier;
use oxydsp_dsp::blocks::synthesis::Nco;
use oxydsp_dsp::blocks::synthesis::OscillatorSource;
use oxydsp_dsp::blocks::utilities::adapters::FlatMap;
use oxydsp_dsp::blocks::utilities::adapters::Map;
use oxydsp_dsp::blocks::utilities::adapters::Repeat;
use oxydsp_dsp::blocks::utilities::adapters::Scan;
use oxydsp_dsp::blocks::utilities::channels::RxSource;
use oxydsp_dsp::blocks::utilities::channels::TxSink;
use oxydsp_dsp::filtering::fir::Fir;
use oxydsp_dsp::units::DigitalFrequency;
use oxydsp_flowgraph::BlockIO;
use oxydsp_flowgraph::block::Block;
use oxydsp_flowgraph::block::BlockResult;
use oxydsp_flowgraph::flowgraph;
use oxydsp_flowgraph::graph::FlowGraph;
use oxydsp_flowgraph::io::In;
use oxydsp_flowgraph::io::Out;
use rand::random;
use std::f32::consts::PI;
use std::iter::FusedIterator;
use std::net::UdpSocket;
use std::ops::BitXor;
use std::sync::mpsc;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::SyncSender;
use std::sync::mpsc::sync_channel;
use std::thread::JoinHandle;
use std::time::Duration;

use crate::CARRIER;
use crate::DEVIATION;
use crate::SAMPLE_PER_SYMBOL;
use crate::SAMPLE_RATE;
use crate::gaussian;
use crate::to_bits;

pub struct Transmitter
{
    flowgraph_handle: JoinHandle<()>,
    packet_sender: SyncSender<Vec<u8>>,
    stream: Stream,
}

impl Transmitter
{
    pub fn start_new() -> Self
    {
        let carrier = DigitalFrequency::from_time_frequency(CARRIER, SAMPLE_RATE as f64);
        let deviation = DigitalFrequency::from_time_frequency(DEVIATION, SAMPLE_RATE as f64);

        let (packet_tx, packet_rx): (_, Receiver<Vec<u8>>) = sync_channel(128);
        let (packet_rec, packets): (_, In<Vec<u8>>) = RxSource::new(packet_rx);
        let (linearizer, bits) = FlatMap::new(packets, |packet| {
            // +1 for chksum
            let packet_length = packet.len() as u16;
            let checksum = packet.iter().copied().reduce(BitXor::bitxor).unwrap();

            // Learning sequence
            let mut frame = vec![0b10101010; 8];
            // Preamble
            frame.push(0b01100111);
            frame.push(packet_length.to_le_bytes()[0]);
            frame.push(packet_length.to_le_bytes()[1]);
            frame.extend(packet.iter());
            frame.push(checksum);
            frame.extend((0..16).map(|_| 0));
            frame
                .into_iter()
                .flat_map(to_bits)
                .map(|x| if x { 1. } else { -1. })
        });

        let (repeat, bits) = FlatMap::new(bits, |symbol| {
            let mut v = vec![0.; SAMPLE_PER_SYMBOL - 1];
            v.push(symbol);
            v
        });

        // gaussian fir
        // let fir = Fir((0..SAMPLE_PER_SYMBOL)
        //     .map(|x| gaussian(0.3, x as f32 / SAMPLE_PER_SYMBOL as f32))
        //     .collect());
        //.normalized();

        // RRC fir
        let rrc_symbol_count = 4;
        let rrc_fir_length = SAMPLE_PER_SYMBOL * rrc_symbol_count;
        let fir = Fir((0..rrc_fir_length)
            .map(|x| {
                let centered = oxydsp_dsp::map(
                    x as f32,
                    0.,
                    rrc_fir_length as f32,
                    -(rrc_symbol_count as f32) * 0.5,
                    rrc_symbol_count as f32 * 0.5,
                );
                root_raised_cosine(centered, 1., 1.)
            })
            .collect());

        let (bit_filter, bits) = FirFilter::new(bits, fir);
        let (to_freq, freq) = Map::new(bits, move |x| {
            DigitalFrequency::from_time_frequency(DEVIATION * x as f64, SAMPLE_RATE as f64)
        });
        let (base_oscillator, baseband) = Nco::<f32>::new(freq);
        let (local_oscillator, lo) = OscillatorSource::<f32>::new(carrier.into());
        let (frontend, passband) = Multiplier::new(baseband, lo);
        let (audio_tx, audio_rx) = mpsc::channel::<Complex<f32>>();

        let reverb_length = 200;
        let (reverb, passband) = FirFilter::new(
            passband,
            Fir((0..reverb_length)
                .map(|x| (-5. * (x as f32) / (reverb_length as f32)).exp())
                .collect())
            .normalized(),
        );

        let (awgn, passband) = Map::new(passband, |x| {
            x + Complex::<f32>::new(2. * (random::<f32>() - 0.5), 2. * (random::<f32>() - 0.5))
                * 0.3
        });

        let (udp_map, passband) = Scan::new(
            passband,
            UdpSocket::bind("0.0.0.0:0").unwrap(),
            |sckt, sample| {
                std::thread::sleep(Duration::from_micros(12));
                sckt.send_to(&(sample.re).to_le_bytes(), "127.0.0.1:25565")
                    .unwrap();
                sample
            },
        );
        let tx_sink = TxSink::new(passband, audio_tx);

        let graph = flowgraph![
            packet_rec,
            linearizer,
            reverb,
            bit_filter,
            udp_map,
            to_freq,
            repeat,
            base_oscillator,
            local_oscillator,
            frontend,
            awgn,
            tx_sink,
        ];

        // Open output device
        let host = cpal::default_host();
        let device = host
            .default_output_device()
            .expect("no output device available");
        let mut supported_configs_range = device
            .supported_output_configs()
            .expect("error while querying configs");
        let supported_config = supported_configs_range
            .next()
            .expect("no supported config?!")
            .with_sample_rate(SAMPLE_RATE as u32);
        let stream = device
            .build_output_stream(
                &supported_config.into(),
                move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
                    for x in data.iter_mut()
                    {
                        if let Ok(y) = audio_rx.try_recv()
                        {
                            *x = y.re * 0.01;
                        }
                        else
                        {
                            *x = 0.;
                        }
                    }
                },
                move |err| panic!(),
                None, // None=blocking, Some(Duration)=timeout
            )
            .unwrap();

        Self {
            flowgraph_handle: graph.run(),
            packet_sender: packet_tx,
            stream,
        }
    }

    pub fn transmit(&self, data: Vec<u8>)
    {
        let _ = self.packet_sender.send(data);
    }
}

pub fn root_raised_cosine(t: f32, beta: f32, ts: f32) -> f32
{
    let eps = 1e-8;

    if t.abs() < eps
    {
        // t = 0 special case
        return (1.0 / ts.sqrt()) * (1.0 + beta * (4.0 / PI - 1.0));
    }

    if beta > 0.0 && (t.abs() - ts / (4.0 * beta)).abs() < eps
    {
        // t = ±T / (4β) special case
        let term1 = (1.0 + 2.0 / PI) * (PI / (4.0 * beta)).sin();
        let term2 = (1.0 - 2.0 / PI) * (PI / (4.0 * beta)).cos();
        return (beta / (ts.sqrt() * 2.0_f32.sqrt())) * (term1 + term2);
    }

    // General case
    let numerator = (PI * t * (1.0 - beta) / ts).sin()
        + 4.0 * beta * t / ts * (PI * t * (1.0 + beta) / ts).cos();

    let denominator = PI * t * (1.0 - (4.0 * beta * t / ts).powi(2)) / ts;

    (1.0 / ts.sqrt()) * (numerator / denominator)
}
