use crate::core::spectrum::SPECTRUM_FILE_CACHE;
use crate::core::texture::{FloatTexture, SpectrumTexture};
use crate::spectra::data::get_named_spectrum;
use crate::spectra::piecewise::ReadFromFile;
use crate::utils::FileLoc;
use anyhow::{bail, Result};
use shared::core::color::RGB;
use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector2f, Vector3f};
use shared::core::spectrum::Spectrum;
use shared::core::texture::{FloatConstantTexture, SpectrumConstantTexture, SpectrumType};
use shared::spectra::{
    PiecewiseLinearSpectrum, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum,
    RGBUnboundedSpectrum,
};
use shared::Float;

use std::collections::HashMap;
use std::sync::{
    atomic::{AtomicBool, Ordering},
    Arc,
};

pub fn error_exit(loc: Option<&FileLoc>, message: &str) -> String {
    if let Some(l) = loc {
        format!("{}: {}", l, message)
    } else {
        message.to_string()
    }
}

#[derive(Debug)]
pub struct ParsedParameter {
    pub type_name: String,
    pub name: String,
    pub loc: FileLoc,

    pub floats: Vec<Float>,
    pub ints: Vec<i32>,
    pub strings: Vec<String>,
    pub bools: Vec<bool>,

    pub looked_up: AtomicBool,
    pub may_be_unused: bool,
    pub color_space: Option<Arc<RGBColorSpace>>,
}

impl Default for ParsedParameter {
    fn default() -> Self {
        Self {
            type_name: String::new(),
            name: String::new(),
            loc: FileLoc::default(),
            floats: Vec::new(),
            ints: Vec::new(),
            strings: Vec::new(),
            bools: Vec::new(),
            looked_up: AtomicBool::new(false),
            may_be_unused: false,
            color_space: None,
        }
    }
}

impl Clone for ParsedParameter {
    fn clone(&self) -> Self {
        let looked_up_val = self.looked_up.load(Ordering::Relaxed);

        Self {
            type_name: self.type_name.clone(),
            name: self.name.clone(),
            loc: self.loc.clone(),
            floats: self.floats.clone(),
            ints: self.ints.clone(),
            strings: self.strings.clone(),
            bools: self.bools.clone(),
            looked_up: AtomicBool::new(looked_up_val),
            may_be_unused: self.may_be_unused,
            color_space: self.color_space.clone(),
        }
    }
}

impl ParsedParameter {
    pub fn new(loc: FileLoc) -> Self {
        Self {
            type_name: String::new(),
            name: String::new(),
            loc,
            floats: Vec::new(),
            ints: Vec::new(),
            strings: Vec::new(),
            bools: Vec::new(),
            looked_up: AtomicBool::new(false),
            may_be_unused: false,
            color_space: None,
        }
    }

    pub fn add_float(&mut self, v: Float) {
        self.floats.push(v);
    }
    pub fn add_int(&mut self, i: i32) {
        self.ints.push(i);
    }
    pub fn add_string(&mut self, s: String) {
        self.strings.push(s);
    }
    pub fn add_bool(&mut self, v: bool) {
        self.bools.push(v);
    }

    pub fn to_string(&self) -> String {
        format!("[ ParsedParameter {} {} ]", self.type_name, self.name)
    }
}

pub type ParsedParameterVector = Vec<ParsedParameter>;

pub trait PBRTParameter: Sized {
    type Raw: Clone;
    const TYPE_NAME: &'static str;
    const N_PER_ITEM: usize;
    fn convert(v: &[Self::Raw]) -> Self;
    fn get_values(param: &ParsedParameter) -> &[Self::Raw];
}

impl PBRTParameter for bool {
    type Raw = bool;
    const TYPE_NAME: &'static str = "bool";
    const N_PER_ITEM: usize = 1;
    fn convert(v: &[Self::Raw]) -> Self {
        v[0]
    }
    fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
        &param.bools
    }
}

impl PBRTParameter for RGB {
    type Raw = Float;
    const TYPE_NAME: &'static str = "rgb";
    const N_PER_ITEM: usize = 3;
    fn convert(v: &[Self::Raw]) -> Self {
        RGB::new(v[0], v[1], v[2])
    }
    fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
        &param.floats
    }
}

