use std::sync::Arc;

use image;
use rand;
use rand::Rng;

use crate::camera::Camera;
use crate::constant_medium::ConstantMedium;
use crate::cuboid::Cuboid;
use crate::hitable::Hit;
use crate::hitable_list::HitableList;
use crate::kdtree::KDTree;
use crate::material::Dielectric;
use crate::material::DiffuseLight;
use crate::material::Lambertian;
use crate::material::Material;
use crate::material::Metal;
use crate::moving_sphere::MovingSphere;
use crate::noise::perlin::Perlin;
use crate::noise::NoiseType;
use crate::rect::XZRect;
use crate::renderer::Opt;
use crate::renderer::Scene;
use crate::rotate::RotateY;
use crate::sphere::Sphere;
use crate::texture::ConstantTexture;
use crate::texture::ImageTexture;
use crate::texture::NoiseTexture;
use crate::translate::Translate;
use crate::vec3::Vec3;

pub fn new(opt: &Opt) -> Scene {
    let lookfrom = Vec3::new(478., 278., -600.);
    let lookat = Vec3::new(278., 278., 0.);
    let dist_to_focus = 10.;
    let aperture = 0.0;
    let t_min = 0.;
    let t_max = 1.;
    let camera = Camera::new(
        lookfrom,
        lookat,
        Vec3::new(0., 1., 0.),
        40.,
        opt.width as f32 / opt.height as f32,
        aperture,
        dist_to_focus,
        t_min,
        t_max,
    );

    let nb = 20;

    let rng = &mut rand::thread_rng();
    let mut boxlist: Vec<Box<dyn Hit>> = Vec::with_capacity(10000);
    let mut list: Vec<Box<dyn Hit>> = Vec::new();
    let white: Arc<dyn Material> =
        Arc::new(Lambertian::new(ConstantTexture::new([0.73, 0.73, 0.73])));
    let ground: Arc<dyn Material> =
        Arc::new(Lambertian::new(ConstantTexture::new([0.48, 0.83, 0.53])));
    for i in 0..nb {
        for j in 0..nb {
            let w = 100.;
            let x0 = -1000. + i as f32 * w;
            let z0 = -1000. + j as f32 * w;
            let y0 = 0.;
            let x1 = x0 + w;
            let y1 = 100. * (rng.gen::<f32>() + 0.01);
            let z1 = z0 + w;
            boxlist.push(Box::new(Cuboid::new(
                Vec3::new(x0, y0, z0),
                Vec3::new(x1, y1, z1),
                Arc::clone(&ground),
            )));
        }
    }
    list.push(Box::new(KDTree::new(boxlist, t_min, t_max)));

    let light = DiffuseLight::new(ConstantTexture::new([7., 7., 7.]));
    // Light in ceiling
    list.push(Box::new(XZRect::new(123., 423., 147., 412., 554., light)));

    let center = Vec3::new(400., 400., 200.);
    // Moving brownish ball
    list.push(Box::new(MovingSphere::new(
        center,
        center + Vec3::new(30., 0., 0.),
        50.,
        t_min,
        t_max,
        Lambertian::new(ConstantTexture::new([0.7, 0.3, 0.1])),
    )));

    // Glass ball, lower-left corner
    list.push(Box::new(Sphere::new(
        [260., 150., 45.],
        50.,
        Dielectric::new(1.5),
    )));
    // Metal ball lower-right corner
    list.push(Box::new(Sphere::new(
        [0., 150., 145.],
        50.,
        Metal::new([0.8, 0.8, 0.9], 10.0),
    )));

    // Blue smokey glass ball lower-left
    let boundary: Arc<dyn Hit> =
        Arc::new(Sphere::new([360., 150., 145.], 70., Dielectric::new(1.5)));
    list.push(Box::new(Arc::clone(&boundary)));
    list.push(Box::new(ConstantMedium::new(
        Arc::clone(&boundary),
        0.2,
        ConstantTexture::new([0.2, 0.4, 0.9]),
    )));

    // General white mist over whole scene
    let boundary: Arc<dyn Hit> = Arc::new(Sphere::new([0., 0., 0.], 5000., Dielectric::new(1.5)));
    list.push(Box::new(Arc::clone(&boundary)));
    list.push(Box::new(ConstantMedium::new(
        Arc::clone(&boundary),
        0.0001,
        ConstantTexture::new([1.0, 1.0, 1.0]),
    )));

    // Earth sphere
    let world_image_bytes = include_bytes!("../../images/world.jpg");
    let world = ImageTexture::new(image::load_from_memory(world_image_bytes).unwrap().to_rgb());
    list.push(Box::new(Sphere::new(
        Vec3::new(400., 200., 400.),
        100.,
        Lambertian::new(world),
    )));

    // Perlin noise sphere
    let noise_source = Perlin::new(rng);
    let noise_type = NoiseType::Marble {
        period: Vec3::new(0., 1., 0.),
        power: 4.,
        size: 32,
        scale: 0.5,
    };
    let pertext = NoiseTexture::new(noise_source, noise_type);
    list.push(Box::new(Sphere::new(
        [220., 280., 300.],
        80.,
        Lambertian::new(pertext),
    )));

    // White 'cube' made of 1000 spheres
    let ns = 1000;
    let mut boxlist: Vec<Box<dyn Hit>> = Vec::with_capacity(1000);
    for _ in 0..ns {
        boxlist.push(Box::new(Sphere::new(
            [
                165. * rng.gen::<f32>(),
                165. * rng.gen::<f32>(),
                165. * rng.gen::<f32>(),
            ],
            10.,
            Arc::clone(&white),
        )));
    }
    list.push(Box::new(Translate::new(
        RotateY::new(KDTree::new(boxlist, t_min, t_max), 15.),
        [-100., 270., 395.],
    )));

    Scene {
        camera,
        world: Box::new(HitableList::new(list)),
        subsamples: opt.subsamples,
        num_threads: opt.num_threads,
        width: opt.width,
        height: opt.height,
        ..Default::default()
    }
}
