// GPU text rendering via bitmap font atlas and sprite batching.
// ASCII-only (32..127). Each glyph is a fixed-width cell in a 16x6 atlas.
// Text is submitted as TextQuads, rasterized to SpriteInstances, then drawn
// as a single instanced sprite batch.

use crate::sprite::SpriteInstance;

/// Style descriptor for GPU text rendering.
#[derive(Debug, Clone)]
pub struct TextStyle {
    pub font_size: f32,
    pub color: [f32; 4],
    pub alignment: TextAlignment,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TextAlignment {
    #[default]
    Left,
    Center,
    Right,
}

/// A positioned text quad for batched GPU text rendering.
#[derive(Debug, Clone)]
pub struct TextQuad {
    pub text: String,
    pub position: [f32; 2],
    pub style: TextStyle,
}

/// Bitmap font atlas layout. 16 columns x 6 rows, ASCII 32..127.
pub struct BitmapFontAtlas {
    /// Glyph cell width in pixels.
    pub cell_width: u32,
    /// Glyph cell height in pixels.
    pub cell_height: u32,
    /// Atlas columns (16 for ASCII layout).
    pub columns: u32,
    /// Atlas rows (6 for ASCII 32..127).
    pub rows: u32,
}

impl Default for BitmapFontAtlas {
    fn default() -> Self {
        Self {
            cell_width: 8,
            cell_height: 16,
            columns: 16,
            rows: 6,
        }
    }
}

impl BitmapFontAtlas {
    /// Atlas texture dimensions.
    pub fn atlas_size(&self) -> (u32, u32) {
        (self.columns * self.cell_width, self.rows * self.cell_height)
    }

    /// Validate atlas dimensions. Returns error if zero-sized.
    pub fn validate(&self) -> Result<(), &'static str> {
        if self.columns == 0 || self.rows == 0 || self.cell_width == 0 || self.cell_height == 0 {
            return Err("bitmap_font_atlas:zero dimension");
        }
        Ok(())
    }

    /// UV rect [u, v, w, h] for an ASCII character.
    /// Returns `[0,0,0,0]` if atlas dimensions are zero (invalid).
    pub fn glyph_uv(&self, ch: char) -> [f32; 4] {
        let (aw, ah) = self.atlas_size();
        if aw == 0 || ah == 0 {
            return [0.0; 4];
        }
        let code = ch as u32;
        if code < 32 || code > 127 {
            return self.glyph_uv('?');
        }
        let idx = code - 32;
        let col = idx % self.columns;
        let row = idx / self.columns;
        [
            (col * self.cell_width) as f32 / aw as f32,
            (row * self.cell_height) as f32 / ah as f32,
            self.cell_width as f32 / aw as f32,
            self.cell_height as f32 / ah as f32,
        ]
    }

    /// Generate a white-on-black atlas texture (CPU-side). 1 byte per pixel (R8).
    /// Each glyph is rendered as a simple block pattern for the character.
    /// In production, replace with a loaded font atlas PNG.
    pub fn generate_placeholder(&self) -> Vec<u8> {
        let (w, h) = self.atlas_size();
        let mut data = vec![0u8; (w * h) as usize];
        // Fill each cell with a simple pattern: border + center dot.
        for idx in 0..96u32 {
            let col = idx % self.columns;
            let row = idx / self.columns;
            let bx = col * self.cell_width;
            let by = row * self.cell_height;
            // Draw border.
            for x in bx..bx + self.cell_width {
                data[(by * w + x) as usize] = 128;
                data[((by + self.cell_height - 1) * w + x) as usize] = 128;
            }
            for y in by..by + self.cell_height {
                data[(y * w + bx) as usize] = 128;
                data[(y * w + bx + self.cell_width - 1) as usize] = 128;
            }
            // Center fill based on character density.
            let density = ((idx % 10) as u8 + 1) * 25;
            let cx = bx + self.cell_width / 2;
            let cy = by + self.cell_height / 2;
            for dy in 0..self.cell_height / 2 {
                for dx in 0..self.cell_width / 2 {
                    let x = cx - self.cell_width / 4 + dx;
                    let y = cy - self.cell_height / 4 + dy;
                    if x < w && y < h {
                        data[(y * w + x) as usize] = density;
                    }
                }
            }
        }
        data
    }
}

/// Batched GPU text renderer. Collects TextQuads and rasterizes to SpriteInstances.
pub struct TextRenderer {
    quads: Vec<TextQuad>,
    atlas: BitmapFontAtlas,
}

impl TextRenderer {
    pub fn new() -> Self {
        Self {
            quads: Vec::new(),
            atlas: BitmapFontAtlas::default(),
        }
    }

    /// Set a custom atlas layout.
    pub fn set_atlas(&mut self, atlas: BitmapFontAtlas) {
        self.atlas = atlas;
    }

    pub fn push(&mut self, quad: TextQuad) {
        self.quads.push(quad);
    }

    pub fn clear(&mut self) {
        self.quads.clear();
    }

    pub fn quads(&self) -> &[TextQuad] {
        &self.quads
    }

