use rand;
use rand::Rng;

use crate::camera::Camera;
use crate::hitable::Hit;
use crate::hitable_list::HitableList;
use crate::kdtree::KDTree;
use crate::material::Dielectric;
use crate::material::Lambertian;
use crate::material::Material;
use crate::material::Metal;
use crate::renderer::Opt;
use crate::renderer::Scene;
use crate::sphere::Sphere;
use crate::texture::CheckerTexture;
use crate::texture::ConstantTexture;
use crate::texture::EnvMap;
use crate::vec3::Vec3;

pub fn new(opt: &Opt) -> Scene {
    let lookfrom = Vec3::new(13., 2., 3.);
    let lookat = Vec3::new(0., 0., 0.);
    let dist_to_focus = 10.;
    let aperture = 0.1;
    let time_min = 0.;
    let time_max = 1.;
    let camera = Camera::new(
        lookfrom,
        lookat,
        Vec3::new(0., 1., 0.),
        20.,
        opt.width as f32 / opt.height as f32,
        aperture,
        dist_to_focus,
        time_min,
        time_max,
    );
    let ground_color = if opt.use_accel {
        [1.0, 0.4, 0.4]
    } else {
        [0.4, 1.0, 0.4]
    };
    let world: Box<dyn Hit> = if opt.use_accel {
        Box::new(KDTree::new(random_scene(ground_color), time_min, time_max))
    } else {
        Box::new(HitableList::new(random_scene(ground_color)))
    };
    let skybox_bytes = include_bytes!("../../images/envmap.jpg");
    let skybox = image::load_from_memory(skybox_bytes).unwrap().to_rgb();
    Scene {
        camera,
        world,
        subsamples: opt.subsamples,
        adaptive_subsampling: Some(0.5),
        num_threads: opt.num_threads,
        width: opt.width,
        height: opt.height,
        global_illumination: true,
        env_map: Some(EnvMap::new(skybox)),
        ..Default::default()
    }
}

fn random_scene<V>(ground_color: V) -> Vec<Box<dyn Hit>>
where
    V: Into<Vec3>,
{
    let mut rng = rand::thread_rng();
    let checker = true;
    let ground_material: Box<dyn Material> = if checker {
        Box::new(Lambertian::new(CheckerTexture::new(
            ConstantTexture::new([0., 0., 0.]),
            ConstantTexture::new(ground_color),
        )))
    } else {
        Box::new(Lambertian::new(ConstantTexture::new(ground_color)))
    };

    let mut objects: Vec<Box<dyn Hit>> = vec![Box::new(Sphere::new(
        [0., -1000., 0.],
        1000.,
        ground_material,
    ))];
    let mut random = || rng.gen::<f32>();

    for a in -11..11 {
        for b in -11..11 {
            let choose_mat = random();
            let center = Vec3::new(a as f32 + 0.9 * random(), 0.2, b as f32 + 0.9 * random());
            if (center - Vec3::new(4., 0.2, 0.)).length() > 0.9 {
                let sphere: Box<dyn Hit> = if choose_mat < 0.8 {
                    // diffuse
                    Box::new(Sphere::new(
                        center,
                        0.2,
                        Lambertian::new(ConstantTexture::new([
                            random() * random(),
                            random() * random(),
                            random() * random(),
                        ])),
                    ))
                } else if choose_mat < 0.95 {
                    // metal
                    Box::new(Sphere::new(
                        center,
                        0.2,
                        Metal::new(
                            Vec3::new(
                                0.5 * (1. + random()),
                                0.5 * (1. + random()),
                                0.5 * (1. + random()),
                            ),
                            0.5 * random(),
                        ),
                    ))
                } else {
                    // glass
                    Box::new(Sphere::new(center, 0.2, Dielectric::new(1.5)))
                };
                objects.push(sphere);
            };
        }
    }

    let more: Vec<Box<dyn Hit>> = vec![
        Box::new(Sphere::new(
            Vec3::new(0., 1., 0.),
            1.0,
            Dielectric::new(1.5),
        )),
        Box::new(Sphere::new(
            Vec3::new(-4., 1., 0.),
            1.0,
            Lambertian::new(ConstantTexture::new(Vec3::new(0.4, 0.2, 0.1))),
        )),
        Box::new(Sphere::new(
            Vec3::new(4., 1., 0.),
            1.0,
            Metal::new(Vec3::new(0.7, 0.6, 0.5), 0.0),
        )),
    ];
    objects.extend(more);
    objects
}
