//! Software rendering canvas backed by ltk's `Canvas`.
//!
//! Lets forge's overlay UIs (OSD, app switcher panel) compose into a single
//! RGBA buffer and upload it as one GL texture instead of stacking dozens of
//! individual textures. Text goes through ltk's shaping and font fallback, so
//! the chrome renders the same glyphs the rest of the desktop does.

use ltk::Canvas as LtkCanvas;
use ltk::{Corners, Rect};

/// RGBA color as four floats in 0..1.
#[derive(Clone, Copy)]
pub struct Color {
    pub r: f32,
    pub g: f32,
    pub b: f32,
    pub a: f32,
}

impl Color {
    pub const fn rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
        Self {
            r: r as f32 / 255.0,
            g: g as f32 / 255.0,
            b: b as f32 / 255.0,
            a: a as f32 / 255.0,
        }
    }

    fn to_ltk(self) -> ltk::Color {
        ltk::Color { r: self.r, g: self.g, b: self.b, a: self.a }
    }
}

/// Software rendering surface.
pub struct SoftCanvas {
    canvas: LtkCanvas,
}

impl SoftCanvas {
    pub fn new(width: u32, height: u32) -> Self {
        Self { canvas: LtkCanvas::new(width.max(1), height.max(1)) }
    }

    /// Resize the pixel buffer. Keeps the glyph cache.
    pub fn resize(&mut self, width: u32, height: u32) {
        self.canvas.resize(width.max(1), height.max(1));
    }

    pub fn width(&self) -> u32 {
        self.canvas.size().0
    }

    pub fn height(&self) -> u32 {
        self.canvas.size().1
    }

    /// Clear to fully transparent.
    pub fn clear(&mut self) {
        self.canvas.clear();
    }

    /// Fill the entire canvas with a solid color.
    pub fn fill(&mut self, color: Color) {
        self.canvas.fill(color.to_ltk());
    }

    /// Fill a rectangle with optional corner radius.
    pub fn fill_rect(&mut self, x: f32, y: f32, w: f32, h: f32, color: Color, radius: f32) {
        self.canvas.fill_rect(
            Rect { x, y, width: w, height: h },
            color.to_ltk(),
            Corners::all(radius),
        );
    }

    /// Render text at (x, y) where y is the baseline. `size` is physical
    /// pixels — see `crate::text::text_px`.
    pub fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: Color) {
        self.canvas.draw_text(text, x, y, size, color.to_ltk());
    }

    /// Measure text advance width.
    pub fn measure_text(&self, text: &str, size: f32) -> f32 {
        self.canvas.measure_text(text, size)
    }

    /// Measure text height from the font metrics at `size`.
    pub fn measure_text_height(&self, size: f32) -> f32 {
        self.canvas.font_metrics('M', size).height as f32
    }

    /// Blit raw straight-alpha RGBA image data at (dest_x, dest_y), unscaled.
    pub fn draw_image_rgba(&mut self, rgba: &[u8], img_w: u32, img_h: u32, dest_x: i32, dest_y: i32) {
        self.canvas.draw_image_data(
            rgba,
            img_w,
            img_h,
            Rect {
                x: dest_x as f32,
                y: dest_y as f32,
                width: img_w as f32,
                height: img_h as f32,
            },
            1.0,
        );
    }

    /// Raw premultiplied RGBA pixels.
    pub fn data(&self) -> &[u8] {
        match &self.canvas {
            LtkCanvas::Software(sc) => sc.pixmap.data(),
            _ => &[],
        }
    }

    /// Zero the alpha of pixels that fall in the corner regions outside
    /// the rounded-rect of `radius_px`. Used to clip a freshly composed
    /// image into the same rounded silhouette the panel background uses.
    pub fn mask_outside_rounded_rect(&mut self, radius_px: f32) {
        if radius_px <= 0.5 {
            return;
        }
        let LtkCanvas::Software(sc) = &mut self.canvas else { return };
        let w = sc.pixmap.width() as i32;
        let h = sc.pixmap.height() as i32;
        let r = radius_px;
        let r_sq = r * r;
        let r_int = r.ceil() as i32;
        let pixels = sc.pixmap.data_mut();
        let corners: [(f32, f32, i32, i32); 4] = [
            ( r,                r,                -1, -1 ),
            ( w as f32 - r,     r,                 1, -1 ),
            ( r,                h as f32 - r,     -1,  1 ),
            ( w as f32 - r,     h as f32 - r,      1,  1 ),
        ];
        for (cx, cy, sx, sy) in corners {
            for dy in 0..r_int {
                for dx in 0..r_int {
                    let px = cx as i32 + sx * dx;
                    let py = cy as i32 + sy * dy;
                    if px < 0 || px >= w || py < 0 || py >= h { continue; }
                    let fx = px as f32 + 0.5 - cx;
                    let fy = py as f32 + 0.5 - cy;
                    if fx * fx + fy * fy > r_sq {
                        let idx = (py as usize * w as usize + px as usize) * 4;
                        if idx + 3 < pixels.len() {
                            pixels[idx]     = 0;
                            pixels[idx + 1] = 0;
                            pixels[idx + 2] = 0;
                            pixels[idx + 3] = 0;
                        }
                    }
                }
            }
        }
    }
}