impl PBRTParameter for Float {
    type Raw = Float;
    const TYPE_NAME: &'static str = "float";
    const N_PER_ITEM: usize = 1;
    fn convert(v: &[Self::Raw]) -> Self {
        v[0]
    }
    fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
        &param.floats
    }
}

impl PBRTParameter for i32 {
    type Raw = i32;
    const TYPE_NAME: &'static str = "integer";
    const N_PER_ITEM: usize = 1;
    fn convert(v: &[Self::Raw]) -> Self {
        v[0]
    }

    fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
        &param.ints
    }
}

impl PBRTParameter for Point2f {
    type Raw = Float;
    const TYPE_NAME: &'static str = "point2";
    const N_PER_ITEM: usize = 2;
    fn convert(v: &[Self::Raw]) -> Self {
        Point2f::new(v[0], v[1])
    }
    fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
        &param.floats
    }
}

impl PBRTParameter for Point3f {
    type Raw = Float;
    const TYPE_NAME: &'static str = "point3";
    const N_PER_ITEM: usize = 3;
    fn convert(v: &[Self::Raw]) -> Self {
        Point3f::new(v[0], v[1], v[2])
    }
    fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
        &param.floats
    }
}

impl PBRTParameter for Vector2f {
    type Raw = Float;
    const TYPE_NAME: &'static str = "vector2";
    const N_PER_ITEM: usize = 2;
    fn convert(v: &[Self::Raw]) -> Self {
        Vector2f::new(v[0], v[1])
    }
    fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
        &param.floats
    }
}

impl PBRTParameter for Vector3f {
    type Raw = Float;
    const TYPE_NAME: &'static str = "vector3";
    const N_PER_ITEM: usize = 3;
    fn convert(v: &[Self::Raw]) -> Self {
        Vector3f::new(v[0], v[1], v[2])
    }
    fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
        &param.floats
    }
}

impl PBRTParameter for Normal3f {
    type Raw = Float;
    const TYPE_NAME: &'static str = "normal";
    const N_PER_ITEM: usize = 3;
    fn convert(v: &[Self::Raw]) -> Self {
        Normal3f::new(v[0], v[1], v[2])
    }
    fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
        &param.floats
    }
}

impl PBRTParameter for String {
    type Raw = String;
    const TYPE_NAME: &'static str = "string";
    const N_PER_ITEM: usize = 1;
    fn convert(v: &[Self::Raw]) -> Self {
        v[0].clone()
    }
    fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
        &param.strings
    }
}

#[derive(Default, Clone)]
pub struct NamedTextures {
    pub float_textures: Arc<HashMap<String, Arc<FloatTexture>>>,
    pub albedo_spectrum_textures: Arc<HashMap<String, Arc<SpectrumTexture>>>,
    pub unbounded_spectrum_textures: Arc<HashMap<String, Arc<SpectrumTexture>>>,
    pub illuminant_spectrum_textures: Arc<HashMap<String, Arc<SpectrumTexture>>>,
}

#[derive(Debug, Default, Clone)]
pub struct ParameterDictionary {
    pub params: ParsedParameterVector,
    pub color_space: Option<Arc<RGBColorSpace>>,
    pub n_owned_params: usize,
}

impl ParameterDictionary {
    pub fn new(
        mut params: ParsedParameterVector,
        color_space: Option<Arc<RGBColorSpace>>,
    ) -> Result<Self> {
        let n_owned_params = params.len();
        params.reverse();
        let dict = Self {
            params,
            color_space,
            n_owned_params,
        };
        dict.check_parameter_types()?;
        Ok(dict)
    }

    pub fn from_array(
        mut p0: ParsedParameterVector,
        params: &[ParsedParameter],
        color_space: Option<Arc<RGBColorSpace>>,
    ) -> Result<Self> {
        let n_owned_params = params.len();
        p0.extend(params.iter().rev().cloned());

        let dict = Self {
            params: p0,
            color_space,
            n_owned_params,
        };
        dict.check_parameter_types()?;
        Ok(dict)
    }

    pub fn colorspace(&self) -> &RGBColorSpace {
        self.color_space
            .as_deref()
            .unwrap_or_else(|| crate::spectra::default_colorspace_ref())
    }

