const vec = @import("./vec.zig");
const Ray = @import("./ray.zig").Ray;
const HitRecord = @import("./hittable.zig").HitRecord;

const Vec3 = vec.Vec3;
const Color = vec.Color;
const random_unit_vector = vec.random_unit_vector;
const random_in_unit_sphere = vec.random_in_unit_sphere;

pub const Material = union(enum) {
    labertian: Labertian,
    metal: Metal,

    pub fn scatter(material: Material, r_in: Ray, rec: HitRecord) ?ScatterRec {
        return switch (material) {
            .labertian => |labertian| labertian.scatter(r_in, rec),
            .metal => |metal| metal.scatter(r_in, rec),
        };
    }
};

pub const ScatterRec = struct { attenuation: Color, scattered: Ray };

pub const Labertian = struct {
    albedo: Vec3,
    pub fn scatter(labertian: Labertian, r_in: Ray, rec: HitRecord) ?ScatterRec {
        _ = r_in;
        var scatter_direction = rec.normal.add(random_unit_vector());
        if (scatter_direction.near_zero()) {
            scatter_direction = rec.normal;
        }
        return ScatterRec{
            .scattered = Ray.init(rec.p, scatter_direction),
            .attenuation = labertian.albedo,
        };
    }
};

pub const Metal = struct {
    albedo: Vec3,
    fuzz: f32,

    pub fn scatter(metal: Metal, r_in: Ray, rec: HitRecord) ?ScatterRec {
        const reflected = reflect(r_in.direction().unit(), rec.normal);
        const scattered = Ray.init(rec.p, reflected.add(random_in_unit_sphere().scale(metal.fuzz)));
        const attenuation = metal.albedo;
        if (scattered.direction().dot(rec.normal) > 0) {
            return ScatterRec{
                .scattered = scattered,
                .attenuation = attenuation,
            };
        } else {
            return null;
        }
    }
    fn reflect(v: Vec3, n: Vec3) Vec3 {
        return v.sub(n.scale(v.dot(n) * 2));
    }
};
