package basics.math.algebra;

public class Vector {
    public double x;
    public double y;
    public double z;

    public Vector(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public static Vector origin() {
        return new Vector(0, 0, 0);
    }

    public static Vector diagonal(double k) {
        return new Vector(k, k, k);
    }

    public Vector add(Vector other) {
        return new Vector(this.x + other.x, this.y + other.y, this.z + other.z);
    }

    public Vector sub(Vector other) {
        return new Vector(this.x - other.x, this.y - other.y, this.z - other.z);
    }

    public Vector scale(double scalar) {
        return new Vector(this.x * scalar, this.y * scalar, this.z * scalar);
    }

    public double dot(Vector other) {
        return this.x * other.x + this.y * other.y + this.z * other.z;
    }

    public double length() {
        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
    }

    public Vector normalize() {
        return this.scale(1.0 / this.length());
    }

    @Override
    public String toString() {
        return String.format("{ %f, %f, %f }", this.x, this.y, this.z);
    }

    public Vector shuffle(SwizzleMask mask) {
        Vector result = new Vector(x, y, z);

        switch (mask) {
            case XYZ -> {
            }
            case YXZ -> {
                result.x = this.y;
                result.y = this.x;
                result.z = this.z;
            }
            case ZXY -> {
                result.x = this.z;
                result.y = this.x;
                result.z = this.y;
            }
            case ZYX -> {
                result.x = this.z;
                result.y = this.y;
                result.z = this.x;
            }
            case XZY -> {
                result.x = this.x;
                result.y = this.z;
                result.z = this.y;
            }
            case YZX -> {
                result.x = this.y;
                result.y = this.z;
                result.z = this.x;
            }
        }

        return result;
    }

    public Vector negate() {
        return new Vector(-x, -y, -z);
    }

    public Vector cross(Vector other) {
        return new Vector(
                this.y * other.z - this.z * other.y,
                this.z * other.x - this.x * other.z,
                this.x * other.y - this.y * other.x
        );
    }

    public Vector project(Vector other) {
        return this.scale(other.dot(this) / this.dot(this));
    }

    public Vector reflect(Vector normal) {
        return this.sub(normal.scale(this.dot(normal) * 2.0));
    }

    // based on https://bheisler.github.io/post/writing-raytracer-in-rust-part-3/
    public Vector refract(Vector normal, Vector incoming, double ior) {

        double iDotN = incoming.dot(normal);
        double etaI = 1;
        double etaT = ior;

        Vector refraction_n = normal;

        if (iDotN < 0) {
            iDotN = -iDotN;
        } else {
            refraction_n = normal.negate();
            etaT = 1.0;
            etaI = ior;
        }

        double eta = etaI / etaT;
        double k = 1.0 - (eta * eta) * (1.0 - iDotN * iDotN);

        if (k < 0.0) {
            return  Vector.origin();
        } else {
            return incoming.add(refraction_n.scale(iDotN)).scale(eta).sub(refraction_n).scale(Math.sqrt(k));
        }
    }

    public Vector lerp(Vector other, double k) {
        return this.scale(k).add(other.scale(1.0 - k));
    }

    public double distance(Vector vector) {
        return this.sub(vector).length();
    }


    public enum SwizzleMask {
        XYZ,
        YXZ,
        ZXY,
        ZYX,
        XZY,
        YZX,
    }
}
