use crate::camera::Camera;
use crate::mesh::Mesh;
use glam::{Mat4, Vec3, Vec4};

pub struct Ray
{
    pub origin: Vec3,
    pub direction: Vec3,
}

impl Ray
{
    pub fn from_screen_position(
        screen_x: f32,
        screen_y: f32,
        screen_width: u32,
        screen_height: u32,
        camera: &Camera,
    ) -> Self
    {
        let ndc_x = (2.0 * screen_x) / screen_width as f32 - 1.0;
        let ndc_y = 1.0 - (2.0 * screen_y) / screen_height as f32;

        let clip_coords = Vec4::new(ndc_x, ndc_y, -1.0, 1.0);

        let view_matrix = camera.view_matrix();
        let projection_matrix = camera.projection_matrix();
        let inv_projection = projection_matrix.inverse();
        let inv_view = view_matrix.inverse();

        let eye_coords = inv_projection * clip_coords;
        let eye_coords = Vec4::new(eye_coords.x, eye_coords.y, -1.0, 0.0);

        let world_coords = inv_view * eye_coords;
        let direction = Vec3::new(world_coords.x, world_coords.y, world_coords.z).normalize();

        Ray {
            origin: camera.position,
            direction,
        }
    }

    pub fn intersects_mesh(&self, mesh: &Mesh, transform: &Mat4) -> Option<f32>
    {
        let inv_transform = transform.inverse();
        let local_origin = inv_transform.transform_point3(self.origin);
        let local_direction = inv_transform.transform_vector3(self.direction).normalize();

        let mut closest_distance = f32::MAX;
        let mut hit = false;

        for triangle_idx in (0..mesh.num_indices).step_by(3)
        {
            let distance =
                self.intersects_triangle_local(local_origin, local_direction, mesh, triangle_idx);

            if let Some(d) = distance
            {
                if d < closest_distance
                {
                    closest_distance = d;
                    hit = true;
                }
            }
        }

        if hit
        {
            Some(closest_distance)
        }
        else
        {
            None
        }
    }

    fn intersects_triangle_local(
        &self,
        local_origin: Vec3,
        local_direction: Vec3,
        _mesh: &Mesh,
        _triangle_idx: u32,
    ) -> Option<f32>
    {
        None
    }
}
