use crate::{
    components::{
        material_instance::HaMaterialInstance, mesh_instance::HaMeshInstance,
        tilemap_instance::HaTileMapInstance,
    },
    ha_renderer::HaRenderer,
    image::{ImageId, ImageInstanceReference, VirtualImageSource},
    material::{
        common::MaterialValue,
        domains::surface::{tilemap::SurfaceTileMapFactory, SurfaceVertexPT},
    },
    mesh::{Mesh, MeshId, MeshInstanceReference},
};
use core::ecs::{life_cycle::EntityChanges, Comp, Entity, Universe, WorldRef};
use std::collections::HashMap;

#[derive(Debug, Default)]
pub struct HaTileMapSystemCache {
    meshes: HashMap<Entity, MeshId>,
}

pub type HaTileMapSystemResources<'a> = (
    WorldRef,
    &'a mut HaRenderer,
    &'a EntityChanges,
    &'a mut HaTileMapSystemCache,
    Comp<&'a mut HaTileMapInstance>,
    Comp<&'a mut HaMeshInstance>,
    Comp<&'a mut HaMaterialInstance>,
);

pub fn ha_tilemap_system(universe: &mut Universe) {
    let (world, mut renderer, changes, mut cache, ..) =
        universe.query_resources::<HaTileMapSystemResources>();

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

    for (entity, (tilemap, mesh, material)) in world
        .query::<(
            &mut HaTileMapInstance,
            &mut HaMeshInstance,
            &mut HaMaterialInstance,
        )>()
        .iter()
    {
        if tilemap.dirty || !cache.meshes.contains_key(&entity) {
            mesh.reference = MeshInstanceReference::None;
            if let Ok(factory) =
                SurfaceTileMapFactory::factory::<SurfaceVertexPT>(tilemap, &renderer.virtual_images)
            {
                let image_id = match renderer
                    .virtual_images
                    .get_named(tilemap.atlas())
                    .unwrap()
                    .source()
                {
                    VirtualImageSource::Image(image_id) => Some(*image_id),
                    _ => None,
                };
                if let Some(image_id) = image_id {
                    if let Some(id) = cache.meshes.get(&entity) {
                        if let Some(m) = renderer.mesh_mut(*id) {
                            m.set_vertex_storage_all(tilemap.change_frequency().into());
                            m.set_index_storage(tilemap.change_frequency().into());
                            if factory.write_into(m).is_ok() {
                                tilemap.dirty = false;
                                mesh.reference = MeshInstanceReference::Id(*id);
                                set_material_sampler(material, image_id);
                            }
                        }
                    } else {
                        let mut m = Mesh::new(factory.layout().to_owned());
                        m.set_vertex_storage_all(tilemap.change_frequency().into());
                        m.set_index_storage(tilemap.change_frequency().into());
                        if factory.write_into(&mut m).is_ok() {
                            if let Ok(id) = renderer.add_mesh(m) {
                                tilemap.dirty = false;
                                mesh.reference = MeshInstanceReference::Id(id);
                                set_material_sampler(material, image_id);
                                cache.meshes.insert(entity, id);
                            }
                        }
                    }
                }
            }
        }
    }
}

fn set_material_sampler(material: &mut HaMaterialInstance, id: ImageId) {
    material.values.insert(
        "mainImage".to_owned(),
        MaterialValue::Sampler2D(ImageInstanceReference::Id(id)),
    );
}
