mod material;
mod light;
mod object;
mod structs;
mod consts;

use object::*;
use crate::consts::*;
use crate::structs::*;
use crate::material::*;
use image::{ColorType, ImageFormat};
use crate::light::Light;

fn default_cam() -> Cam {
    Cam::new_pointing(Y*3. - X*5., O, 0.5)
}

fn default_scene1() -> Scene {
    let s0 = WithMaterial::new(
        Sphere::new_xyz(0., 0., 0., 1.),
        RED
    );
    let s1 = WithMaterial::new(
        Sphere::new_xyz(0., 1., 1., 0.5),
        MIRROR
    );
    let spheres = Union::new(s0, s1);
    let floor = WithMaterial::new(
        Plane::new_xyz(0., 1., 0., 2.),
        WHITE
    );
    let backwall = WithMaterial::new(
        Plane::new_xyz(-1., 0., 0., 2.),
        BLUE
    );
    let sidewalls = WithMaterial::new(
        Union::new(
            Plane::new_xyz(0., 0., -1., 2.5),
            Plane::new_xyz(0., 0., 1., 2.5)
        ),
        GREEN
    );
    let walls = Union::new(
        Union::new(floor, backwall),
        sidewalls
    );
    let scene = Union::new(spheres, walls);
    let light = WithMaterial::new(
        Plane::new_xyz(0., -1., 0., 8.),
        LIGHTSOURCE
    );
    let scene = Union::new(scene, light);
    Scene::new(scene)
}

fn default_scene2() -> Scene {
    let s1 = Sphere::new_xyz(0., 0., 0., 1.);
    let scaled = AffineTransform::new_linear(s1, scale(1.2));
    let moved = AffineTransform::new_translate(scaled, Vec3::new(3., 1., 1.));
    let sphere = WithMaterial::new(moved, BLUE);
    let wall = WithMaterial::new(Plane::new_xyz(0., 0., -1., 2.5), GREEN);
    let backwall = WithMaterial::new(Plane::new_xyz(-1., 0., 0., 3.), WHITE);
    let mirror = WithMaterial::new(Plane::new_xyz(0., 1., 0., 2.), MIRROR);
    let light = WithMaterial::new(Sphere::new_xyz(-1., 1.5, 1., 0.5), LIGHTSOURCE);
    Scene::new(Union::new(Union::new(sphere, Union::new(wall, backwall)), Union::new(light, mirror)))
}

fn default_scene3() -> Scene {
    //TODO fix this scene
    let s1 = WithMaterial::new(Sphere::new_xyz(4., 0., 0., 1.), WHITE);
    let s2 = WithMaterial::new(Sphere::new_xyz(3., 1., 1., 0.5), GREEN);
    let navion = WithMaterial::new(Plane::new_xyz(0., 1., -1., 3.), BLUE);
    let backwall = WithMaterial::new(Plane::new_xyz(-1., -1., -0.5, 8.), RED);
    let boiboite = Cuboid::new_xyz(4., 1.1, -1.1, 0.5, 0.275, 0.275);
    let decal = AffineTransform::new_translate(boiboite, Vec3::new(-0.25, 0., 0.5));
    let nope = Exclusion::new(s1, Sphere::new_xyz(3.75, 0.75, 0.75, 1.));
    let walls = Union::new(navion, backwall);
    let spheres = Union::new(AffineTransform::new_linear(nope, scale(0.75)), s2);
    let scene = Union::new(spheres, Union::new(walls, decal));
    let light = WithMaterial::new(Plane::new_xyz(0., -1., 0., 8.), BRIGHT_AF_LIGHTSOURCE);
    let scene = WithLight::new_one(scene, Light::new(Y*3.-X*5., ColorVec::new([2., 3., 4., 0.])));
    Scene::new(Union::new(scene, light))
}

fn main() {
    // get scene and camera
    let scene = default_scene2();
    let cam = default_cam();

    // get stats on the scene we're about to render
    let total_rpp = (RAYS_PER_PIXEL * SUPERSAMPLING * SUPERSAMPLING) as u64;
    let lights = scene.get_lights().len() as u32;
    let nodes = scene.node_count();
    let min_rays = IMG_SIZE as u64 * RAYS_PER_PIXEL as u64 * SUPERSAMPLING as u64;
    let max_rays = min_rays * MAX_BOUNCES as u64 * (1+lights) as u64;
    println!("Image size: {}x{} ({} pixels)", IMG_WIDTH, IMG_HEIGHT, IMG_SIZE);
    println!("Rpp: {}, {}x supersampling ({} total rays per image pixel)", RAYS_PER_PIXEL, SUPERSAMPLING, total_rpp);
    println!("Max bounces: {}, dist: {}, steps: {}", MAX_BOUNCES, MAX_DIST, MAX_STEPS);
    println!("Lights: {}, Nodes: {}", lights, nodes);
    println!("Threads: {}, {} slices per thread", THREAD_COUNT, SLICES_PER_THREAD);
    println!("Total rays: min {} max {}", min_rays, max_rays);

    // generate image and save it
    let data = cam.render(&scene);
    image::save_buffer_with_format(
        "a.png",
        &data,
        IMG_WIDTH as u32,
        IMG_HEIGHT as u32,
        ColorType::Rgb8,
        ImageFormat::Png
    ).unwrap();
}
