//! Oscilloscope waveform widget using braille characters.

use crate::theme;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::Color;
use ratatui::widgets::Widget;
use std::cell::RefCell;

thread_local! {
    static PATTERNS: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
}

/// Rendering direction for the oscilloscope.
#[derive(Clone, Copy)]
pub enum Orientation {
    Horizontal,
    Vertical,
}

/// Single-channel oscilloscope using braille dot plotting.
pub struct Scope<'a> {
    data: &'a [f32],
    orientation: Orientation,
    color: Option<Color>,
    gain: f32,
}

impl<'a> Scope<'a> {
    pub fn new(data: &'a [f32]) -> Self {
        Self {
            data,
            orientation: Orientation::Horizontal,
            color: None,
            gain: 1.0,
        }
    }

    pub fn orientation(mut self, o: Orientation) -> Self {
        self.orientation = o;
        self
    }

    pub fn color(mut self, c: Color) -> Self {
        self.color = Some(c);
        self
    }

    pub fn gain(mut self, g: f32) -> Self {
        self.gain = g;
        self
    }
}

impl Widget for Scope<'_> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        if area.width == 0 || area.height == 0 || self.data.is_empty() {
            return;
        }

        let color = self.color.unwrap_or_else(|| theme::get().meter.low);

        match self.orientation {
            Orientation::Horizontal => {
                render_horizontal(self.data, area, buf, color, self.gain)
            }
            Orientation::Vertical => render_vertical(self.data, area, buf, color, self.gain),
        }
    }
}

fn render_horizontal(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, gain: f32) {
    let width = area.width as usize;
    let height = area.height as usize;
    let fine_width = width * 2;
    let fine_height = height * 4;

    PATTERNS.with(|p| {
        let mut patterns = p.borrow_mut();
        let size = width * height;
        patterns.clear();
        patterns.resize(size, 0);

        for fine_x in 0..fine_width {
            let sample_idx = (fine_x * data.len()) / fine_width;
            let sample = (data.get(sample_idx).copied().unwrap_or(0.0) * gain).clamp(-1.0, 1.0);

            let fine_y = ((1.0 - sample) * 0.5 * (fine_height - 1) as f32).round() as usize;
            let fine_y = fine_y.min(fine_height - 1);

            let char_x = fine_x / 2;
            let char_y = fine_y / 4;
            let dot_x = fine_x % 2;
            let dot_y = fine_y % 4;

            let bit = match (dot_x, dot_y) {
                (0, 0) => 0x01,
                (0, 1) => 0x02,
                (0, 2) => 0x04,
                (0, 3) => 0x40,
                (1, 0) => 0x08,
                (1, 1) => 0x10,
                (1, 2) => 0x20,
                (1, 3) => 0x80,
                _ => unreachable!(),
            };

            patterns[char_y * width + char_x] |= bit;
        }

        for cy in 0..height {
            for cx in 0..width {
                let pattern = patterns[cy * width + cx];
                if pattern != 0 {
                    let ch = char::from_u32(0x2800 + pattern as u32).unwrap_or(' ');
                    buf[(area.x + cx as u16, area.y + cy as u16)]
                        .set_char(ch)
                        .set_fg(color);
                }
            }
        }
    });
}

fn render_vertical(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, gain: f32) {
    let width = area.width as usize;
    let height = area.height as usize;
    let fine_width = width * 2;
    let fine_height = height * 4;

    PATTERNS.with(|p| {
        let mut patterns = p.borrow_mut();
        let size = width * height;
        patterns.clear();
        patterns.resize(size, 0);

        for fine_y in 0..fine_height {
            let sample_idx = (fine_y * data.len()) / fine_height;
            let sample = (data.get(sample_idx).copied().unwrap_or(0.0) * gain).clamp(-1.0, 1.0);

            let fine_x = ((sample + 1.0) * 0.5 * (fine_width - 1) as f32).round() as usize;
            let fine_x = fine_x.min(fine_width - 1);

            let char_x = fine_x / 2;
            let char_y = fine_y / 4;
            let dot_x = fine_x % 2;
            let dot_y = fine_y % 4;

            let bit = match (dot_x, dot_y) {
                (0, 0) => 0x01,
                (0, 1) => 0x02,
                (0, 2) => 0x04,
                (0, 3) => 0x40,
                (1, 0) => 0x08,
                (1, 1) => 0x10,
                (1, 2) => 0x20,
                (1, 3) => 0x80,
                _ => unreachable!(),
            };

            patterns[char_y * width + char_x] |= bit;
        }

        for cy in 0..height {
            for cx in 0..width {
                let pattern = patterns[cy * width + cx];
                if pattern != 0 {
                    let ch = char::from_u32(0x2800 + pattern as u32).unwrap_or(' ');
                    buf[(area.x + cx as u16, area.y + cy as u16)]
                        .set_char(ch)
                        .set_fg(color);
                }
            }
        }
    });
}