    fn check_parameter_types(&self) -> Result<()> {
        for p in &self.params {
            match p.type_name.as_str() {
                bool::TYPE_NAME => {
                    if p.bools.is_empty() {
                        bail!(
                            "{}: non-Boolean values provided for Boolean-valued parameter \"{}\"",
                            &p.loc,
                            p.name
                        );
                    }
                }

                Float::TYPE_NAME
                | i32::TYPE_NAME
                | Point2f::TYPE_NAME
                | Vector2f::TYPE_NAME
                | Point3f::TYPE_NAME
                | Vector3f::TYPE_NAME
                | Normal3f::TYPE_NAME
                | "rgb"
                | "blackbody" => {
                    if p.ints.is_empty() && p.floats.is_empty() {
                        bail!(
                            "{}: non-numeric values provided for numeric-valued parameter \"{}\"",
                            &p.loc,
                            p.name
                        );
                    }
                }

                String::TYPE_NAME | "texture" => {
                    if p.strings.is_empty() {
                        bail!(
                            "{}: non-string values provided for string-valued parameter \"{}\"",
                            &p.loc,
                            p.name
                        );
                    }
                }

                "spectrum" => {
                    if p.strings.is_empty() && p.ints.is_empty() && p.floats.is_empty() {
                        bail!(
                            "{}: expecting string or numeric-valued parameter for spectrum parameter \"{}\"",
                            &p.loc,
                            p.name
                        );
                    }
                }

                unknown => {
                    bail!(
                        "{}: unknown parameter type \"{}\" '{}'",
                        &p.loc,
                        p.name,
                        unknown,
                    );
                }
            }
        }

        Ok(())
    }

    fn lookup_single<T>(&self, name: &str, default_val: T) -> Result<T>
    where
        T: PBRTParameter,
    {
        for param in &self.params {
            if param.name == name && param.type_name == T::TYPE_NAME {
                let values = T::get_values(param);

                if values.is_empty() {
                    bail!(
                        "{}: No values provided for parameter \"{}\".",
                        &param.loc,
                        name
                    );
                }

                if values.len() != T::N_PER_ITEM {
                    bail!(
                        "{}: Expected {} values for parameter \"{}\". Found {}.",
                        &param.loc,
                        T::N_PER_ITEM,
                        name,
                        values.len()
                    );
                }

                param.looked_up.store(true, Ordering::Relaxed);
                return Ok(T::convert(values));
            }
        }

        Ok(default_val)
    }

    pub fn lookup_array<T>(&self, name: &str) -> Result<Vec<T>>
    where
        T: PBRTParameter,
    {
        for param in &self.params {
            if param.name == name && param.type_name == T::TYPE_NAME {
                let values = T::get_values(param);

                if values.len() % T::N_PER_ITEM != 0 {
                    bail!(
                        "{}: Number of values for \"{}\" is not a multiple of {}",
                        &param.loc,
                        name,
                        T::N_PER_ITEM
                    );
                }

                param.looked_up.store(true, Ordering::Relaxed);

                return Ok(values
                    .chunks(T::N_PER_ITEM)
                    .map(|chunk| T::convert(chunk))
                    .collect());
            }
        }
        Ok(Vec::new())
    }

    pub fn get_one_float(&self, name: &str, def: Float) -> Result<Float> {
        self.lookup_single(name, def)
    }

    pub fn get_one_int(&self, name: &str, def: i32) -> Result<i32> {
        self.lookup_single(name, def)
    }

    pub fn get_one_bool(&self, name: &str, def: bool) -> Result<bool> {
        self.lookup_single(name, def)
    }

    pub fn get_one_string(&self, name: &str, def: &str) -> Result<String> {
        self.lookup_single(name, def.to_string())
    }

    pub fn get_one_point2f(&self, name: &str, def: Point2f) -> Result<Point2f> {
        self.lookup_single(name, def)
    }

    pub fn get_one_point3f(&self, name: &str, def: Point3f) -> Result<Point3f> {
        self.lookup_single(name, def)
    }

    pub fn get_one_vector2f(&self, name: &str, def: Vector2f) -> Result<Vector2f> {
        self.lookup_single(name, def)
    }

