pub mod app;
pub(crate) mod asset_manager;
pub(crate) mod interactions;

use crate::asset_manager::AssetsManager;
use bytemuck::{Pod, Zeroable};
use raui_core::widget::utils::Color;
use raui_tesselate_renderer::{TesselateBatch, TesselateBatchConverter, TesselateVertex};
use spitfire_fontdue::TextVertex;
use spitfire_glow::{
    graphics::Texture,
    prelude::{GraphicsBatch, Shader},
    renderer::{
        GlowBlending, GlowTextureFiltering, GlowUniformValue, GlowVertexAttrib, GlowVertexAttribs,
    },
};
use vek::Rect;

pub use glutin::{event, window};

pub mod prelude {
    pub use crate::{
        app::*,
        app::{declarative::*, immediate::*, retained::*},
        event::*,
        window::*,
    };
}

#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Vertex {
    pub position: [f32; 2],
    pub uv: [f32; 3],
    pub color: [f32; 4],
}

impl Default for Vertex {
    fn default() -> Self {
        Self {
            position: Default::default(),
            uv: Default::default(),
            color: [1.0, 1.0, 1.0, 1.0],
        }
    }
}

impl GlowVertexAttribs for Vertex {
    const ATTRIBS: &'static [(&'static str, GlowVertexAttrib)] = &[
        (
            "a_position",
            GlowVertexAttrib::Float {
                channels: 2,
                normalized: false,
            },
        ),
        (
            "a_uv",
            GlowVertexAttrib::Float {
                channels: 3,
                normalized: false,
            },
        ),
        (
            "a_color",
            GlowVertexAttrib::Float {
                channels: 4,
                normalized: false,
            },
        ),
    ];
}

impl TesselateVertex for Vertex {
    fn apply(&mut self, position: [f32; 2], tex_coord: [f32; 3], color: [f32; 4]) {
        self.position = position;
        self.uv = tex_coord;
        self.color = color;
    }

    fn transform(&mut self, matrix: vek::Mat4<f32>) {
        let result = matrix.mul_point(vek::Vec3 {
            x: self.position[0],
            y: self.position[1],
            z: 0.0,
        });
        self.position[0] = result.x;
        self.position[1] = result.y;
    }
}

impl TextVertex<Color> for Vertex {
    fn apply(&mut self, position: [f32; 2], tex_coord: [f32; 3], color: Color) {
        self.position = position;
        self.uv = tex_coord;
        self.color = [color.r, color.g, color.b, color.a];
    }
}

pub(crate) struct TesselateToGraphics<'a> {
    colored_shader: &'a Shader,
    textured_shader: &'a Shader,
    text_shader: &'a Shader,
    glyphs_texture: &'a Texture,
    missing_texture: &'a Texture,
    assets: &'a AssetsManager,
    clip_stack: Vec<Rect<i32, i32>>,
    viewport_height: i32,
    projection_view_matrix: [f32; 16],
}

impl<'a> TesselateBatchConverter<GraphicsBatch> for TesselateToGraphics<'a> {
    fn convert(&mut self, batch: TesselateBatch) -> Option<GraphicsBatch> {
        match batch {
            TesselateBatch::Color => Some(GraphicsBatch {
                shader: Some(self.colored_shader.clone()),
                blending: GlowBlending::Alpha,
                scissor: self.clip_stack.last().copied(),
                ..Default::default()
            }),
            TesselateBatch::Image { id } => {
                let id = AssetsManager::parse_image_id(&id).0;
                Some(GraphicsBatch {
                    shader: Some(self.textured_shader.clone()),
                    textures: vec![(
                        self.assets
                            .textures
                            .get(id)
                            .map(|texture| texture.texture.clone())
                            .unwrap_or_else(|| self.missing_texture.clone()),
                        GlowTextureFiltering::Linear,
                    )],
                    blending: GlowBlending::Alpha,
                    scissor: self.clip_stack.last().copied(),
                    ..Default::default()
                })
            }
            TesselateBatch::Text => Some(GraphicsBatch {
                shader: Some(self.text_shader.clone()),
                textures: vec![(self.glyphs_texture.clone(), GlowTextureFiltering::Linear)],
                blending: GlowBlending::Alpha,
                scissor: self.clip_stack.last().copied(),
                ..Default::default()
            }),
            TesselateBatch::Procedural {
                id,
                images,
                parameters,
            } => Some(GraphicsBatch {
                shader: self
                    .assets
                    .shaders
                    .get(&id)
                    .map(|shader| shader.shader.clone()),
                uniforms: parameters
                    .into_iter()
                    .map(|(k, v)| (k.into(), GlowUniformValue::F1(v)))
                    .chain((0..images.len()).map(|index| {
                        (
                            if index > 0 {
                                format!("u_image{}", index).into()
                            } else {
                                "u_image".into()
                            },
                            GlowUniformValue::I1(index as _),
                        )
                    }))
                    .chain(std::iter::once((
                        "u_projection_view".into(),
                        GlowUniformValue::M4(self.projection_view_matrix),
                    )))
                    .collect(),
                textures: images
                    .into_iter()
                    .filter_map(|id| {
                        Some((
                            self.assets.textures.get(&id)?.texture.to_owned(),
                            GlowTextureFiltering::Linear,
                        ))
                    })
                    .collect(),
                scissor: self.clip_stack.last().copied(),
                ..Default::default()
            }),
            TesselateBatch::ClipPush { x, y, w, h } => {
                self.clip_stack.push(vek::Rect {
                    x: x as _,
                    y: self.viewport_height - y as i32 - h as i32,
                    w: w as _,
                    h: h as _,
                });
                None
            }
            TesselateBatch::ClipPop => {
                self.clip_stack.pop();
                None
            }
        }
    }
}
