package net.minecraft.world.level;

import net.minecraft.world.level.border.WorldBorder;
import java.util.Objects;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.level.block.Blocks;
import java.util.function.Consumer;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.level.block.state.BlockState;
import java.util.function.BiPredicate;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Cursor3D;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.AABB;
import javax.annotation.Nullable;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.shapes.VoxelShape;
import java.util.Spliterators;

public class CollisionSpliterator extends Spliterators.AbstractSpliterator<VoxelShape> {
    @Nullable
    private final Entity source;
    private final AABB box;
    private final CollisionContext context;
    private final Cursor3D cursor;
    private final BlockPos.MutableBlockPos pos;
    private final VoxelShape entityShape;
    private final CollisionGetter collisionGetter;
    private boolean needsBorderCheck;
    private final BiPredicate<BlockState, BlockPos> predicate;
    
    public CollisionSpliterator(final CollisionGetter bpk, @Nullable final Entity aom, final AABB deg) {
        this(bpk, aom, deg, (cfj, fu) -> true);
    }
    
    public CollisionSpliterator(final CollisionGetter bpk, @Nullable final Entity aom, final AABB deg, final BiPredicate<BlockState, BlockPos> biPredicate) {
        super(Long.MAX_VALUE, 1280);
        this.context = ((aom == null) ? CollisionContext.empty() : CollisionContext.of(aom));
        this.pos = new BlockPos.MutableBlockPos();
        this.entityShape = Shapes.create(deg);
        this.collisionGetter = bpk;
        this.needsBorderCheck = (aom != null);
        this.source = aom;
        this.box = deg;
        this.predicate = biPredicate;
        final int integer6 = Mth.floor(deg.minX - 1.0E-7) - 1;
        final int integer7 = Mth.floor(deg.maxX + 1.0E-7) + 1;
        final int integer8 = Mth.floor(deg.minY - 1.0E-7) - 1;
        final int integer9 = Mth.floor(deg.maxY + 1.0E-7) + 1;
        final int integer10 = Mth.floor(deg.minZ - 1.0E-7) - 1;
        final int integer11 = Mth.floor(deg.maxZ + 1.0E-7) + 1;
        this.cursor = new Cursor3D(integer6, integer8, integer10, integer7, integer9, integer11);
    }
    
    @Override
    public boolean tryAdvance(final Consumer<? super VoxelShape> consumer) {
        return (this.needsBorderCheck && this.worldBorderCheck(consumer)) || this.collisionCheck(consumer);
    }
    
    boolean collisionCheck(final Consumer<? super VoxelShape> consumer) {
        while (this.cursor.advance()) {
            final int integer3 = this.cursor.nextX();
            final int integer4 = this.cursor.nextY();
            final int integer5 = this.cursor.nextZ();
            final int integer6 = this.cursor.getNextType();
            if (integer6 == 3) {
                continue;
            }
            final BlockGetter bpg7 = this.getChunk(integer3, integer5);
            if (bpg7 == null) {
                continue;
            }
            this.pos.set(integer3, integer4, integer5);
            final BlockState cfj8 = bpg7.getBlockState(this.pos);
            if (!this.predicate.test(cfj8, this.pos)) {
                continue;
            }
            if (integer6 == 1 && !cfj8.hasLargeCollisionShape()) {
                continue;
            }
            if (integer6 == 2 && !cfj8.is(Blocks.MOVING_PISTON)) {
                continue;
            }
            final VoxelShape dfg9 = cfj8.getCollisionShape(this.collisionGetter, this.pos, this.context);
            if (dfg9 == Shapes.block()) {
                if (this.box.intersects(integer3, integer4, integer5, integer3 + 1.0, integer4 + 1.0, integer5 + 1.0)) {
                    consumer.accept(dfg9.move(integer3, integer4, integer5));
                    return true;
                }
                continue;
            }
            else {
                final VoxelShape dfg10 = dfg9.move(integer3, integer4, integer5);
                if (Shapes.joinIsNotEmpty(dfg10, this.entityShape, BooleanOp.AND)) {
                    consumer.accept(dfg10);
                    return true;
                }
                continue;
            }
        }
        return false;
    }
    
    @Nullable
    private BlockGetter getChunk(final int integer1, final int integer2) {
        final int integer3 = integer1 >> 4;
        final int integer4 = integer2 >> 4;
        return this.collisionGetter.getChunkForCollisions(integer3, integer4);
    }
    
    boolean worldBorderCheck(final Consumer<? super VoxelShape> consumer) {
        Objects.<Entity>requireNonNull(this.source);
        this.needsBorderCheck = false;
        final WorldBorder cgw3 = this.collisionGetter.getWorldBorder();
        final AABB deg4 = this.source.getBoundingBox();
        if (!isBoxFullyWithinWorldBorder(cgw3, deg4)) {
            final VoxelShape dfg5 = cgw3.getCollisionShape();
            if (!isOutsideBorder(dfg5, deg4) && isCloseToBorder(dfg5, deg4)) {
                consumer.accept(dfg5);
                return true;
            }
        }
        return false;
    }
    
    private static boolean isCloseToBorder(final VoxelShape dfg, final AABB deg) {
        return Shapes.joinIsNotEmpty(dfg, Shapes.create(deg.inflate(1.0E-7)), BooleanOp.AND);
    }
    
    private static boolean isOutsideBorder(final VoxelShape dfg, final AABB deg) {
        return Shapes.joinIsNotEmpty(dfg, Shapes.create(deg.deflate(1.0E-7)), BooleanOp.AND);
    }
    
    public static boolean isBoxFullyWithinWorldBorder(final WorldBorder cgw, final AABB deg) {
        final double double3 = Mth.floor(cgw.getMinX());
        final double double4 = Mth.floor(cgw.getMinZ());
        final double double5 = Mth.ceil(cgw.getMaxX());
        final double double6 = Mth.ceil(cgw.getMaxZ());
        return deg.minX > double3 && deg.minX < double5 && deg.minZ > double4 && deg.minZ < double6 && deg.maxX > double3 && deg.maxX < double5 && deg.maxZ > double4 && deg.maxZ < double6;
    }
}
