use crate::Arena;
use crate::core::image::Image;
use crate::utils::TextureParameterDictionary;
use crate::utils::error::FileLoc;
use anyhow::{Result, anyhow};
use shared::core::material::Material;
use shared::materials::*;
use std::collections::HashMap;
use std::sync::Arc;

pub trait CreateMaterial: Sized {
    fn create(
        parameters: &TextureParameterDictionary,
        normal_map: Option<Arc<Image>>,
        named_materials: &HashMap<String, Material>,
        loc: &FileLoc,
        arena: &Arena,
    ) -> Result<Material>;
}

pub trait MaterialFactory {
    fn create(
        name: &str,
        params: &TextureParameterDictionary,
        normal_map: Option<Arc<Image>>,
        named_materials: &HashMap<String, Material>,
        loc: FileLoc,
        arena: &Arena,
    ) -> Result<Self>
    where
        Self: Sized;
}

impl MaterialFactory for Material {
    fn create(
        name: &str,
        parameters: &TextureParameterDictionary,
        normal_map: Option<Arc<Image>>,
        named_materials: &HashMap<String, Material>,
        loc: FileLoc,
        arena: &Arena,
    ) -> Result<Self>
    where
        Self: Sized,
    {
        match name {
            "diffuse" => {
                DiffuseMaterial::create(parameters, normal_map, named_materials, &loc, arena)
            }
            "coateddiffuse" => {
                CoatedDiffuseMaterial::create(parameters, normal_map, named_materials, &loc, arena)
            }
            "coatedconductor" => CoatedConductorMaterial::create(
                parameters,
                normal_map,
                named_materials,
                &loc,
                arena,
            ),
            "diffusetransmission" => DiffuseTransmissionMaterial::create(
                parameters,
                normal_map,
                named_materials,
                &loc,
                arena,
            ),
            "dielectric" => {
                DielectricMaterial::create(parameters, normal_map, named_materials, &loc, arena)
            }
            "thindielectric" => {
                ThinDielectricMaterial::create(parameters, normal_map, named_materials, &loc, arena)
            }
            "hair" => HairMaterial::create(parameters, normal_map, named_materials, &loc, arena),
            "conductor" => {
                ConductorMaterial::create(parameters, normal_map, named_materials, &loc, arena)
            }
            "measured" => {
                MeasuredMaterial::create(parameters, normal_map, named_materials, &loc, arena)
            }
            "subsurface" => {
                SubsurfaceMaterial::create(parameters, normal_map, named_materials, &loc, arena)
            }
            "mix" => MixMaterial::create(parameters, normal_map, named_materials, &loc, arena),

            _ => Err(anyhow!("Material type '{}' unknown at {}", name, &loc)),
        }
    }
}

pub fn default_diffuse_material(arena: &Arena) -> Material {
    use shared::core::texture::GPUSpectrumTexture;
    use shared::core::texture::SpectrumConstantTexture;
    use shared::core::spectrum::{ConstantSpectrum, Spectrum};
    use shared::materials::DiffuseMaterial;
    use shared::utils::Ptr;

    let grey = Spectrum::Constant(ConstantSpectrum { c: 0.5 });
    let tex = GPUSpectrumTexture::Constant(SpectrumConstantTexture::new(grey));
    let tex_ptr = arena.alloc(tex);

    Material::Diffuse(DiffuseMaterial {
        normal_map: Ptr::null(),
        displacement: Ptr::null(),
        reflectance: tex_ptr,
    })
}
