use std::{
    sync::{Arc, Mutex},
    thread::{self, JoinHandle},
};

use anyhow::Result;
use pulse::sample::{Format, Spec};
use ringbuffer::{ConstGenericRingBuffer, RingBufferExt, RingBufferWrite};
use sdl2::{
    event::Event,
    keyboard::Keycode,
    pixels::Color,
    rect::{Point, Rect},
};

use crate::{
    capture,
    dsp::{self, ZTransformFilter},
};

use super::OutputLayer;

const SAMPLE_RATE: usize = 5000;
const PULSE_UPDATES_PER_SECOND: usize = 50;
const BUFFER_SIZE: usize = SAMPLE_RATE / PULSE_UPDATES_PER_SECOND;

const POINT_COUNT: usize = SAMPLE_RATE / 200;
const POINT_BUFFER_SIZE: usize = POINT_COUNT.next_power_of_two();

const MIN_MAX_SAMPLE_COUNT: usize = 10;

pub struct BeatDetectinator {
    join_handle: Option<JoinHandle<Result<()>>>,
    shared_state: Arc<Mutex<BeatDetectinatorSharedState>>,
}

struct BeatDetectinatorSharedState {
    running: bool,
    brightness: f32,
    threshold: f32,
    point_buf: ConstGenericRingBuffer<f32, POINT_BUFFER_SIZE>,
}

impl BeatDetectinator {
    pub fn new() -> Self {
        let shared_state = Arc::new(Mutex::new(BeatDetectinatorSharedState {
            running: true,
            brightness: 0.0,
            threshold: 1.5,
            point_buf: Default::default(),
        }));

        {
            shared_state.lock().unwrap().point_buf.fill_default();
        }

        let join_handle = {
            let shared_state = shared_state.clone();
            Some(thread::spawn(move || audio_loop(shared_state)))
        };

        println!("Audio thread started.");

        Self {
            join_handle,
            shared_state,
        }
    }
}

impl Drop for BeatDetectinator {
    fn drop(&mut self) {
        {
            self.shared_state.lock().unwrap().running = false;
        }

        match self.join_handle.take().unwrap().join().unwrap() {
            Ok(_) => println!("Audio thread stopped."),
            Err(e) => println!("Audio thread died: {:?}", e),
        }
    }
}

fn audio_loop(shared_state: Arc<Mutex<BeatDetectinatorSharedState>>) -> Result<()> {
    let spec = Spec {
        format: Format::F32le,
        rate: SAMPLE_RATE as u32,
        channels: 1,
    };
    assert!(spec.is_valid());

    let reader = capture::get_audio_reader(&spec)?;
    let mut buffer = [0u8; 4 * BUFFER_SIZE];

    let mut bass_filter = dsp::BassFilter::default();
    let mut envelope_filter = dsp::EnvelopeFilter::default();
    let mut beat_filter = dsp::BeatFilter::default();
    let mut j = 0;

    loop {
        {
            if shared_state.lock().unwrap().running == false {
                break Ok(());
            }
        }

        reader.read(&mut buffer)?;

        for i in 0..BUFFER_SIZE {
            let mut float_bytes = [0u8; 4];
            float_bytes.copy_from_slice(&buffer[4 * i..4 * i + 4]);

            j += 1;
            let sample = f32::from_le_bytes(float_bytes);
            let mut value = bass_filter.process(sample);

            if value < 0f32 {
                value = -value;
            }

            let envelope = envelope_filter.process(value);

            if j == 200 {
                let beat = beat_filter.process(envelope);

                shared_state.lock().unwrap().point_buf.push(beat);

                j = 0;
            }
        }
    }
}

impl OutputLayer for BeatDetectinator {
    fn tick(&mut self, _dt: std::time::Duration) {}

    fn draw_sdl(&self, canvas: &mut sdl2::render::Canvas<sdl2::video::Window>, texture_size: u32) {
        let min_y = 0f32;
        let max_y = 3f32;

        let beat = {
            self.shared_state
                .lock()
                .unwrap()
                .point_buf
                .back()
                .unwrap()
                .clone()
        };

        let get_y = |y: &f32| {
            let mut y = y.clone();

            if y <= 0f32 {
                y = 0f32;
            }

            y = (1f32 + y).log2();

            let y = (y - min_y) / (max_y - min_y);

            ((1f32 - y) * texture_size as f32) as u32
        };

        // background
        let v;
        let (threshold, points) = {
            let mut shared_state = self.shared_state.lock().unwrap();
            shared_state.brightness = if beat > shared_state.threshold {
                1f32
            } else {
                0.75f32 * shared_state.brightness
            };

            v = (255f32 * shared_state.brightness) as u8;

            (
                shared_state.threshold.clone(),
                shared_state.point_buf.to_vec(),
            )
        };

        canvas.set_draw_color(Color::RGB(v, v, v));
        canvas.clear();

        // zero

        canvas.set_draw_color(Color::RGB(0, 128, 0));
        let y = get_y(&0f32);

        canvas
            .draw_line(
                Point::new(0, y as i32),
                Point::new(texture_size as i32, y as i32),
            )
            .unwrap();

        // min / max lines

        canvas.set_draw_color(Color::RGB(255, 0, 0));

        let min_beat = points
            .iter()
            .skip(points.len() - MIN_MAX_SAMPLE_COUNT)
            .reduce(|a, b| if a < b { a } else { b })
            .unwrap();

        let x = texture_size - MIN_MAX_SAMPLE_COUNT as u32 * 10;

        let y = get_y(min_beat);

        canvas
            .draw_line(
                Point::new(x as i32, y as i32),
                Point::new(texture_size as i32, y as i32),
            )
            .unwrap();

        let max_beat = points
            .iter()
            .skip(points.len() - MIN_MAX_SAMPLE_COUNT)
            .reduce(|a, b| if a > b { a } else { b })
            .unwrap();

        let y = get_y(max_beat);

        canvas
            .draw_line(
                Point::new(x as i32, y as i32),
                Point::new(texture_size as i32, y as i32),
            )
            .unwrap();

        // threshhold line

        canvas.set_draw_color(Color::RGB(0, 0, 255));
        let y = get_y(&threshold);

        canvas
            .draw_line(
                Point::new(0, y as i32),
                Point::new(texture_size as i32, y as i32),
            )
            .unwrap();

        // values

        canvas.set_draw_color(Color::RGB(0, 255, 0));

        for (i, beat) in points.iter().skip(points.len() - POINT_COUNT).enumerate() {
            let x = 10 * i;
            let y = get_y(beat);

            canvas
                .fill_rect(Rect::new((x + 1) as i32, (y - 1) as i32, 8, 3))
                .unwrap();
        }
    }

    fn on_sdl_event(&mut self, event: &Event) {
        match event {
            Event::KeyDown {
                keycode: Some(Keycode::Up),
                ..
            } => {
                let mut shared_state = self.shared_state.lock().unwrap();
                shared_state.threshold += 0.01f32;
                println!("threshold: {:.2}", shared_state.threshold);
            }
            Event::KeyDown {
                keycode: Some(Keycode::Down),
                ..
            } => {
                let mut shared_state = self.shared_state.lock().unwrap();
                shared_state.threshold -= 0.01f32;
                println!("threshold: {:.2}", shared_state.threshold);
            }
            _ => {}
        }
    }

    fn update_dmx(&self, output: &mut [crate::fixtures::MovingHead; 4]) {
        let shared_state = self.shared_state.lock().unwrap();
        for head in output.iter_mut() {
            head.dimmer = 0.2 + 0.8 * shared_state.brightness;
        }
    }
}
