use crate::core::texture::{get_texture_cache, CreateTextureMapping, TexInfo};
use crate::core::texture::{
    CreateFloatTexture, CreateSpectrumTexture, FloatTexture, FloatTextureTrait, SpectrumTexture,
    SpectrumTextureTrait,
};
use crate::utils::mipmap::{MIPMap, MIPMapFilterOptions};
use crate::utils::{FileLoc, TextureParameterDictionary};
use crate::Arena;
use anyhow::Result;
use shared::core::color::RGB;
use shared::core::color::{ColorEncoding, SRGBEncoding};
use shared::core::geometry::Vector2f;
use shared::core::image::WrapMode;
use shared::core::spectrum::SpectrumTrait;
use shared::core::texture::{SpectrumType, TextureEvalContext, TextureMapping2D};
use shared::spectra::{
    RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
    SampledWavelengths,
};
use shared::utils::Transform;
use shared::Float;
use std::path::Path;
use std::sync::Arc;

#[derive(Clone, Debug)]
pub struct ImageTextureBase {
    pub mapping: TextureMapping2D,
    pub filename: String,
    pub scale: Float,
    pub invert: bool,
    pub mipmap: Arc<MIPMap>,
}

impl ImageTextureBase {
    pub fn new(
        mapping: TextureMapping2D,
        filename: String,
        filter_options: MIPMapFilterOptions,
        wrap_mode: WrapMode,
        scale: Float,
        invert: bool,
        encoding: ColorEncoding,
    ) -> Self {
        let tex_info = TexInfo {
            filename: filename.clone(),
            filter_options,
            wrap_mode,
            encoding,
        };

        let cache_mutex = get_texture_cache();

        {
            let cache = cache_mutex.lock().unwrap();
            if let Some(mipmap) = cache.get(&tex_info) {
                return Self {
                    mapping,
                    filename,
                    scale,
                    invert,
                    mipmap: mipmap.clone(),
                };
            }
        }

        let path = Path::new(&filename);
        let mipmap_raw = MIPMap::create_from_file(path, filter_options, wrap_mode, encoding)
            .expect("Failed to create MIPMap from file");

        let mipmap_arc = Arc::new(mipmap_raw);

        {
            let mut cache = cache_mutex.lock().unwrap();

            let stored_mipmap = cache.entry(tex_info).or_insert(mipmap_arc);

            Self {
                mapping,
                filename,
                scale,
                invert,
                mipmap: stored_mipmap.clone(),
            }
        }
    }

    pub fn clear_cache() {
        let mut cache = get_texture_cache().lock().unwrap();
        cache.clear();
    }

    pub fn multiply_scale(&mut self, s: Float) {
        self.scale *= s;
    }
}

#[derive(Clone, Debug)]
pub struct SpectrumImageTexture {
    pub base: ImageTextureBase,
    pub spectrum_type: SpectrumType,
}

impl SpectrumImageTexture {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        mapping: TextureMapping2D,
        filename: String,
        filter_options: MIPMapFilterOptions,
        wrap_mode: WrapMode,
        scale: Float,
        invert: bool,
        encoding: ColorEncoding,
        spectrum_type: SpectrumType,
    ) -> Self {
        let base = ImageTextureBase::new(
            mapping,
            filename,
            filter_options,
            wrap_mode,
            scale,
            invert,
            encoding,
        );

        Self {
            base,
            spectrum_type,
        }
    }
}

impl SpectrumTextureTrait for SpectrumImageTexture {
    fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
        let mut c = self.base.mapping.map(ctx);
        c.st[1] = 1. - c.st[1];
        let dst0 = Vector2f::new(c.dsdx, c.dtdx);
        let dst1 = Vector2f::new(c.dsdy, c.dtdy);
        let rgb_unclamp = self.base.scale * self.base.mipmap.filter::<RGB>(c.st, dst0, dst1);
        let rgb = RGB::clamp_zero(&rgb_unclamp);
        if let Some(cs) = self.base.mipmap.get_rgb_colorspace() {
            match self.spectrum_type {
                SpectrumType::Unbounded => {
                    return RGBUnboundedSpectrum::new(&cs, rgb).sample(lambda);
                }
                SpectrumType::Albedo => {
                    return RGBAlbedoSpectrum::new(&cs, rgb).sample(lambda);
                }
                _ => return RGBIlluminantSpectrum::new(&cs, rgb).sample(lambda),
            }
        }
        assert!(rgb[0] == rgb[1] && rgb[1] == rgb[2]);
        SampledSpectrum::new(rgb[0])
    }
}

impl CreateSpectrumTexture for SpectrumImageTexture {
    fn create(
        render_from_texture: Transform,
        parameters: TextureParameterDictionary,
        spectrum_type: SpectrumType,
        loc: FileLoc,
    ) -> Result<SpectrumTexture> {
        let mapping = TextureMapping2D::create(&parameters, &render_from_texture, &loc)?;

        let filename = crate::utils::resolve_filename(&parameters.get_one_string("filename", "")?);
        let scale = parameters.get_one_float("scale", 1.0)?;
        let invert = parameters.get_one_bool("invert", false)?;

        let filter_options = MIPMapFilterOptions::default();
        let wrap_str = parameters.get_one_string("wrap", "repeat")?;
        let wrap_mode = match wrap_str.as_str() {
            "repeat" => WrapMode::Repeat,
            "clamp" => WrapMode::Clamp,
            "black" => WrapMode::Black,
            _ => WrapMode::Repeat,
        };

        let encoding = ColorEncoding::SRGB(SRGBEncoding);

        let tex = SpectrumImageTexture::new(
            mapping,
            filename,
            filter_options,
            wrap_mode,
            scale,
            invert,
            encoding,
            spectrum_type,
        );

        Ok(SpectrumTexture::Image(tex))
    }
}

#[derive(Debug, Clone)]
pub struct FloatImageTexture {
    pub base: ImageTextureBase,
}

impl FloatImageTexture {
    pub fn new(
        mapping: TextureMapping2D,
        filename: String,
        filter_options: MIPMapFilterOptions,
        wrap_mode: WrapMode,
        scale: Float,
        invert: bool,
        encoding: ColorEncoding,
    ) -> Self {
        Self {
            base: ImageTextureBase::new(
                mapping,
                filename,
                filter_options,
                wrap_mode,
                scale,
                invert,
                encoding,
            ),
        }
    }
}

impl FloatTextureTrait for FloatImageTexture {
    fn evaluate(&self, _ctx: &TextureEvalContext) -> Float {
        todo!()
    }
}

impl CreateFloatTexture for FloatImageTexture {
    fn create(
        _render_from_texture: Transform,
        _parameters: TextureParameterDictionary,
        _loc: FileLoc,
        _arena: &Arena,
    ) -> Result<FloatTexture> {
        todo!()
    }
}
