// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.math.shape;

import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.codec.validation.ValidationResults;
import com.hypixel.hytale.function.predicate.TriIntObjPredicate;
import com.hypixel.hytale.function.predicate.TriIntPredicate;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.Axis;
import javax.annotation.Nonnull;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.codec.Codec;

public class Box implements Shape
{
    public static final Codec<Box> CODEC;
    public static final Box UNIT;
    @Nonnull
    public final Vector3d min;
    @Nonnull
    public final Vector3d max;
    
    @Nonnull
    public static Box horizontallyCentered(final double width, final double height, final double depth) {
        return new Box(-width / 2.0, 0.0, -depth / 2.0, width / 2.0, height, depth / 2.0);
    }
    
    public Box() {
        this.min = new Vector3d();
        this.max = new Vector3d();
    }
    
    public Box(@Nonnull final Box box) {
        this();
        this.min.assign(box.min);
        this.max.assign(box.max);
    }
    
    public Box(@Nonnull final Vector3d min, @Nonnull final Vector3d max) {
        this();
        this.min.assign(min);
        this.max.assign(max);
    }
    
    public Box(final double xMin, final double yMin, final double zMin, final double xMax, final double yMax, final double zMax) {
        this();
        this.min.assign(xMin, yMin, zMin);
        this.max.assign(xMax, yMax, zMax);
    }
    
    public static Box cube(@Nonnull final Vector3d min, final double side) {
        return new Box(min.x, min.y, min.z, min.x + side, min.y + side, min.z + side);
    }
    
    public static Box centeredCube(@Nonnull final Vector3d center, final double inradius) {
        return new Box(center.x - inradius, center.y - inradius, center.z - inradius, center.x + inradius, center.y + inradius, center.z + inradius);
    }
    
    @Nonnull
    public Box setMinMax(@Nonnull final Vector3d min, @Nonnull final Vector3d max) {
        this.min.assign(min);
        this.max.assign(max);
        return this;
    }
    
    @Nonnull
    public Box setMinMax(@Nonnull final double[] min, @Nonnull final double[] max) {
        this.min.assign(min);
        this.max.assign(max);
        return this;
    }
    
    @Nonnull
    public Box setMinMax(@Nonnull final float[] min, @Nonnull final float[] max) {
        this.min.assign(min);
        this.max.assign(max);
        return this;
    }
    
    @Nonnull
    public Box setEmpty() {
        this.setMinMax(Double.MAX_VALUE, -1.7976931348623157E308);
        return this;
    }
    
    @Nonnull
    public Box setMinMax(final double min, final double max) {
        this.min.assign(min);
        this.max.assign(max);
        return this;
    }
    
    @Nonnull
    public Box union(@Nonnull final Box bb) {
        if (this.min.x > bb.min.x) {
            this.min.x = bb.min.x;
        }
        if (this.min.y > bb.min.y) {
            this.min.y = bb.min.y;
        }
        if (this.min.z > bb.min.z) {
            this.min.z = bb.min.z;
        }
        if (this.max.x < bb.max.x) {
            this.max.x = bb.max.x;
        }
        if (this.max.y < bb.max.y) {
            this.max.y = bb.max.y;
        }
        if (this.max.z < bb.max.z) {
            this.max.z = bb.max.z;
        }
        return this;
    }
    
    @Nonnull
    public Box assign(@Nonnull final Box other) {
        this.min.assign(other.min);
        this.max.assign(other.max);
        return this;
    }
    
    @Nonnull
    public Box assign(final double minX, final double minY, final double minZ, final double maxX, final double maxY, final double maxZ) {
        this.min.assign(minX, minY, minZ);
        this.max.assign(maxX, maxY, maxZ);
        return this;
    }
    
    @Nonnull
    public Box minkowskiSum(@Nonnull final Box bb) {
        this.min.subtract(bb.max);
        this.max.subtract(bb.min);
        return this;
    }
    
    @Nonnull
    public Box scale(final float scale) {
        this.min.scale(scale);
        this.max.scale(scale);
        return this;
    }
    
    @Nonnull
    public Box normalize() {
        if (this.min.x > this.max.x) {
            final double t = this.min.x;
            this.min.x = this.max.x;
            this.max.x = t;
        }
        if (this.min.y > this.max.y) {
            final double t = this.min.y;
            this.min.y = this.max.y;
            this.max.y = t;
        }
        if (this.min.z > this.max.z) {
            final double t = this.min.z;
            this.min.z = this.max.z;
            this.max.z = t;
        }
        return this;
    }
    
    @Nonnull
    public Box rotateX(final float angleInRadians) {
        this.min.rotateX(angleInRadians);
        this.max.rotateX(angleInRadians);
        return this;
    }
    
    @Nonnull
    public Box rotateY(final float angleInRadians) {
        this.min.rotateY(angleInRadians);
        this.max.rotateY(angleInRadians);
        return this;
    }
    
    @Nonnull
    public Box rotateZ(final float angleInRadians) {
        this.min.rotateZ(angleInRadians);
        this.max.rotateZ(angleInRadians);
        return this;
    }
    
