package cn.nukkit.math;

import cn.nukkit.Server;
import cn.nukkit.level.MovingObjectPosition;

/**
 * auth||: MagicDroidX
 * Nukkit Project
 */
public class AxisAlignedBB implements Cloneable {

    public double minX;
    public double minY;
    public double minZ;
    public double maxX;
    public double maxY;
    public double maxZ;

    public AxisAlignedBB(Vector3 pos1, Vector3 pos2) {
        this.minX = Math.min(pos1.x, pos2.x);
        this.minY = Math.min(pos1.y, pos2.y);
        this.minZ = Math.min(pos1.z, pos2.z);
        this.maxX = Math.max(pos1.x, pos2.x);
        this.maxY = Math.max(pos1.y, pos2.y);
        this.maxZ = Math.max(pos1.z, pos2.z);
    }

    public AxisAlignedBB(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
        this.minX = minX;
        this.minY = minY;
        this.minZ = minZ;
        this.maxX = maxX;
        this.maxY = maxY;
        this.maxZ = maxZ;
    }

    public AxisAlignedBB setBounds(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
        this.minX = minX;
        this.minY = minY;
        this.minZ = minZ;
        this.maxX = maxX;
        this.maxY = maxY;
        this.maxZ = maxZ;
        return this;
    }

    public AxisAlignedBB addCoord(double x, double y, double z) {
        double minX = this.minX;
        double minY = this.minY;
        double minZ = this.minZ;
        double maxX = this.maxX;
        double maxY = this.maxY;
        double maxZ = this.maxZ;

        if (x < 0) minX += x;
        if (x > 0) maxX += x;

        if (y < 0) minY += y;
        if (y > 0) maxY += y;

        if (z < 0) minZ += z;
        if (z > 0) maxZ += z;

        return new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
    }

    public AxisAlignedBB grow(double x, double y, double z) {
        return new AxisAlignedBB(this.minX - x, this.minY - y, this.minZ - z, this.maxX + x, this.maxY + y, this.maxZ + z);
    }

    public AxisAlignedBB expand(double x, double y, double z)

    {
        this.minX -= x;
        this.minY -= y;
        this.minZ -= z;
        this.maxX += x;
        this.maxY += y;
        this.maxZ += z;

        return this;
    }

    public AxisAlignedBB offset(double x, double y, double z) {
        this.minX += x;
        this.minY += y;
        this.minZ += z;
        this.maxX += x;
        this.maxY += y;
        this.maxZ += z;

        return this;
    }

    public AxisAlignedBB shrink(double x, double y, double z) {
        return new AxisAlignedBB(this.minX + x, this.minY + y, this.minZ + z, this.maxX - x, this.maxY - y, this.maxZ - z);
    }

    public AxisAlignedBB contract(double x, double y, double z) {
        this.minX += x;
        this.minY += y;
        this.minZ += z;
        this.maxX -= x;
        this.maxY -= y;
        this.maxZ -= z;

        return this;
    }

    public AxisAlignedBB setBB(AxisAlignedBB bb) {
        this.minX = bb.minX;
        this.minY = bb.minY;
        this.minZ = bb.minZ;
        this.maxX = bb.maxX;
        this.maxY = bb.maxY;
        this.maxZ = bb.maxZ;
        return this;
    }

    public AxisAlignedBB getOffsetBoundingBox(double x, double y, double z) {
        return new AxisAlignedBB(this.minX + x, this.minY + y, this.minZ + z, this.maxX + x, this.maxY + y, this.maxZ + z);
    }

    public double calculateXOffset(AxisAlignedBB bb, double x) {
        if (bb.maxY <= this.minY || bb.minY >= this.maxY) {
            return x;
        }
        if (bb.maxZ <= this.minZ || bb.minZ >= this.maxZ) {
            return x;
        }
        if (x > 0 && bb.maxX <= this.minX) {
            double x1 = this.minX - bb.maxX;
            if (x1 < x) {
                x = x1;
            }
        }
        if (x < 0 && bb.minX >= this.maxX) {
            double x2 = this.maxX - bb.minX;
            if (x2 > x) {
                x = x2;
            }
        }

        return x;
    }

    public double calculateYOffset(AxisAlignedBB bb, double y) {
        if (bb.maxX <= this.minX || bb.minX >= this.maxX) {
            return y;
        }
        if (bb.maxZ <= this.minZ || bb.minZ >= this.maxZ) {
            return y;
        }
        if (y > 0 && bb.maxY <= this.minY) {
            double y1 = this.minY - bb.maxY;
            if (y1 < y) {
                y = y1;
            }
        }
        if (y < 0 && bb.minY >= this.maxY) {
            double y2 = this.maxY - bb.minY;
            if (y2 > y) {
                y = y2;
            }
        }

        return y;
    }

