use std::collections::HashMap;
use std::path::Path;

use fontdue::layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle};
use fontdue::{Font, FontSettings};

const FALLBACK_FONT_PATHS: &[&str] = &[
    "/usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf",
    "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
    "/usr/share/fonts/TTF/DejaVuSans.ttf",
    "/usr/share/fonts/dejavu/DejaVuSans.ttf",
    "/usr/share/fonts/liberation-sans/LiberationSans-Regular.ttf",
    "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
    "/usr/share/fonts/google-noto/NotoSans-Regular.ttf",
    "/usr/share/fonts/noto/NotoSans-Regular.ttf",
    "/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf",
    "/usr/share/fonts/google-droid-sans-fonts/DroidSans.ttf",
];

/// Cached rasterized glyph data.
struct CachedGlyph {
    bitmap: Vec<u8>,
    width: u32,
    height: u32,
}

pub struct TextRenderer {
    font: Font,
    cache: HashMap<(char, u16), CachedGlyph>,
}

impl TextRenderer {
    pub fn new() -> Self {
        let font = Self::load_font();
        Self { font, cache: HashMap::new() }
    }

    fn load_font() -> Font {
        for path in FALLBACK_FONT_PATHS {
            if Path::new(path).exists() {
                if let Ok(data) = std::fs::read(path) {
                    if let Ok(font) = Font::from_bytes(data, FontSettings::default()) {
                        return font;
                    }
                }
            }
        }

        // Last resort: try to find any TTF file
        for dir in &["/usr/share/fonts"] {
            if let Ok(font) = Self::find_any_font(Path::new(dir)) {
                return font;
            }
        }

        panic!("No usable font found on the system");
    }

    fn find_any_font(dir: &Path) -> Result<Font, ()> {
        let entries = std::fs::read_dir(dir).map_err(|_| ())?;
        for entry in entries.flatten() {
            let path = entry.path();
            if path.is_dir() {
                if let Ok(font) = Self::find_any_font(&path) {
                    return Ok(font);
                }
            } else if path.extension().and_then(|e| e.to_str()) == Some("ttf") {
                if let Ok(data) = std::fs::read(&path) {
                    if let Ok(font) = Font::from_bytes(data, FontSettings::default()) {
                        return Ok(font);
                    }
                }
            }
        }
        Err(())
    }

    /// Render a single line of text into an RGBA buffer.
    /// Returns (buffer, width, height).
    pub fn render_line(&mut self, text: &str, font_size: f32, color: [u8; 4]) -> (Vec<u8>, u32, u32) {
        if text.is_empty() {
            return (Vec::new(), 0, 0);
        }

        let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
        layout.reset(&LayoutSettings::default());
        layout.append(&[&self.font], &TextStyle::new(text, font_size, 0));

        let glyphs = layout.glyphs();
        if glyphs.is_empty() {
            return (Vec::new(), 0, 0);
        }

        let height = layout.height().ceil() as u32;
        let width = glyphs
            .iter()
            .map(|g| (g.x + g.width as f32).ceil() as u32)
            .max()
            .unwrap_or(0);

        if width == 0 || height == 0 {
            return (Vec::new(), 0, 0);
        }

        let mut buffer = vec![0u8; (width * height * 4) as usize];

        for glyph in glyphs {
            let key = (glyph.parent, (font_size * 10.0) as u16);
            let cached = self.cache.entry(key).or_insert_with(|| {
                let (metrics, bitmap) = self.font.rasterize(glyph.parent, font_size);
                CachedGlyph {
                    bitmap,
                    width: metrics.width as u32,
                    height: metrics.height as u32,
                }
            });

            let gx = glyph.x as i32;
            let gy = glyph.y as i32;

            for row in 0..cached.height {
                for col in 0..cached.width {
                    let px = gx + col as i32;
                    let py = gy + row as i32;

                    if px < 0 || py < 0 || px >= width as i32 || py >= height as i32 {
                        continue;
                    }

                    let alpha = cached.bitmap[(row * cached.width + col) as usize];
                    if alpha == 0 {
                        continue;
                    }

                    let offset = ((py as u32 * width + px as u32) * 4) as usize;
                    let a = alpha as f32 / 255.0;

                    // Porter-Duff "over" in premultiplied-alpha space.
                    // The buffer stores premultiplied values throughout so
                    // smithay's GL blend (ONE, ONE_MINUS_SRC_ALPHA) renders
                    // correctly without dark halos around glyph edges.
                    let inv = 1.0 - a;
                    buffer[offset]     = (color[0] as f32 * a + buffer[offset]     as f32 * inv).min(255.0) as u8;
                    buffer[offset + 1] = (color[1] as f32 * a + buffer[offset + 1] as f32 * inv).min(255.0) as u8;
                    buffer[offset + 2] = (color[2] as f32 * a + buffer[offset + 2] as f32 * inv).min(255.0) as u8;
                    let dst_a = buffer[offset + 3] as f32 / 255.0;
                    buffer[offset + 3] = ((a + dst_a * inv) * 255.0).min(255.0) as u8;
                }
            }
        }

        (buffer, width, height)
    }
}