    @Nonnull
    public Box offset(final double x, final double y, final double z) {
        this.min.add(x, y, z);
        this.max.add(x, y, z);
        return this;
    }
    
    @Nonnull
    public Box offset(@Nonnull final Vector3d pos) {
        this.min.add(pos);
        this.max.add(pos);
        return this;
    }
    
    @Nonnull
    public Box sweep(@Nonnull final Vector3d v) {
        if (v.x < 0.0) {
            final Vector3d min = this.min;
            min.x += v.x;
        }
        else if (v.x > 0.0) {
            final Vector3d max = this.max;
            max.x += v.x;
        }
        if (v.y < 0.0) {
            final Vector3d min2 = this.min;
            min2.y += v.y;
        }
        else if (v.y > 0.0) {
            final Vector3d max2 = this.max;
            max2.y += v.y;
        }
        if (v.z < 0.0) {
            final Vector3d min3 = this.min;
            min3.z += v.z;
        }
        else if (v.z > 0.0) {
            final Vector3d max3 = this.max;
            max3.z += v.z;
        }
        return this;
    }
    
    @Nonnull
    public Box extend(final double extentX, final double extentY, final double extentZ) {
        this.min.subtract(extentX, extentY, extentZ);
        this.max.add(extentX, extentY, extentZ);
        return this;
    }
    
    public double width() {
        return this.max.x - this.min.x;
    }
    
    public double height() {
        return this.max.y - this.min.y;
    }
    
    public double depth() {
        return this.max.z - this.min.z;
    }
    
    public double dimension(@Nonnull final Axis axis) {
        return switch (axis) {
            default -> throw new MatchException(null, null);
            case X -> this.width();
            case Y -> this.height();
            case Z -> this.depth();
        };
    }
    
    public double getThickness() {
        return MathUtil.minValue(this.width(), this.height(), this.depth());
    }
    
    public double getMaximumThickness() {
        return MathUtil.maxValue(this.width(), this.height(), this.depth());
    }
    
    public double getVolume() {
        final double w = this.width();
        if (w <= 0.0) {
            return 0.0;
        }
        final double h = this.height();
        if (h <= 0.0) {
            return 0.0;
        }
        final double d = this.depth();
        if (d <= 0.0) {
            return 0.0;
        }
        return w * h * d;
    }
    
    public boolean hasVolume() {
        return this.min.x <= this.max.x && this.min.y <= this.max.y && this.min.z <= this.max.z;
    }
    
    public boolean isIntersecting(@Nonnull final Box other) {
        return this.min.x <= other.max.x && other.min.x <= this.max.x && this.min.y <= other.max.y && other.min.y <= this.max.y && this.min.z <= other.max.z && other.min.z <= this.max.z;
    }
    
    public boolean isUnitBox() {
        return this.min.equals(Vector3d.ZERO) && this.max.equals(Vector3d.ALL_ONES);
    }
    
    public double middleX() {
        return (this.min.x + this.max.x) / 2.0;
    }
    
    public double middleY() {
        return (this.min.y + this.max.y) / 2.0;
    }
    
    public double middleZ() {
        return (this.min.z + this.max.z) / 2.0;
    }
    
    @Nonnull
    public Box clone() {
        final Box box = new Box();
        box.assign(this);
        return box;
    }
    
    @Nonnull
    public Vector3d getMin() {
        return this.min;
    }
    
    @Nonnull
    public Vector3d getMax() {
        return this.max;
    }
    
    @Nonnull
    @Override
    public Box getBox(final double x, final double y, final double z) {
        return new Box(this.min.getX() + x, this.min.getY() + y, this.min.getZ() + z, this.max.getX() + x, this.max.getY() + y, this.max.getZ() + z);
    }
    
    @Override
    public boolean containsPosition(final double x, final double y, final double z) {
        return x >= this.min.getX() && x <= this.max.getX() && y >= this.min.getY() && y <= this.max.getY() && z >= this.min.getZ() && z <= this.max.getZ();
    }
    
    @Override
    public void expand(final double radius) {
        this.extend(radius, radius, radius);
    }
    
    public boolean containsBlock(final int x, final int y, final int z) {
        final int minX = MathUtil.floor(this.min.getX());
        final int minY = MathUtil.floor(this.min.getY());
        final int minZ = MathUtil.floor(this.min.getZ());
        final int maxX = MathUtil.ceil(this.max.getX());
        final int maxY = MathUtil.ceil(this.max.getY());
        final int maxZ = MathUtil.ceil(this.max.getZ());
        return x >= minX && x < maxX && y >= minY && y < maxY && z >= minZ && z < maxZ;
    }
    
    public boolean containsBlock(@Nonnull final Vector3i origin, final int x, final int y, final int z) {
        return this.containsBlock(x - origin.getX(), y - origin.getY(), z - origin.getZ());
    }
    
