use super::*;

#[allow(clippy::too_many_arguments)]
pub(super) fn draw_braille_dataset(
    dataset: &Dataset,
    x_min: f64,
    x_max: f64,
    y_min: f64,
    y_max: f64,
    plot_chars: &mut [char],
    plot_styles: &mut [Style],
    cols: usize,
    rows: usize,
) {
    if dataset.data.is_empty() || cols == 0 || rows == 0 {
        return;
    }

    let px_w = cols * 2;
    let px_h = rows * 4;
    let mut bits: Vec<u32> = vec![0u32; cols * rows];
    let mut color_map: Vec<Option<Color>> = vec![None; cols * rows];

    let mut set_dot_colored = |px: usize, py: usize, color: Color| {
        set_braille_dot(px, py, &mut bits, cols, rows);
        let char_col = px / 2;
        let char_row = py / 4;
        if char_col < cols && char_row < rows {
            color_map[char_row * cols + char_col] = Some(color);
        }
    };

    let points = dataset
        .data
        .iter()
        .filter(|(x, y)| x.is_finite() && y.is_finite())
        .map(|(x, y)| {
            (
                map_value_to_cell(*x, x_min, x_max, px_w, false),
                map_value_to_cell(*y, y_min, y_max, px_h, true),
                *y,
            )
        })
        .collect::<Vec<_>>();

    if points.is_empty() {
        return;
    }

    if matches!(dataset.graph_type, GraphType::Line | GraphType::Area) {
        let mut line_y_by_x = if matches!(dataset.graph_type, GraphType::Area) {
            vec![None::<usize>; px_w]
        } else {
            Vec::new()
        };
        let mut line_color_by_x = if matches!(dataset.graph_type, GraphType::Area) {
            vec![None::<Color>; px_w]
        } else {
            Vec::new()
        };

        for idx in 0..points.len().saturating_sub(1) {
            let a = points[idx];
            let b = points[idx + 1];
            let seg_color = if let (Some(up), Some(down)) = (dataset.up_color, dataset.down_color) {
                if b.2 > a.2 {
                    up
                } else {
                    down
                }
            } else {
                dataset.color
            };

            plot_bresenham(
                a.0 as isize,
                a.1 as isize,
                b.0 as isize,
                b.1 as isize,
                |x, y| {
                    if x < 0 || y < 0 {
                        return;
                    }
                    let px = x as usize;
                    let py = y as usize;
                    set_dot_colored(px, py, seg_color);
                    if matches!(dataset.graph_type, GraphType::Area) && px < px_w && py < px_h {
                        line_y_by_x[px] = Some(match line_y_by_x[px] {
                            Some(existing) => existing.min(py),
                            None => py,
                        });
                        line_color_by_x[px] = Some(seg_color);
                    }
                },
            );
        }

        if matches!(dataset.graph_type, GraphType::Area) {
            for px in 0..px_w {
                if let Some(line_y) = line_y_by_x[px] {
                    let fill_color = line_color_by_x[px].unwrap_or(dataset.color);
                    for py in line_y..px_h {
                        set_dot_colored(px, py, fill_color);
                    }
                }
            }
        }
    } else {
        for (x, y, _) in &points {
            set_dot_colored(*x, *y, dataset.color);
        }
    }

    for row in 0..rows {
        for col in 0..cols {
            let idx = row * cols + col;
            if bits[idx] != 0 {
                let ch = char::from_u32(BRAILLE_BASE + bits[idx]).unwrap_or(' ');
                plot_chars[idx] = ch;
                let color = color_map[idx].unwrap_or(dataset.color);
                plot_styles[idx] = Style::new().fg(color);
            }
        }
    }

    if !matches!(dataset.marker, Marker::Braille) {
        let m = marker_char(dataset.marker);
        for (x, y) in dataset
            .data
            .iter()
            .filter(|(x, y)| x.is_finite() && y.is_finite())
        {
            let col = map_value_to_cell(*x, x_min, x_max, cols, false);
            let row = map_value_to_cell(*y, y_min, y_max, rows, true);
            if row < rows && col < cols {
                let idx = row * cols + col;
                plot_chars[idx] = m;
                plot_styles[idx] = Style::new().fg(dataset.color);
            }
        }
    }
}

fn set_braille_dot(px: usize, py: usize, bits: &mut [u32], cols: usize, rows: usize) {
    if cols == 0 || rows == 0 {
        return;
    }
    let char_col = px / 2;
    let char_row = py / 4;
    if char_col >= cols || char_row >= rows {
        return;
    }
    let sub_col = px % 2;
    let sub_row = py % 4;
    bits[char_row * cols + char_col] |= if sub_col == 0 {
        BRAILLE_LEFT_BITS[sub_row]
    } else {
        BRAILLE_RIGHT_BITS[sub_row]
    };
}

fn plot_bresenham(x0: isize, y0: isize, x1: isize, y1: isize, mut plot: impl FnMut(isize, isize)) {
    let mut x = x0;
    let mut y = y0;
    let dx = (x1 - x0).abs();
    let sx = if x0 < x1 { 1 } else { -1 };
    let dy = -(y1 - y0).abs();
    let sy = if y0 < y1 { 1 } else { -1 };
    let mut err = dx + dy;

    loop {
        plot(x, y);
        if x == x1 && y == y1 {
            break;
        }
        let e2 = 2 * err;
        if e2 >= dy {
            err += dy;
            x += sx;
        }
        if e2 <= dx {
            err += dx;
            y += sy;
        }
    }
}
