use crate::{
    asset_protocols::font::FontAsset,
    components::{
        material_instance::HaMaterialInstance, mesh_instance::HaMeshInstance,
        text_instance::HaTextInstance,
    },
    ha_renderer::HaRenderer,
    image::{ImageInstanceReference, ImageResourceMapping},
    material::{
        common::MaterialValue,
        domains::surface::{text::SurfaceTextFactory, SurfaceVertexText},
    },
    mesh::{Mesh, MeshId, MeshInstanceReference},
};
use core::{
    assets::{asset::AssetId, database::AssetsDatabase},
    ecs::{life_cycle::EntityChanges, Comp, Entity, Universe, WorldRef},
};
use std::collections::HashMap;

#[derive(Debug, Default)]
pub struct HaFontSystemCache {
    fonts_map: HashMap<String, AssetId>,
    fonts_table: HashMap<AssetId, String>,
    meshes: HashMap<Entity, MeshId>,
}

pub type HaFontSystemResources<'a> = (
    WorldRef,
    &'a mut HaRenderer,
    &'a AssetsDatabase,
    &'a EntityChanges,
    &'a ImageResourceMapping,
    &'a mut HaFontSystemCache,
    Comp<&'a mut HaTextInstance>,
    Comp<&'a mut HaMeshInstance>,
    Comp<&'a mut HaMaterialInstance>,
);

pub fn ha_font_system(universe: &mut Universe) {
    let (world, mut renderer, assets, changes, image_mapping, mut cache, ..) =
        universe.query_resources::<HaFontSystemResources>();

    for id in assets.lately_loaded_protocol("font") {
        if let Some(asset) = assets.asset_by_id(*id) {
            if asset.is::<FontAsset>() {
                cache.fonts_map.insert(asset.path().to_owned(), *id);
                cache.fonts_table.insert(*id, asset.path().to_owned());
            }
        }
    }
    for id in assets.lately_unloaded_protocol("font") {
        if let Some(name) = cache.fonts_table.remove(id) {
            cache.fonts_map.remove(&name);
        }
    }

    for entity in changes.despawned() {
        if let Some(id) = cache.meshes.remove(&entity) {
            let _ = renderer.remove_mesh(id);
        }
    }

    for (entity, (text, mesh, material)) in world
        .query::<(
            &mut HaTextInstance,
            &mut HaMeshInstance,
            &mut HaMaterialInstance,
        )>()
        .iter()
    {
        if text.dirty || !cache.meshes.contains_key(&entity) {
            mesh.reference = MeshInstanceReference::None;
            if let Some(id) = cache.fonts_map.get(text.font()) {
                if let Some(asset) = assets.asset_by_id(*id) {
                    if let Some(asset) = asset.get::<FontAsset>() {
                        if let Ok(factory) =
                            SurfaceTextFactory::factory::<SurfaceVertexText>(text, asset)
                        {
                            if let Some(id) = cache.meshes.get(&entity) {
                                if let Some(m) = renderer.mesh_mut(*id) {
                                    m.set_vertex_storage_all(text.change_frequency().into());
                                    m.set_index_storage(text.change_frequency().into());
                                    if factory.write_into(m).is_ok() {
                                        text.dirty = false;
                                        mesh.reference = MeshInstanceReference::Id(*id);
                                        set_material_sampler(material, asset, &image_mapping);
                                    }
                                }
                            } else {
                                let mut m = Mesh::new(factory.layout().to_owned());
                                m.set_vertex_storage_all(text.change_frequency().into());
                                m.set_index_storage(text.change_frequency().into());
                                if factory.write_into(&mut m).is_ok() {
                                    if let Ok(id) = renderer.add_mesh(m) {
                                        text.dirty = false;
                                        mesh.reference = MeshInstanceReference::Id(id);
                                        set_material_sampler(material, asset, &image_mapping);
                                        cache.meshes.insert(entity, id);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

fn set_material_sampler(
    material: &mut HaMaterialInstance,
    font: &FontAsset,
    image_mapping: &ImageResourceMapping,
) {
    if let Some((_, id)) = font.pages_image_assets.get(0) {
        if let Some(id) = image_mapping.resource_by_asset(*id) {
            material.values.insert(
                "mainImage".to_owned(),
                MaterialValue::Sampler2D(ImageInstanceReference::Id(id)),
            );
        }
    }
}