    pub fn get_one_vector3f(&self, name: &str, def: Vector3f) -> Result<Vector3f> {
        self.lookup_single(name, def)
    }

    pub fn get_one_normal3f(&self, name: &str, def: Normal3f) -> Result<Normal3f> {
        self.lookup_single(name, def)
    }

    pub fn get_one_rgb(&self, name: &str, def: RGB) -> Result<RGB> {
        self.lookup_single(name, def)
    }

    pub fn get_float_array(&self, name: &str) -> Result<Vec<Float>> {
        self.lookup_array(name)
    }

    pub fn get_int_array(&self, name: &str) -> Result<Vec<i32>> {
        self.lookup_array(name)
    }

    pub fn get_bool_array(&self, name: &str) -> Result<Vec<bool>> {
        self.lookup_array(name)
    }

    pub fn get_string_array(&self, name: &str) -> Result<Vec<String>> {
        self.lookup_array(name)
    }

    pub fn get_point2f_array(&self, name: &str) -> Result<Vec<Point2f>> {
        self.lookup_array(name)
    }

    pub fn get_point3f_array(&self, name: &str) -> Result<Vec<Point3f>> {
        self.lookup_array(name)
    }

    pub fn get_vector3f_array(&self, name: &str) -> Result<Vec<Vector3f>> {
        self.lookup_array(name)
    }

    pub fn get_normal3f_array(&self, name: &str) -> Result<Vec<Normal3f>> {
        self.lookup_array(name)
    }

    pub fn get_rgb_array(&self, name: &str) -> Result<Vec<RGB>> {
        self.lookup_array(name)
    }

    pub fn get_one_spectrum(
        &self,
        name: &str,
        def: Option<Spectrum>,
        stype: SpectrumType,
    ) -> Option<Spectrum> {
        for param in &self.params {
            if param.name == name {
                param.looked_up.store(true, Ordering::Relaxed);
                if param.type_name == "spectrum"
                    || param.type_name == "rgb"
                    || param.type_name == "blackbody"
                {
                    return Some(self.extract_spectrum_array(param, stype)[0].clone());
                }
            }
        }
        def
    }

    pub fn get_spectrum_array(&self, name: &str, stype: SpectrumType) -> Vec<Spectrum> {
        for param in &self.params {
            if param.name == name {
                param.looked_up.store(true, Ordering::Relaxed);
                if param.type_name == "spectrum"
                    || param.type_name == "rgb"
                    || param.type_name == "blackbody"
                {
                    return self.extract_spectrum_array(param, stype);
                }
            }
        }
        Vec::new()
    }

    pub fn get_texture(&self, name: &str) -> String {
        for p in &self.params {
            if p.name == name && p.type_name == "texture" {
                if p.strings.len() != 1 {
                    panic!(
                        "[{:?}] Expected 1 texture name for {}, found {}",
                        p.loc,
                        name,
                        p.strings.len()
                    );
                }
                p.looked_up.store(true, Ordering::Relaxed);
                return p.strings[0].clone();
            }
        }
        String::new()
    }

    pub fn report_unused(&self) {
        for param in &self.params {
            if !param.looked_up.load(Ordering::Relaxed) && !param.may_be_unused {
                eprintln!(
                    "Warning: Parameter '{}' ({}) was unused.",
                    param.name, param.type_name
                );
            }
        }
    }

    pub fn remove_float(&mut self, name: &str) {
        self.remove(name, "float");
    }
    pub fn remove_int(&mut self, name: &str) {
        self.remove(name, "integer");
    }
    pub fn remove_bool(&mut self, name: &str) {
        self.remove(name, "bool");
    }

    fn remove(&mut self, name: &str, type_name: &str) {
        self.params
            .retain(|p| !(p.name == name && p.type_name == type_name));
    }

    pub fn rename_parameter(&mut self, before: &str, after: &str) {
        for param in &mut self.params {
            if param.name == before {
                param.name = after.to_string();
            }
        }
    }

    fn extract_spectrum_array(
        &self,
        param: &ParsedParameter,
        spectrum_type: SpectrumType,
    ) -> Vec<Spectrum> {
        match param.type_name.as_str() {
            "rgb" | "color" => self.extract_rgb_spectrum(param, spectrum_type),
            "blackbody" => self.extract_file_spectrum(param),
            "spectrum" => self.extract_sampled_spectrum(param),
            _ => Vec::new(),
        }
    }

