use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
use crate::core::film::Film;
use crate::core::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction};
use crate::core::medium::Medium;
use crate::core::pbrt::{Float, PI};
use crate::core::sampler::CameraSample;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square};
use num_traits::Float as NumFloat;

#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Mapping {
    EquiRectangular,
    EqualArea,
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct SphericalCamera {
    pub mapping: Mapping,
    pub base: CameraBase,
}

impl CameraTrait for SphericalCamera {
    fn base(&self) -> &CameraBase {
        &self.base
    }

    fn generate_ray(
        &self,
        sample: CameraSample,
        _lamdba: &SampledWavelengths,
    ) -> Option<CameraRay> {
        // Compute spherical camera ray direction
        let film = self.get_film();
        let mut uv = Point2f::new(
            sample.p_film.x() / film.full_resolution().x() as Float,
            sample.p_film.y() / film.full_resolution().y() as Float,
        );
        let dir: Vector3f;
        if self.mapping == Mapping::EquiRectangular {
            let theta = PI * uv[1];
            let phi = 2. * PI * uv[0];
            dir = spherical_direction(theta.sin(), theta.cos(), phi);
        } else {
            // Compute ray direction using equal area mapping
            uv = wrap_equal_area_square(&mut uv);
            dir = equal_area_square_to_sphere(uv);
        }
        core::mem::swap(&mut dir.y(), &mut dir.z());

        let ray = Ray::new(
            Point3f::new(0., 0., 0.),
            dir,
            Some(self.sample_time(sample.time)),
            &self.base().medium,
        );
        Some(CameraRay {
            ray: self.render_from_camera(&ray, &mut None),
            weight: SampledSpectrum::default(),
        })
    }
}
