// use crate::utils::{Arena, FileLoc, TextureParameterDictionary};
use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait};
use ptex::Cache;
use ptex_filter::{PtexFilterOptions, PtexFilterType, ptex_filter_create};
use shared::Float;
use shared::core::color::{ColorEncoding, RGB};
use shared::core::texture::{SpectrumType, TextureEvalContext};
use shared::spectra::{SampledSpectrum, SampledWavelengths};
use std::sync::OnceLock;

struct PtexCache(Cache);
unsafe impl Sync for PtexCache {}
unsafe impl Send for PtexCache {}

static PTEX_CACHE: OnceLock<PtexCache> = OnceLock::new();

fn get_ptex_cache() -> &'static Cache {
    PTEX_CACHE
        .get_or_init(|| {
            let max_files = 100;
            let max_mem = 1 << 32;
            let premultiply = true;
            let cache = Cache::new(max_files, max_mem, premultiply);
            PtexCache(cache)
        })
        .0
}

#[derive(Debug, Clone)]
pub struct PtexTextureBase {
    pub valid: bool,
    pub filename: String,
    pub encoding: ColorEncoding,
    pub scale: Float,
}

impl PtexTextureBase {
    pub fn new(filename: String, encoding: ColorEncoding, scale: Float) -> Self {
        let cache: &mut Cache = get_ptex_cache();

        // Attempt to get the texture to verify it exists and is valid
        let (valid, num_channels) = match cache.get(&filename) {
            Ok(tex) => {
                let nc = tex.num_channels();
                (nc == 1 || nc == 3, nc)
            }
            Err(e) => {
                log::error!("Ptex Error for {}: {}", filename, e);
                (false, 0)
            }
        };

        if !valid && num_channels != 0 {
            log::error!(
                "{}: only 1 and 3 channel ptex textures are supported",
                filename
            );
        }

        Self {
            filename,
            encoding,
            scale,
            valid,
        }
    }

    pub fn sample_texture(&self, ctx: &TextureEvalContext) -> Option<RGB> {
        if !self.valid {
            return None;
        }

        let mut result = [0.0; 3];
        let nc = self.ptex_handle.eval(
            &mut result,
            ctx.face_index,
            ctx.uv[0],
            ctx.uv[1],
            ctx.dudx,
            ctx.dvdx,
            ctx.dudy,
            ctx.dvdy,
        );
        let cache = get_ptex_cache();
        let texture = match cache.get(&self.filename) {
            Ok(t) => t,
            Err(e) => {
                log::error!("Ptex cache lookup failed for {}: {}", self.filename, e);
                return None;
            }
        };

        let nc = texture.num_channels();
        let mut result = [0.0; 4];
        unsafe {
            let opts = PtexFilterOptions {
                filter: PtexFilterType::BSpline,
                lerp: 1,
                sharpness: 0.0,
                noedgeblend: 0,
                // __structSize: std::mem::size_of::<PtexFilterOptions>() as i32,
            };

            let filter_ptr = ptex_filter_create(texture.as_ptr(), &opts);
            if filter_ptr.is_null() {
                return None;
            }

            // Evaluate
            (*filter_ptr).eval(
                result.as_mut_ptr(),
                0,
                texture.num_channels(),
                ctx.face_index as i32,
                ctx.uv[0],
                ctx.uv[1],
                ctx.dudx,
                ctx.dvdx,
                ctx.dudy,
                ctx.dvdy,
            );

            // Manually release the C++ object
            (*filter_ptr).release();
        }

        let mut rgb = RGB::new(result[0], result[1], result[2]);

        if self.encoding != ColorEncoding::Linear {
            // Convert to 8-bit, process, and convert back
            let r8 = (rgb.r * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
            let g8 = (rgb.g * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
            let b8 = (rgb.b * 255.0 + 0.5).clamp(0.0, 255.0) as u8;

            rgb = self.encoding.to_linear_rgb([r8, g8, b8]);
        }

        Some(rgb * self.scale)
    }
}

#[derive(Debug, Clone)]
pub struct FloatPtexTexture {
    pub base: PtexTextureBase,
}

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

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

impl SpectrumTextureTrait for SpectrumPtexTexture {
    fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
        todo!()
    }
}