    fn extract_rgb_spectrum(
        &self,
        param: &ParsedParameter,
        spectrum_type: SpectrumType,
    ) -> Vec<Spectrum> {
        let color_space = param
            .color_space
            .as_ref()
            .or(self.color_space.as_ref())
            .expect("No color available");

        param
            .floats
            .chunks_exact(3)
            .map(|chunk| {
                let rgb = RGB::new(chunk[0], chunk[1], chunk[2]);

                if rgb.r < 0.0 || rgb.g < 0.0 || rgb.b < 0.0 {
                    panic!(
                        "{}: RGB parameter '{}' has negative component.",
                        param.loc, param.name
                    );
                }

                match spectrum_type {
                    SpectrumType::Albedo => {
                        if rgb.r > 1.0 || rgb.g > 1.0 || rgb.b > 1.0 {
                            panic!(
                                "{}: RGB parameter '{}' has > 1 component.",
                                param.loc, param.name
                            );
                        }
                        Spectrum::RGBAlbedo(RGBAlbedoSpectrum::new(color_space.as_ref(), rgb))
                    }
                    SpectrumType::Unbounded => {
                        Spectrum::RGBUnbounded(RGBUnboundedSpectrum::new(color_space.as_ref(), rgb))
                    }
                    SpectrumType::Illuminant => Spectrum::RGBIlluminant(
                        RGBIlluminantSpectrum::new(color_space.as_ref(), rgb),
                    ),
                }
            })
            .collect()
    }

    fn extract_sampled_spectrum(&self, param: &ParsedParameter) -> Vec<Spectrum> {
        if param.floats.len() % 2 != 0 {
            panic!(
                "{}: Found odd number of values for '{}'",
                param.loc, param.name
            );
        }

        let n_samples = param.floats.len() / 2;
        if n_samples == 1 {
            eprintln!(
                "{}: Specified spectrum is only non-zero at a single wavelength.",
                param.loc
            );
        }

        let (lambdas, values): (Vec<Float>, Vec<Float>) = param
            .floats
            .chunks(2)
            .enumerate()
            .map(|(i, chunk)| {
                let (lam, val) = (chunk[0], chunk[1]);
                if i > 0 && lam <= param.floats[(i - 1) * 2] {
                    panic!(
                        "{}: Spectrum invalid, wavelengths not increasing: {} >= {}.",
                        param.loc,
                        param.floats[(i - 1) * 2],
                        lam
                    );
                }
                (lam as Float, val as Float)
            })
            .unzip();

        vec![Spectrum::Piecewise(leak(PiecewiseLinearSpectrum {
            lambdas: gvec_from_slice(lambdas),
            values: gvec_from_slice(values),
            count: lambdas.len() as u32,
        }))]
    }

    fn extract_file_spectrum(&self, param: &ParsedParameter) -> Vec<Spectrum> {
        param
            .strings
            .iter()
            .map(|s| {
                get_named_spectrum(s)
                    .ok_or(())
                    .or_else(|_| read_spectrum_from_file(s).map_err(|_| ()))
                    .unwrap_or_else(|_| panic!("{}: {}: unable to read spectrum", param.loc, s))
            })
            .collect()
    }
}

fn read_spectrum_from_file(filename: &str) -> Result<Spectrum, String> {
    let fn_key = filename.to_string();
    {
        let cache = SPECTRUM_FILE_CACHE.lock();
        if let Some(s) = cache.get(&fn_key) {
            return Ok(*s); // Spectrum is Copy, so just copy it
        }
    }

    let pls = PiecewiseLinearSpectrum::read(&fn_key)
        .ok_or_else(|| format!("unable to read or parse spectrum file '{}'", fn_key))?;

    let spectrum = Spectrum::Piecewise(leak(pls));

    {
        let mut cache = SPECTRUM_FILE_CACHE.lock();
        cache.insert(fn_key, spectrum);
    }

    Ok(spectrum)
}

pub struct TextureParameterDictionary {
    dict: Arc<ParameterDictionary>,
    textures: Option<NamedTextures>,
}

