use std::time::Instant;

use anyhow::Result;
use structopt::StructOpt;

use rtchallenge::{
    camera::{Camera, RenderStrategy},
    float::consts::PI,
    lights::PointLightBuilder,
    materials::MaterialBuilder,
    matrices::Matrix4x4,
    shapes::{Geometry, ShapeBuilder},
    transformations::view_transform,
    tuples::Tuple,
    world::WorldBuilder,
    Float, WHITE,
};

/// Experimenting with balls.
#[derive(StructOpt, Debug)]
#[structopt(name = "balls")]
struct Opt {
    #[structopt(long, default_value = "rayon")]
    render_strategy: RenderStrategy,
    /// Number of samples per pixel.  0 renders from the center of the pixel, 1 or more samples N
    /// times randomly across the pixel.
    #[structopt(short, long, default_value = "0")]
    samples: usize,
}

fn main() -> Result<()> {
    let start = Instant::now();
    let opt = Opt::from_args();
    let width = 2560;
    let height = 1440;

    let light1 = PointLightBuilder::default()
        .position(Tuple::point(-5., 5., -5.))
        .intensity(WHITE)
        .build()?;
    let light2 = PointLightBuilder::default()
        .position(Tuple::point(5., 5., -5.))
        .intensity([0.2, 0.2, 0.6])
        .build()?;
    let light3 = PointLightBuilder::default()
        .position(Tuple::point(0., 2., -5.))
        .intensity([0.2, 0.2, 0.1])
        .build()?;

    let ball_size = 0.5;
    let num_per_axis = 3;
    let center_to_center = 4. * ball_size;
    let from = Tuple::point(
        -5. * center_to_center,
        5. * center_to_center,
        -4. * center_to_center,
    );
    let to = Tuple::point(
        num_per_axis as Float * center_to_center / 2.,
        0., //num_per_axis as Float * center_to_center / 2.,
        num_per_axis as Float * center_to_center / 2.,
    );
    let up = Tuple::point(0., 1., 0.);
    let mut camera = Camera::new(width, height, PI / 6.);
    camera.set_transform(view_transform(from, to, up));
    camera.render_strategy = opt.render_strategy;
    camera.samples_per_pixel = opt.samples;

    let floor = ShapeBuilder::default()
        .geometry(Geometry::Plane)
        .transform(Matrix4x4::translation(0., -ball_size, 0.))
        .material(
            MaterialBuilder::default()
                .color([0.2, 0.2, 0.2])
                .specular(0.)
                .build()?,
        )
        .build()?;
    let mut objects = vec![floor];

    for z in 0..num_per_axis {
        for y in 0..num_per_axis {
            for x in 0..num_per_axis {
                objects.push(
                    ShapeBuilder::default()
                        .transform(
                            Matrix4x4::translation(
                                x as Float * center_to_center,
                                y as Float * center_to_center,
                                z as Float * center_to_center,
                            ) * Matrix4x4::scaling(ball_size, ball_size, ball_size),
                        )
                        .material(
                            MaterialBuilder::default()
                                .color([0.1, 1., 0.5])
                                .ambient(y as Float / 4.)
                                .diffuse(x as Float / 4.)
                                .specular(z as Float / 4.)
                                .build()?,
                        )
                        .build()?,
                );
            }
        }
    }

    let world = WorldBuilder::default()
        .lights(vec![light1, light2, light3])
        .objects(objects)
        .build()?;

    let image = camera.render(&world);

    let path = "/tmp/balls.png";
    println!("saving output to {}", path);
    image.write_to_file(path)?;
    println!("Render time {:.3} seconds", start.elapsed().as_secs_f32());
    Ok(())
}
