use cpal;
use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait};
use std::sync::Arc;
use std::thread;

use super::sources::AudioSource;

pub struct Hal {
    event_loop: Arc<cpal::EventLoop>,
    stream_id: cpal::StreamId,
    format: cpal::Format,
}

impl Hal {
    pub fn new() -> Option<Self> {
        let host = cpal::default_host();
        let device = host.default_output_device()?;
        let format = device.default_output_format().ok()?;
        let event_loop = Arc::new(host.event_loop());
        let stream_id = event_loop.build_output_stream(&device, &format).ok()?;
        Some(Hal {
            event_loop,
            stream_id,
            format,
        })
    }

    pub fn get_sample_rate(&self) -> u32 {
        self.format.sample_rate.0
    }

    pub fn start(&self, mut source: Box<dyn AudioSource + Send>) {
        self.event_loop
            .play_stream(self.stream_id.clone())
            .expect("Failed to start audio stream");

        // Spawn the event loop
        let event_loop = self.event_loop.clone();
        let channels = self.format.channels as usize;

        thread::spawn(move || {
            event_loop.run(move |stream_id, stream_result| {
                #[cfg(feature = "logger")]
                let time_logger = crate::utils::logger::TimeLogger::new();

                source.process_messages();

                #[cfg(feature = "logger")]
                time_logger.log("Events processed");

                let stream_data = match stream_result {
                    Ok(data) => data,
                    Err(err) => {
                        eprintln!("Stream {:?} closed due to an error: {}", stream_id, err);
                        return;
                    }
                };

                match stream_data {
                    cpal::StreamData::Output {
                        buffer: cpal::UnknownTypeOutputBuffer::U16(mut buffer),
                    } => {
                        for sample in buffer.chunks_mut(channels) {
                            let value =
                                ((source.get_sample() * 0.5 + 0.5) * std::u16::MAX as f32) as u16;
                            for out in sample.iter_mut() {
                                *out = value;
                            }
                        }
                    }
                    cpal::StreamData::Output {
                        buffer: cpal::UnknownTypeOutputBuffer::I16(mut buffer),
                    } => {
                        for sample in buffer.chunks_mut(channels) {
                            let value = (source.get_sample() * std::i16::MAX as f32) as i16;
                            for out in sample.iter_mut() {
                                *out = value;
                            }
                        }
                    }
                    cpal::StreamData::Output {
                        buffer: cpal::UnknownTypeOutputBuffer::F32(mut buffer),
                    } => {
                        for sample in buffer.chunks_mut(channels) {
                            let value = source.get_sample();
                            for out in sample.iter_mut() {
                                *out = value;
                            }
                        }
                    }
                    _ => (),
                }

                #[cfg(feature = "logger")]
                time_logger.log("Done");
            });
        });
    }
}