impl TextureParameterDictionary {
    pub fn new(dict: Arc<ParameterDictionary>, textures: Option<&NamedTextures>) -> Self {
        Self {
            dict,
            textures: textures.cloned(),
        }
    }

    pub fn get_one_float(&self, name: &str, def: Float) -> Result<Float> {
        self.dict.get_one_float(name, def)
    }

    pub fn get_one_int(&self, name: &str, def: i32) -> Result<i32> {
        self.dict.get_one_int(name, def)
    }

    pub fn get_one_bool(&self, name: &str, def: bool) -> Result<bool> {
        self.dict.get_one_bool(name, def)
    }

    pub fn get_one_string(&self, name: &str, def: &str) -> Result<String> {
        self.dict.get_one_string(name, def)
    }

    pub fn get_float_array(&self, name: &str) -> Result<Vec<Float>> {
        self.dict.get_float_array(name)
    }

    pub fn get_int_array(&self, name: &str) -> Result<Vec<i32>> {
        self.dict.get_int_array(name)
    }

    pub fn get_bool_array(&self, name: &str) -> Result<Vec<bool>> {
        self.dict.get_bool_array(name)
    }

    pub fn get_string_array(&self, name: &str) -> Result<Vec<String>> {
        self.dict.get_string_array(name)
    }

    pub fn get_one_point3f(&self, name: &str, def: Point3f) -> Result<Point3f> {
        self.dict.get_one_point3f(name, def)
    }

    pub fn get_one_vector3f(&self, name: &str, def: Vector3f) -> Result<Vector3f> {
        self.dict.get_one_vector3f(name, def)
    }

    pub fn get_one_normal3f(&self, name: &str, def: Normal3f) -> Result<Normal3f> {
        self.dict.get_one_normal3f(name, def)
    }

    pub fn get_point3f_array(&self, name: &str) -> Result<Vec<Point3f>> {
        self.dict.get_point3f_array(name)
    }

    pub fn get_vector3f_array(&self, name: &str) -> Result<Vec<Vector3f>> {
        self.dict.get_vector3f_array(name)
    }

    pub fn get_normal3f_array(&self, name: &str) -> Result<Vec<Normal3f>> {
        self.dict.get_normal3f_array(name)
    }

    pub fn get_one_point2f(&self, name: &str, def: Point2f) -> Result<Point2f> {
        self.dict.get_one_point2f(name, def)
    }

    pub fn get_one_vector2f(&self, name: &str, def: Vector2f) -> Result<Vector2f> {
        self.dict.get_one_vector2f(name, def)
    }

    pub fn get_one_spectrum(
        &self,
        name: &str,
        def: Option<Spectrum>,
        stype: SpectrumType,
    ) -> Option<Spectrum> {
        self.dict.get_one_spectrum(name, def, stype)
    }

    pub fn get_spectrum_array(&self, name: &str, stype: SpectrumType) -> Vec<Spectrum> {
        self.dict.get_spectrum_array(name, stype)
    }

    pub fn get_texture(&self, name: &str) -> String {
        self.dict.get_texture(name)
    }

    pub fn report_unused(&self) {
        self.dict.report_unused()
    }

    pub fn get_spectrum_texture(
        &self,
        name: &str,
        val: Option<Spectrum>,
        stype: SpectrumType,
    ) -> Option<Arc<SpectrumTexture>> {
        let tex = self.get_spectrum_texture_or_null(name, stype);
        if tex.is_some() {
            tex
        } else if val.is_some() {
            Some(Arc::new(SpectrumTexture::Constant(
                SpectrumConstantTexture::new(val.unwrap()),
            )))
        } else {
            None
        }
    }

    pub fn get_float_texture(&self, name: &str, val: Float) -> Result<Arc<FloatTexture>> {
        if let Some(tex) = self.get_float_texture_or_null(name)? {
            return Ok(tex);
        } else {
            return Ok(Arc::new(FloatTexture::Constant(FloatConstantTexture::new(
                val,
            ))));
        }
    }

    pub fn get_float_texture_with_fallback(
        &self,
        primary: &str,
        fallback: &str,
        default: Float,
    ) -> Result<Arc<FloatTexture>> {
        match self.get_float_texture_or_null(primary)? {
            Some(tex) => Ok(tex),
            None => self.get_float_texture(fallback, default),
        }
    }

