const std = @import("std");
const camera = @import("./camera.zig");
const color = @import("./color.zig");
const ray = @import("./ray.zig");
const vec = @import("./vec.zig");
const sphere = @import("./sphere.zig");
const hittable = @import("./hittable.zig");
const hittable_list = @import("./hittable_list.zig");

const Camera = camera.Camera;
const Color = vec.Color;
const Hittable = hittable.Hittable;
const HittableList = hittable_list.HittableList;
const Point3 = vec.Point3;
const Ray = ray.Ray;
const Sphere = sphere.Sphere;
const Vec3 = vec.Vec3;
const random_unit_vector = vec.random_unit_vector;
const random_in_unit_sphere = vec.random_in_unit_sphere;
const info = std.log.info;
const write_color = color.write_color;

fn ray_color(r: Ray, world: Hittable, depth: isize) Color {
    // If we've exceeded the ray bounce limit, no more light is gathered.
    if (depth <= 0) return Color.init(0, 0, 0);

    var hit = world.hit(r, 0.0001, std.math.inf(f32));
    if (hit) |rec| {
        const target = rec.p.add(rec.normal.add(random_unit_vector()));
        return ray_color(Ray.init(rec.p, target.sub(rec.p)), world, depth - 1).scale(0.5);
    }
    var unit_direction = r.direction().unit();
    const t = 0.5 * (unit_direction.y() + 1);
    return Color.init(1, 1, 1).scale(1 - t).add(Color.init(0.5, 0.7, 1.0).scale(t));
}

pub fn main() anyerror!void {
    // Image
    const aspect_ratio: f32 = 16.0 / 9.0;
    const image_width = 400;
    const image_height = @floatToInt(isize, @intToFloat(f32, image_width) / aspect_ratio);
    const samples_per_pixel = 100;
    const max_depth = 50;

    // World
    var tmp_world = HittableList.init();
    try tmp_world.add(Hittable{ .sphere = Sphere.init(Point3.init(0, 0, -1), 0.5) });
    try tmp_world.add(Hittable{ .sphere = Sphere.init(Point3.init(0, -100.5, -1), 100) });
    const world = Hittable{ .hittable_list = tmp_world };

    // Camera
    const cam = Camera.init();

    // Render
    const stdout = std.io.getStdOut();

    info("Writing image {d}x{d}", .{ image_width, image_height });
    try stdout.writer().print("P3\n{d} {d}\n255\n", .{ image_width, image_height });

    var prng = std.rand.DefaultPrng.init(0);
    const rand = prng.random();
    var j: isize = image_height - 1;
    while (j >= 0) : (j -= 1) {
        info("Scanlines remaining: {d}", .{j});

        var i: isize = 0;
        while (i < image_width) : (i += 1) {
            var pixel_color = Color.init(0, 0, 0);
            var s: isize = 0;
            while (s < samples_per_pixel) : (s += 1) {
                const u = (@intToFloat(f32, i) + rand.float(f32)) / @intToFloat(f32, image_width - 1);
                const v = (@intToFloat(f32, j) + rand.float(f32)) / @intToFloat(f32, image_height - 1);
                const r = cam.get_ray(u, v);
                pixel_color = pixel_color.add(ray_color(r, world, max_depth));
            }
            try write_color(pixel_color, samples_per_pixel);
        }
    }
    info("Done", .{});
}

test {
    // Run tests in imported source.
    std.testing.refAllDecls(@This());
}
