use bytemuck::Pod;
use etagere::{euclid::default::Rect, size2, AtlasAllocator};
use fontdue::{
    layout::{GlyphPosition, GlyphRasterConfig, Layout},
    Font,
};
use spitfire_core::VertexStream;
use std::{
    collections::{hash_map::Entry, HashMap},
    marker::PhantomData,
};
pub trait TextVertex<UD: Copy> {
    fn apply(&mut self, position: [f32; 2], tex_coord: [f32; 3], user_data: UD);
}
#[derive(Debug, Default, Clone, Copy)]
pub struct TextRendererGlyph {
    pub page: usize,
    pub rectangle: Rect<u32>,
}
pub struct TextRendererUnpacked<UD: Copy> {
    pub glyphs: HashMap<GlyphRasterConfig, TextRendererGlyph>,
    pub atlas_size: [usize; 3],
    pub image: Vec<u8>,
    pub renderables: Vec<GlyphPosition<UD>>,
}
#[derive(Clone)]
pub struct TextRenderer<UD: Copy = ()> {
    pub renderables_resize: usize,
    used_glyphs: HashMap<GlyphRasterConfig, TextRendererGlyph>,
    atlas_size: [usize; 3],
    image: Vec<u8>,
    atlases: Vec<AtlasAllocator>,
    ready_to_render: Vec<GlyphPosition<UD>>,
    _phantom: PhantomData<fn() -> UD>,
}
impl<UD: Copy> Default for TextRenderer<UD> {
    fn default() -> Self {
        Self::new(1024, 1024)
    }
}
impl<UD: Copy> TextRenderer<UD> {
    pub fn new(width: usize, height: usize) -> Self {
        Self {
            renderables_resize: 1024,
            used_glyphs: Default::default(),
            atlas_size: [width, height, 0],
            image: Default::default(),
            atlases: Default::default(),
            ready_to_render: Default::default(),
            _phantom: Default::default(),
        }
    }
    pub fn clear(&mut self) {
        self.used_glyphs.clear();
        self.atlas_size[2] = 0;
        self.image.clear();
        self.atlases.clear();
        self.ready_to_render.clear();
    }
    pub fn include(&mut self, fonts: &[Font], layout: &Layout<UD>) {
        for glyph in layout.glyphs() {
            if glyph.char_data.rasterize() {
                if self.ready_to_render.len() == self.ready_to_render.capacity() {
                    self.ready_to_render.reserve(self.renderables_resize);
                }
                self.ready_to_render.push(*glyph);
            }
            if let Entry::Vacant(entry) = self.used_glyphs.entry(glyph.key) {
                let font = &fonts[glyph.font_index];
                let (metrics, coverage) = font.rasterize_config(glyph.key);
                if glyph.char_data.rasterize() {
                    let allocation = self
                        .atlases
                        .iter_mut()
                        .enumerate()
                        .find_map(|(page, atlas)| {
                            Some((
                                page,
                                atlas
                                    .allocate(size2(
                                        metrics.width as i32 + 1,
                                        metrics.height as i32 + 1,
                                    ))?
                                    .rectangle
                                    .to_rect()
                                    .origin
                                    .to_u32(),
                            ))
                        })
                        .or_else(|| {
                            let w = self.atlas_size[0];
                            let h = self.atlas_size[1];
                            let mut atlas = AtlasAllocator::new(size2(w as _, h as _));
                            let page = self.atlases.len();
                            let origin = atlas
                                .allocate(size2(
                                    metrics.width as i32 + 1,
                                    metrics.height as i32 + 1,
                                ))?
                                .rectangle
                                .to_rect()
                                .origin
                                .to_u32();
                            self.atlases.push(atlas);
                            self.atlas_size[2] += 1;
                            let [w, h, d] = self.atlas_size;
                            self.image.resize(w * h * d, 0);
                            Some((page, origin))
                        });
                    if let Some((page, origin)) = allocation {
                        let [w, h, _] = self.atlas_size;
                        for (index, value) in coverage.iter().enumerate() {
                            let x = origin.x as usize + index % metrics.width;
                            let y = origin.y as usize + index / metrics.width;
                            let index = page * w * h + y * w + x;
                            self.image[index] = *value;
                        }
                        entry.insert(TextRendererGlyph {
                            page,
                            rectangle: Rect::new(
                                origin,
                                [metrics.width as _, metrics.height as _].into(),
                            ),
                        });
                    }
                }
            }
        }
    }
    pub fn include_consumed(
        &mut self,
        fonts: &[Font],
        layout: &Layout<UD>,
    ) -> impl Iterator<Item = (GlyphPosition<UD>, TextRendererGlyph)> + '_ {
        self.include(fonts, layout);
        self.consume_renderables()
    }
    pub fn glyph(&self, key: &GlyphRasterConfig) -> Option<TextRendererGlyph> {
        self.used_glyphs.get(key).copied()
    }
    pub fn consume_renderables(
        &mut self,
    ) -> impl Iterator<Item = (GlyphPosition<UD>, TextRendererGlyph)> + '_ {
        self.ready_to_render
            .drain(..)
            .filter_map(|glyph| Some((glyph, *self.used_glyphs.get(&glyph.key)?)))
    }
    pub fn image(&self) -> &[u8] {
        &self.image
    }
    pub fn atlas_size(&self) -> [usize; 3] {
        self.atlas_size
    }
    pub fn into_image(self) -> (Vec<u8>, [usize; 3]) {
        (self.image, self.atlas_size)
    }
    pub fn into_inner(self) -> TextRendererUnpacked<UD> {
        TextRendererUnpacked {
            glyphs: self.used_glyphs,
            atlas_size: self.atlas_size,
            image: self.image,
            renderables: self.ready_to_render,
        }
    }
    pub fn render_to_stream<V, B>(&mut self, stream: &mut VertexStream<V, B>)
    where
        V: TextVertex<UD> + Pod + Default,
    {
        let [w, h, _] = self.atlas_size;
        let w = w as f32;
        let h = h as f32;
        for glyph in self.ready_to_render.drain(..) {
            if let Some(data) = self.used_glyphs.get(&glyph.key) {
                let mut a = V::default();
                let mut b = V::default();
                let mut c = V::default();
                let mut d = V::default();
                a.apply(
                    [glyph.x, glyph.y],
                    [
                        data.rectangle.min_x() as f32 / w,
                        data.rectangle.min_y() as f32 / h,
                        data.page as f32,
                    ],
                    glyph.user_data,
                );
                b.apply(
                    [glyph.x + glyph.width as f32, glyph.y],
                    [
                        data.rectangle.max_x() as f32 / w,
                        data.rectangle.min_y() as f32 / h,
                        data.page as f32,
                    ],
                    glyph.user_data,
                );
                c.apply(
                    [glyph.x + glyph.width as f32, glyph.y + glyph.height as f32],
                    [
                        data.rectangle.max_x() as f32 / w,
                        data.rectangle.max_y() as f32 / h,
                        data.page as f32,
                    ],
                    glyph.user_data,
                );
                d.apply(
                    [glyph.x, glyph.y + glyph.height as f32],
                    [
                        data.rectangle.min_x() as f32 / w,
                        data.rectangle.max_y() as f32 / h,
                        data.page as f32,
                    ],
                    glyph.user_data,
                );
                stream.quad([a, b, c, d]);
            }
        }
    }
}
#[cfg(test)]
mod tests {
    use crate::TextRenderer;
    use fontdue::{
        layout::{CoordinateSystem, Layout, TextStyle},
        Font,
    };
    use image::RgbImage;
    #[test]
    fn test_text_renderer() {
        let text = include_str!("../../../resources/text.txt");
        let font = include_bytes!("../../../resources/Roboto-Regular.ttf") as &[_];
        let font = Font::from_bytes(font, Default::default()).unwrap();
        let fonts = [font];
        let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
        let mut renderer = TextRenderer::new(256, 256);
        for line in text.lines() {
            layout.append(&fonts, &TextStyle::new(line, 32.0, 0));
        }
        renderer.include(&fonts, &layout);
        let (image, [width, height, _]) = renderer.into_image();
        let image = RgbImage::from_vec(
            width as _,
            height as _,
            image
                .into_iter()
                .flat_map(|value| [value, value, value])
                .collect(),
        )
        .unwrap();
        image.save("../../resources/test.png").unwrap();
    }
}