    pub fn quad_count(&self) -> usize {
        self.quads.len()
    }

    /// Rasterize all queued TextQuads into SpriteInstances.
    /// Each character becomes one sprite instance.
    /// Appends to `out` to allow caller allocation reuse across frames.
    /// `screen_width` and `screen_height` are used for alignment calculations.
    pub fn rasterize_into(&self, _screen_width: f32, _screen_height: f32, out: &mut Vec<SpriteInstance>) {
        out.clear();
        for quad in &self.quads {
            let scale = quad.style.font_size / self.atlas.cell_height as f32;
            let char_width = self.atlas.cell_width as f32 * scale;
            let text_width = quad.text.len() as f32 * char_width;

            let start_x = match quad.style.alignment {
                TextAlignment::Left => quad.position[0],
                TextAlignment::Center => quad.position[0] - text_width * 0.5,
                TextAlignment::Right => quad.position[0] - text_width,
            };

            for (i, ch) in quad.text.chars().enumerate() {
                if ch == ' ' {
                    continue; // Skip spaces.
                }
                let uv = self.atlas.glyph_uv(ch);
                out.push(SpriteInstance {
                    position: [start_x + i as f32 * char_width, quad.position[1], 0.0],
                    size: quad.style.font_size,
                    uv_rect: uv,
                    color: quad.style.color,
                });
            }
        }
    }

    /// Convenience wrapper that allocates a new Vec. Prefer `rasterize_into()`
    /// in frame loops to avoid per-frame allocation.
    pub fn rasterize(&self, screen_width: f32, screen_height: f32) -> Vec<SpriteInstance> {
        let mut out = Vec::new();
        self.rasterize_into(screen_width, screen_height, &mut out);
        out
    }
}

impl Default for TextRenderer {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn text_renderer_push_and_clear() {
        let mut r = TextRenderer::new();
        assert_eq!(r.quad_count(), 0);
        r.push(TextQuad {
            text: "hello".into(),
            position: [0.0, 0.0],
            style: TextStyle {
                font_size: 16.0,
                color: [1.0; 4],
                alignment: TextAlignment::Left,
            },
        });
        assert_eq!(r.quad_count(), 1);
        r.clear();
        assert_eq!(r.quad_count(), 0);
    }

    #[test]
    fn text_alignment_default() {
        assert_eq!(TextAlignment::default(), TextAlignment::Left);
    }

    #[test]
    fn text_renderer_default() {
        let r = TextRenderer::default();
        assert!(r.quads().is_empty());
    }

    #[test]
    fn bitmap_font_atlas_size() {
        let atlas = BitmapFontAtlas::default();
        assert_eq!(atlas.atlas_size(), (128, 96));
    }

    #[test]
    fn glyph_uv_space() {
        let atlas = BitmapFontAtlas::default();
        let uv = atlas.glyph_uv(' ');
        assert_eq!(uv[0], 0.0);
        assert_eq!(uv[1], 0.0);
    }

    #[test]
    fn glyph_uv_tilde() {
        let atlas = BitmapFontAtlas::default();
        let uv = atlas.glyph_uv('~');
        // '~' = 126, idx = 94, col = 94 % 16 = 14, row = 94 / 16 = 5
        let (aw, ah) = atlas.atlas_size();
        let expected_u = (14 * 8) as f32 / aw as f32;
        let expected_v = (5 * 16) as f32 / ah as f32;
        assert!((uv[0] - expected_u).abs() < 0.001);
        assert!((uv[1] - expected_v).abs() < 0.001);
    }

    #[test]
    fn glyph_uv_out_of_range_fallback() {
        let atlas = BitmapFontAtlas::default();
        let uv_ctrl = atlas.glyph_uv('\x01');
        let uv_q = atlas.glyph_uv('?');
        assert_eq!(uv_ctrl, uv_q);
    }

    #[test]
    fn rasterize_produces_instances() {
        let mut r = TextRenderer::new();
        r.push(TextQuad {
            text: "AB".into(),
            position: [0.0, 0.0],
            style: TextStyle {
                font_size: 16.0,
                color: [1.0; 4],
                alignment: TextAlignment::Left,
            },
        });
        let instances = r.rasterize(800.0, 600.0);
        assert_eq!(instances.len(), 2); // 2 characters, no spaces
    }

    #[test]
    fn rasterize_skips_spaces() {
        let mut r = TextRenderer::new();
        r.push(TextQuad {
            text: "A B".into(),
            position: [0.0, 0.0],
            style: TextStyle {
                font_size: 16.0,
                color: [1.0; 4],
                alignment: TextAlignment::Left,
            },
        });
        let instances = r.rasterize(800.0, 600.0);
        assert_eq!(instances.len(), 2); // space skipped
    }

    #[test]
    fn placeholder_atlas_size() {
        let atlas = BitmapFontAtlas::default();
        let data = atlas.generate_placeholder();
        let (w, h) = atlas.atlas_size();
        assert_eq!(data.len(), (w * h) as usize);
    }
}
