use std::f32::consts::PI;

use rand;
use rand::Rng;

use crate::ray::Ray;
use crate::vec3::cross;
use crate::vec3::Vec3;

fn random_in_unit_disk() -> Vec3 {
    let mut rng = rand::thread_rng();
    let v = Vec3::new(1., 1., 0.);
    loop {
        let p = 2. * Vec3::new(rng.gen(), rng.gen(), 0.) - v;
        if p.squared_length() < 1. {
            return p;
        }
    }
}

pub struct Camera {
    origin: Vec3,
    lower_left_corner: Vec3,
    horizontal: Vec3,
    vertical: Vec3,
    u: Vec3,
    v: Vec3,
    lens_radius: f32,
    time_min: f32,
    time_max: f32,
}

impl Camera {
    // Parameters match the example C++ code.
    #[allow(clippy::too_many_arguments)]
    // vfov is top to bottom in degrees
    pub fn new(
        lookfrom: Vec3,
        lookat: Vec3,
        vup: Vec3,
        vfov: f32,
        aspect: f32,
        aperture: f32,
        focus_dist: f32,
        time_min: f32,
        time_max: f32,
    ) -> Camera {
        let theta = vfov * PI / 180.;
        let half_height = (theta / 2.).tan();
        let half_width = aspect * half_height;
        let origin = lookfrom;
        let w = (lookfrom - lookat).unit_vector();
        let u = cross(vup, w).unit_vector();
        let v = cross(w, u);
        Camera {
            lower_left_corner: origin
                - half_width * focus_dist * u
                - half_height * focus_dist * v
                - focus_dist * w,
            horizontal: 2. * half_width * focus_dist * u,
            vertical: 2. * half_height * focus_dist * v,
            origin,
            u: cross(vup, w).unit_vector(),
            v: cross(w, u),
            lens_radius: aperture / 2.,
            time_min,
            time_max,
        }
    }

    pub fn get_ray(&self, u: f32, v: f32) -> Ray {
        let mut rng = rand::thread_rng();
        let rd = self.lens_radius * random_in_unit_disk();
        let offset = self.u * rd.x + self.v * rd.y;
        let time = self.time_min + rng.gen::<f32>() * (self.time_max - self.time_min);
        Ray::new(
            self.origin + offset,
            self.lower_left_corner + self.horizontal * u + self.vertical * v - self.origin - offset,
            time,
        )
    }
}