    pub fn get_spectrum_texture_or_null(
        &self,
        name: &str,
        stype: SpectrumType,
    ) -> Option<Arc<SpectrumTexture>> {
        for p in &self.dict.params {
            if p.name != name {
                continue;
            }

            match p.type_name.as_str() {
                "texture" => {
                    if p.strings.len() != 1 {
                        panic!(
                            "[{:?}] Expected 1 texture name for {}, found {}",
                            p.loc,
                            name,
                            p.strings.len()
                        );
                    }

                    p.looked_up.store(true, Ordering::Relaxed);
                    let tex_name = &p.strings[0];

                    if let Some(nt) = &self.textures {
                        let map = match stype {
                            SpectrumType::Unbounded => &nt.unbounded_spectrum_textures,
                            SpectrumType::Albedo => &nt.albedo_spectrum_textures,
                            _ => &nt.illuminant_spectrum_textures,
                        };

                        if let Some(tex) = map.get(tex_name) {
                            return Some(Arc::clone(tex));
                        }
                        panic!(
                            "[{:?}] Couldn't find spectrum texture named '{}'",
                            p.loc, tex_name
                        );
                    }
                    return None;
                }

                "rgb" => {
                    if p.floats.len() != 3 {
                        panic!("[{:?}] RGB parameter '{}' needs 3 floats", p.loc, name);
                    }
                    p.looked_up.store(true, Ordering::Relaxed);

                    let rgb = RGB::new(p.floats[0], p.floats[1], p.floats[2]);
                    if rgb.r < 0.0 || rgb.g < 0.0 || rgb.b < 0.0 {
                        panic!("[{:?}] Negative RGB values for '{}'", p.loc, name);
                    }

                    let cs = self.dict.color_space.as_ref().unwrap();
                    let s: Spectrum = match stype {
                        SpectrumType::Illuminant => {
                            Spectrum::RGBIlluminant(RGBIlluminantSpectrum::new(cs, rgb))
                        }
                        SpectrumType::Unbounded => {
                            Spectrum::RGBUnbounded(RGBUnboundedSpectrum::new(cs, rgb))
                        }
                        SpectrumType::Albedo => {
                            if rgb.r > 1.0 || rgb.g > 1.0 || rgb.b > 1.0 {
                                panic!("[{:?}] Albedo RGB > 1 for '{}'", p.loc, name);
                            }
                            Spectrum::RGBAlbedo(RGBAlbedoSpectrum::new(cs, rgb))
                        }
                    };
                    return Some(Arc::new(SpectrumTexture::Constant(
                        SpectrumConstantTexture::new(s),
                    )));
                }

                "spectrum" | "blackbody" => {
                    let s = self.dict.get_one_spectrum(name, None, stype)?;
                    return Some(Arc::new(SpectrumTexture::Constant(
                        SpectrumConstantTexture::new(s),
                    )));
                }

                _ => {}
            }
        }
        return None;
    }

    pub fn get_float_texture_or_null(&self, name: &str) -> Result<Option<Arc<FloatTexture>>> {
        for p in &self.dict.params {
            if p.name != name {
                continue;
            }

            match p.type_name.as_str() {
                "texture" => {
                    if p.strings.len() != 1 {
                        panic!(
                            "[{:?}] Expected 1 texture name for {}, found {}",
                            p.loc,
                            name,
                            p.strings.len()
                        );
                    }

                    p.looked_up.store(true, Ordering::Relaxed);
                    let tex_name = &p.strings[0];

                    if let Some(nt) = &self.textures {
                        let map = &nt.float_textures;
                        if let Some(tex) = map.get(tex_name) {
                            return Ok(Some(Arc::clone(tex)));
                        }
                        panic!(
                            "[{:?}] Couldn't find float texture named '{}'",
                            p.loc, tex_name
                        );
                    }
                    return Ok(None);
                }
                "float" => {
                    let v = self.get_one_float(name, 0.)?;
                    return Ok(Some(Arc::new(FloatTexture::Constant(
                        FloatConstantTexture::new(v),
                    ))));
                }
                _ => {
                    panic!("[{:?}] Couldn't find float texture", p.loc);
                }
            }
        }
        Ok(None)
    }
}
