mod capture;
mod dsp;

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

use crate::dsp::ZTransformFilter;

const SAMPLE_RATE: usize = 5000;
const FPS: usize = 50;

const BUFFER_SIZE: usize = SAMPLE_RATE / FPS;

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;

fn main() -> 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;

    let mut threshold = 1.5f32;

    let mut brightness = 0f32;

    let mut beat_toggle = false;

    // sdl

    let sdl = sdl2::init().unwrap();
    let sdl_video = sdl.video().unwrap();

    let window = sdl_video
        .window("Beat Detectinator", 800, 600)
        .resizable()
        .build()?;
    let mut canvas = window.into_canvas().build()?;
    let texture_creator = canvas.texture_creator();
    let texture_size = 10 * POINT_COUNT as u32;
    let mut texture = texture_creator.create_texture_target(
        canvas.default_pixel_format(),
        texture_size,
        texture_size,
    )?;

    let mut point_buf = ConstGenericRingBuffer::<f32, POINT_BUFFER_SIZE>::new();
    point_buf.fill_default();

    let mut event_pump = sdl.event_pump().unwrap();

    'running: loop {
        for event in event_pump.poll_iter() {
            match event {
                Event::Quit { .. }
                | Event::KeyDown {
                    keycode: Some(Keycode::Escape),
                    ..
                } => break 'running,
                Event::KeyDown {
                    keycode: Some(Keycode::Up),
                    ..
                } => {
                    threshold += 0.01f32;
                    println!("threshold: {:.2}", threshold);
                }
                Event::KeyDown {
                    keycode: Some(Keycode::Down),
                    ..
                } => {
                    threshold -= 0.01f32;
                    println!("threshold: {:.2}", threshold);
                }
                Event::KeyDown {
                    keycode: Some(k), ..
                } => {
                    println!("{}", k)
                }
                _ => {}
            }
        }

        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);
                point_buf.push(beat);

                canvas.with_texture_canvas(&mut texture, |canvas| {
                    let min_y = 0f32;
                    let max_y = 3f32;

                    let get_y = |y: &f32| {
                        
                        let mut y = y.clone();
                        
                        if y <= 0f32 {
                            y = 0f32;
                        }

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

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

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

                    // background

                    let prev_brightness = brightness;
                    brightness = if beat > threshold {
                        1f32
                    } else {
                        0.75f32 * brightness
                    };

                    if brightness - prev_brightness > 0.5 {
                        println!("beat. {}", if beat_toggle {"-"} else {"|"});
                        beat_toggle ^= true;
                    }

                    let v = (255f32 * brightness) as u8;

                    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 = point_buf
                        .iter()
                        .skip(point_buf.capacity() - 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 = point_buf
                        .iter()
                        .skip(point_buf.capacity() - 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 point_buf
                        .iter()
                        .skip(point_buf.capacity() - POINT_COUNT)
                        .enumerate()
                    {
                        let x = 10 * i;
                        let y = get_y(beat);

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

                j = 0;
            }
        }

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

        let (w, h) = canvas.window().drawable_size();

        let target_rect = if w > h {
            Rect::new(((w - h) / 2) as i32, 0, h, h)
        } else {
            Rect::new(0, ((h - w) / 2) as i32, w, w)
        };

        canvas.copy(&texture, None, Some(target_rect)).unwrap();
        canvas.present();
    }

    Ok(())
}