    @Override
    public boolean forEachBlock(final double x, final double y, final double z, final double epsilon, @Nonnull final TriIntPredicate consumer) {
        final int minX = MathUtil.floor(x + this.min.getX() - epsilon);
        final int minY = MathUtil.floor(y + this.min.getY() - epsilon);
        final int minZ = MathUtil.floor(z + this.min.getZ() - epsilon);
        final int maxX = MathUtil.floor(x + this.max.getX() + epsilon);
        final int maxY = MathUtil.floor(y + this.max.getY() + epsilon);
        final int maxZ = MathUtil.floor(z + this.max.getZ() + epsilon);
        for (int _x = minX; _x <= maxX; ++_x) {
            for (int _y = minY; _y <= maxY; ++_y) {
                for (int _z = minZ; _z <= maxZ; ++_z) {
                    if (!consumer.test(_x, _y, _z)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }
    
    @Override
    public <T> boolean forEachBlock(final double x, final double y, final double z, final double epsilon, final T t, @Nonnull final TriIntObjPredicate<T> consumer) {
        final int minX = MathUtil.floor(x + this.min.getX() - epsilon);
        final int minY = MathUtil.floor(y + this.min.getY() - epsilon);
        final int minZ = MathUtil.floor(z + this.min.getZ() - epsilon);
        final int maxX = MathUtil.floor(x + this.max.getX() + epsilon);
        final int maxY = MathUtil.floor(y + this.max.getY() + epsilon);
        final int maxZ = MathUtil.floor(z + this.max.getZ() + epsilon);
        for (int _x = minX; _x <= maxX; ++_x) {
            for (int _y = minY; _y <= maxY; ++_y) {
                for (int _z = minZ; _z <= maxZ; ++_z) {
                    if (!consumer.test(_x, _y, _z, t)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }
    
    public double getMaximumExtent() {
        double maximumExtent = 0.0;
        if (-this.min.x > maximumExtent) {
            maximumExtent = -this.min.x;
        }
        if (-this.min.y > maximumExtent) {
            maximumExtent = -this.min.y;
        }
        if (-this.min.z > maximumExtent) {
            maximumExtent = -this.min.z;
        }
        if (this.max.x - 1.0 > maximumExtent) {
            maximumExtent = this.max.x - 1.0;
        }
        if (this.max.y - 1.0 > maximumExtent) {
            maximumExtent = this.max.y - 1.0;
        }
        if (this.max.z - 1.0 > maximumExtent) {
            maximumExtent = this.max.z - 1.0;
        }
        return maximumExtent;
    }
    
    public boolean intersectsLine(@Nonnull final Vector3d start, @Nonnull final Vector3d end) {
        final Vector3d direction = end.clone().subtract(start);
        double tmin = 0.0;
        double tmax = 1.0;
        if (Math.abs(direction.x) < 1.0E-10) {
            if (start.x < this.min.x || start.x > this.max.x) {
                return false;
            }
        }
        else {
            double t1 = (this.min.x - start.x) / direction.x;
            double t2 = (this.max.x - start.x) / direction.x;
            if (t1 > t2) {
                final double temp = t1;
                t1 = t2;
                t2 = temp;
            }
            tmin = Math.max(tmin, t1);
            tmax = Math.min(tmax, t2);
            if (tmin > tmax) {
                return false;
            }
        }
        if (Math.abs(direction.y) < 1.0E-10) {
            if (start.y < this.min.y || start.y > this.max.y) {
                return false;
            }
        }
        else {
            double t1 = (this.min.y - start.y) / direction.y;
            double t2 = (this.max.y - start.y) / direction.y;
            if (t1 > t2) {
                final double temp = t1;
                t1 = t2;
                t2 = temp;
            }
            tmin = Math.max(tmin, t1);
            tmax = Math.min(tmax, t2);
            if (tmin > tmax) {
                return false;
            }
        }
        if (Math.abs(direction.z) < 1.0E-10) {
            return start.z >= this.min.z && start.z <= this.max.z;
        }
        double t1 = (this.min.z - start.z) / direction.z;
        double t2 = (this.max.z - start.z) / direction.z;
        if (t1 > t2) {
            final double temp = t1;
            t1 = t2;
            t2 = temp;
        }
        tmin = Math.max(tmin, t1);
        tmax = Math.min(tmax, t2);
        return tmin <= tmax;
    }
    
    @Nonnull
    @Override
    public String toString() {
        return "Box{min=" + String.valueOf(this.min) + ", max=" + String.valueOf(this.max);
    }
    
    static {
        CODEC = BuilderCodec.builder(Box.class, Box::new).append(new KeyedCodec<Vector3d>("Min", Vector3d.CODEC), (box, v) -> box.min.assign(v), box -> box.min).add().append(new KeyedCodec("Max", Vector3d.CODEC), (box, v) -> box.max.assign(v), box -> box.max).add().validator((box, results) -> {
            if (box.width() <= 0.0) {
                results.fail("Width is <= 0! Given: " + box.width());
            }
            if (box.height() <= 0.0) {
                results.fail("Height is <= 0! Given: " + box.height());
            }
            if (box.depth() <= 0.0) {
                results.fail("Depth is <= 0! Given: " + box.depth());
            }
            return;
        }).build();
        UNIT = new Box(Vector3d.ZERO, Vector3d.ALL_ONES);
    }
}
