use std::time::Instant;

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

use rtchallenge::{
    camera::{Camera, RenderStrategy},
    float::consts::PI,
    lights::PointLight,
    materials::Material,
    matrices::Matrix4x4,
    shapes::Shape,
    transformations::view_transform,
    tuples::{Color, Tuple},
    world::World,
    WHITE,
};

/// End of chapter 9 challenge.
#[derive(StructOpt, Debug)]
#[structopt(name = "eoc9")]
struct Opt {
    /// Strategy for casting rays into image.
    #[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,
    /// Rendered image width in pixels.
    #[structopt(short, long, default_value = "2560")]
    width: usize,
    /// Rendered image height in pixels.
    #[structopt(short, long, default_value = "1440")]
    height: usize,
}

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

    let light_position = Tuple::point(-5., 5., -5.);
    let light_color = WHITE;
    let light1 = PointLight::new(light_position, light_color);
    let light_position = Tuple::point(5., 5., -5.);
    let light_color = Color::new(0.2, 0.2, 0.6);
    let light2 = PointLight::new(light_position, light_color);
    let light_position = Tuple::point(0., 2., -5.);
    let light_color = Color::new(0.2, 0.2, 0.1);
    let light3 = PointLight::new(light_position, light_color);

    let mut camera = Camera::new(opt.width, opt.height, PI / 4.);
    let from = Tuple::point(0., 1.5, -5.);
    let to = Tuple::point(0., 1., 0.);
    let up = Tuple::point(0., 1., 0.);
    camera.set_transform(view_transform(from, to, up));
    camera.render_strategy = opt.render_strategy;
    camera.samples_per_pixel = opt.samples;

    let mut floor = Shape::plane();
    floor.material = Material {
        color: Color::new(1., 0.2, 0.2).into(),
        specular: 0.,
        ..Material::default()
    };
    let mut ceiling = Shape::plane();
    ceiling.set_transform(Matrix4x4::translation(0., 6., 0.) * Matrix4x4::rotation_x(PI));
    ceiling.material = Material {
        color: Color::new(0.6, 0.6, 0.8).into(),
        specular: 0.2,
        ..Material::default()
    };

    let mut middle = Shape::sphere();
    middle.set_transform(Matrix4x4::translation(-0.5, 0.5, 0.5));
    middle.material = Material {
        color: Color::new(0.1, 1., 0.5).into(),
        diffuse: 0.7,
        specular: 0.3,
        ..Material::default()
    };

    let mut right = Shape::sphere();
    right.set_transform(Matrix4x4::translation(1.5, 0.5, -0.5) * Matrix4x4::scaling(0.5, 0.5, 0.5));
    right.material = Material {
        color: Color::new(1., 1., 1.).into(),
        diffuse: 0.7,
        specular: 0.0,
        ..Material::default()
    };

    let mut left = Shape::sphere();
    left.set_transform(
        Matrix4x4::translation(-1.5, 0.33, -0.75) * Matrix4x4::scaling(0.33, 0.33, 0.33),
    );
    left.material = Material {
        color: Color::new(1., 0.8, 0.1).into(),
        diffuse: 0.7,
        specular: 0.3,
        ..Material::default()
    };

    let mut world = World::default();
    world.lights = vec![light1, light2, light3];
    world.objects = vec![floor, ceiling, middle, right, left];

    let image = camera.render(&world);

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