package eu.jonahbauer.raytracing.scene.hittable3d;

import eu.jonahbauer.raytracing.render.material.Material;
import eu.jonahbauer.raytracing.math.BoundingBox;
import eu.jonahbauer.raytracing.math.Range;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.scene.HitResult;
import eu.jonahbauer.raytracing.scene.Hittable;
import org.jetbrains.annotations.NotNull;

import java.util.Objects;
import java.util.Optional;

public record Sphere(@NotNull Vec3 center, double radius, @NotNull Material material) implements Hittable {

    public Sphere {
        Objects.requireNonNull(center, "center");
        Objects.requireNonNull(material, "material");
        if (radius <= 0 || !Double.isFinite(radius)) throw new IllegalArgumentException("radius must be positive");
    }

    public Sphere(double x, double y, double z, double r, @NotNull Material material) {
        this(new Vec3(x, y, z), r, material);
    }

    @Override
    public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
        var oc = ray.origin().minus(center());

        var a = ray.direction().squared();
        var h = ray.direction().times(oc);
        var c = oc.squared() - radius * radius;

        var discriminant = h * h - a * c;
        if (discriminant < 0) return Optional.empty();

        var sd = Math.sqrt(discriminant);

        double t = (- h - sd) / a;
        if (!range.surrounds(t)) t = (- h + sd) / a;
        if (!range.surrounds(t)) return Optional.empty();

        var position = ray.at(t);
        var normal = position.minus(center);
        var frontFace = normal.times(ray.direction()) < 0;
        return Optional.of(new HitResult(t, position, frontFace ? normal : normal.times(-1), material, frontFace));
    }

    @Override
    public @NotNull Optional<BoundingBox> getBoundingBox() {
        return Optional.of(new BoundingBox(
                center.minus(radius, radius, radius),
                center.plus(radius, radius, radius)
        ));
    }

    public @NotNull Sphere withCenter(@NotNull Vec3 center) {
        return new Sphere(center, radius, material);
    }

    public @NotNull Sphere withCenter(double x, double y, double z) {
        return withCenter(new Vec3(x, y, z));
    }

    public @NotNull Sphere withRadius(double radius) {
        return new Sphere(center, radius, material);
    }
}
