package eu.jonahbauer.raytracing.scene.hittable2d;

import eu.jonahbauer.raytracing.math.Range;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.material.Material;
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 abstract class Hittable2D implements Hittable {
    protected final @NotNull Vec3 origin;
    protected final @NotNull Vec3 u;
    protected final @NotNull Vec3 v;
    private final @NotNull Material material;

    // internal
    private final @NotNull Vec3 normal;
    private final double d;
    private final @NotNull Vec3 w;

    protected Hittable2D(@NotNull Vec3 origin, @NotNull Vec3 u, @NotNull Vec3 v, @NotNull Material material) {
        this.origin = Objects.requireNonNull(origin);
        this.u = Objects.requireNonNull(u);
        this.v = Objects.requireNonNull(v);
        this.material = Objects.requireNonNull(material);

        var n = u.cross(v);
        if (n.squared() < 1e-8) throw new IllegalArgumentException();
        this.normal = n.unit();
        this.d = origin.times(normal);
        this.w = n.div(n.squared());
    }

    @Override
    public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
        var denominator = ray.direction().times(normal);
        if (Math.abs(denominator) < 1e-8) return Optional.empty(); // parallel

        var t = (d - ray.origin().times(normal)) / denominator;
        if (!range.surrounds(t)) return Optional.empty();

        var position = ray.at(t);
        var p = position.minus(origin);

        var alpha = w.times(p.cross(v));
        var beta = w.times(u.cross(p));
        if (!isInterior(alpha, beta)) return Optional.empty();

        var frontFace = denominator < 0;
        return Optional.of(new HitResult(t, position, frontFace ? normal : normal.neg(), material, frontFace));
    }

    protected abstract boolean isInterior(double alpha, double beta);
}
