Documentation
use crate::core::sstorage::ImmutableString;
use crate::{
    asset::ResourceState,
    core::{scope_profile, sparse::SparseBuffer},
    engine::resource_manager::DEFAULT_RESOURCE_LIFETIME,
    material::shader::{Shader, ShaderState},
    renderer::{
        cache::CacheEntry,
        framework::{framebuffer::DrawParameters, gpu_program::GpuProgram, state::PipelineState},
    },
    utils::log::{Log, MessageKind},
};
use fxhash::FxHashMap;
use std::ops::Deref;

pub struct RenderPassData {
    pub program: GpuProgram,
    pub draw_params: DrawParameters,
}

pub struct ShaderSet {
    pub render_passes: FxHashMap<ImmutableString, RenderPassData>,
}

impl ShaderSet {
    pub fn new(state: &mut PipelineState, shader: &ShaderState) -> Option<Self> {
        let mut map = FxHashMap::default();
        for render_pass in shader.definition.passes.iter() {
            let program_name = format!("{}_{}", shader.definition.name, render_pass.name);
            match GpuProgram::from_source(
                state,
                &program_name,
                &render_pass.vertex_shader,
                &render_pass.fragment_shader,
            ) {
                Ok(gpu_program) => {
                    map.insert(
                        ImmutableString::new(&render_pass.name),
                        RenderPassData {
                            program: gpu_program,
                            draw_params: render_pass.draw_parameters.clone(),
                        },
                    );
                }
                Err(e) => {
                    Log::writeln(
                        MessageKind::Error,
                        format!(
                            "Failed to create {} shader' GPU program. Reason: {:?}",
                            program_name, e
                        ),
                    );
                    return None;
                }
            };
        }

        Some(Self { render_passes: map })
    }
}

#[derive(Default)]
pub struct ShaderCache {
    pub(super) buffer: SparseBuffer<CacheEntry<ShaderSet>>,
}

impl ShaderCache {
    pub fn get(&mut self, state: &mut PipelineState, shader: &Shader) -> Option<&ShaderSet> {
        scope_profile!();

        let key = shader.key();
        let shader = shader.state();

        if let ResourceState::Ok(shader_state) = shader.deref() {
            if self.buffer.is_index_valid(&shader_state.cache_index) {
                Some(&self.buffer.get(&shader_state.cache_index).unwrap().value)
            } else {
                let index = self.buffer.spawn(CacheEntry {
                    value: ShaderSet::new(state, shader_state)?,
                    time_to_live: DEFAULT_RESOURCE_LIFETIME,
                    value_hash: key as u64,
                });
                shader_state.cache_index.set(index.get());
                Some(&self.buffer.get(&index).unwrap().value)
            }
        } else {
            None
        }
    }

    pub fn update(&mut self, dt: f32) {
        scope_profile!();

        for entry in self.buffer.iter_mut() {
            entry.time_to_live -= dt;
        }

        for i in 0..self.buffer.len() {
            if let Some(entry) = self.buffer.get_raw(i) {
                if entry.time_to_live <= 0.0 {
                    self.buffer.free_raw(i);
                }
            }
        }
    }

    pub fn clear(&mut self) {
        self.buffer.clear();
    }
}