use nalgebra_glm::Mat4;

#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct PerspectiveCamera {
    pub aspect_ratio: Option<f32>,
    pub y_fov_rad: f32,
    pub z_far: Option<f32>,
    pub z_near: f32,
}

impl Default for PerspectiveCamera {
    fn default() -> Self {
        Self {
            aspect_ratio: None,
            y_fov_rad: 45.0_f32.to_radians(),
            z_far: None,
            z_near: 0.01,
        }
    }
}

impl PerspectiveCamera {
    pub fn matrix(&self) -> Mat4 {
        let aspect_ratio = self.aspect_ratio.unwrap_or(16.0 / 9.0);
        self.matrix_with_aspect(aspect_ratio)
    }

    pub fn matrix_with_aspect(&self, aspect_ratio: f32) -> Mat4 {
        match self.z_far {
            Some(z_far) => reverse_z_perspective(aspect_ratio, self.y_fov_rad, self.z_near, z_far),
            None => infinite_reverse_z_perspective(aspect_ratio, self.y_fov_rad, self.z_near),
        }
    }
}

fn infinite_reverse_z_perspective(aspect_ratio: f32, y_fov_rad: f32, z_near: f32) -> Mat4 {
    let f = 1.0 / (y_fov_rad / 2.0).tan();
    Mat4::new(
        f / aspect_ratio,
        0.0,
        0.0,
        0.0,
        0.0,
        f,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        z_near,
        0.0,
        0.0,
        -1.0,
        0.0,
    )
}

fn reverse_z_perspective(aspect_ratio: f32, y_fov_rad: f32, z_near: f32, z_far: f32) -> Mat4 {
    let f = 1.0 / (y_fov_rad / 2.0).tan();
    Mat4::new(
        f / aspect_ratio,
        0.0,
        0.0,
        0.0,
        0.0,
        f,
        0.0,
        0.0,
        0.0,
        0.0,
        z_near / (z_far - z_near),
        z_near * z_far / (z_far - z_near),
        0.0,
        0.0,
        -1.0,
        0.0,
    )
}