    public double calculateZOffset(AxisAlignedBB bb, double z) {
        if (bb.maxX <= this.minX || bb.minX >= this.maxX) {
            return z;
        }
        if (bb.maxY <= this.minY || bb.minY >= this.maxY) {
            return z;
        }
        if (z > 0 && bb.maxZ <= this.minZ) {
            double z1 = this.minZ - bb.maxZ;
            if (z1 < z) {
                z = z1;
            }
        }
        if (z < 0 && bb.minZ >= this.maxZ) {
            double z2 = this.maxZ - bb.minZ;
            if (z2 > z) {
                z = z2;
            }
        }

        return z;
    }

    public boolean intersectsWith(AxisAlignedBB bb) {
        if (bb.maxX > this.minX && bb.minX < this.maxX) {
            if (bb.maxY > this.minY && bb.minY < this.maxY) {
                return bb.maxZ > this.minZ && bb.minZ < this.maxZ;
            }
        }

        return false;
    }

    public boolean isVectorInside(Vector3 vector) {
        return vector.x >= this.minX && vector.x <= this.maxX && vector.y >= this.minY && vector.y <= this.maxY && vector.z >= this.minZ && vector.z <= this.maxZ;

    }

    public double getAverageEdgeLength() {
        return (this.maxX - this.minX + this.maxY - this.minY + this.maxZ - this.minZ) / 3;
    }

    public boolean isVectorInYZ(Vector3 vector) {
        return vector.y >= this.minY && vector.y <= this.maxY && vector.z >= this.minZ && vector.z <= this.maxZ;
    }

    public boolean isVectorInXZ(Vector3 vector) {
        return vector.x >= this.minX && vector.x <= this.maxX && vector.z >= this.minZ && vector.z <= this.maxZ;
    }

    public boolean isVectorInXY(Vector3 vector) {
        return vector.x >= this.minX && vector.x <= this.maxX && vector.y >= this.minY && vector.y <= this.maxY;
    }

    public MovingObjectPosition calculateIntercept(Vector3 pos1, Vector3 pos2) {
        Vector3 v1 = pos1.getIntermediateWithXValue(pos2, this.minX);
        Vector3 v2 = pos1.getIntermediateWithXValue(pos2, this.maxX);
        Vector3 v3 = pos1.getIntermediateWithYValue(pos2, this.minY);
        Vector3 v4 = pos1.getIntermediateWithYValue(pos2, this.maxY);
        Vector3 v5 = pos1.getIntermediateWithZValue(pos2, this.minZ);
        Vector3 v6 = pos1.getIntermediateWithZValue(pos2, this.maxZ);

        if (v1 != null && !this.isVectorInYZ(v1)) {
            v1 = null;
        }

        if (v2 != null && !this.isVectorInYZ(v2)) {
            v2 = null;
        }

        if (v3 != null && !this.isVectorInXZ(v3)) {
            v3 = null;
        }

        if (v4 != null && !this.isVectorInXZ(v4)) {
            v4 = null;
        }

        if (v5 != null && !this.isVectorInXY(v5)) {
            v5 = null;
        }

        if (v6 != null && !this.isVectorInXY(v6)) {
            v6 = null;
        }

        Vector3 vector = null;

        //if (v1 != null && (vector == null || pos1.distanceSquared(v1) < pos1.distanceSquared(vector))) {
        if (v1 != null) {
            vector = v1;
        }

        if (v2 != null && (vector == null || pos1.distanceSquared(v2) < pos1.distanceSquared(vector))) {
            vector = v2;
        }

        if (v3 != null && (vector == null || pos1.distanceSquared(v3) < pos1.distanceSquared(vector))) {
            vector = v3;
        }

        if (v4 != null && (vector == null || pos1.distanceSquared(v4) < pos1.distanceSquared(vector))) {
            vector = v4;
        }

        if (v5 != null && (vector == null || pos1.distanceSquared(v5) < pos1.distanceSquared(vector))) {
            vector = v5;
        }

        if (v6 != null && (vector == null || pos1.distanceSquared(v6) < pos1.distanceSquared(vector))) {
            vector = v6;
        }

        if (vector == null) {
            return null;
        }

        int face = -1;

        if (vector == v1) {
            face = 4;
        } else if (vector == v2) {
            face = 5;
        } else if (vector == v3) {
            face = 0;
        } else if (vector == v4) {
            face = 1;
        } else if (vector == v5) {
            face = 2;
        } else if (vector == v6) {
            face = 3;
        }

        return MovingObjectPosition.fromBlock(0, 0, 0, face, vector);
    }

    @Override
    public String toString() {
        return "AxisAlignedBB(" + this.minX + ", " + this.minY + ", " + this.minZ + ", " + this.maxX + ", " + this.maxY + ", " + this.maxZ + ")";
    }

    @Override
    public AxisAlignedBB clone() {
        try {
            return (AxisAlignedBB) super.clone();
        } catch (CloneNotSupportedException e) {
            Server.getInstance().getLogger().logException(e);
        }
        return null;
    }
}