package net.minecraft.world.level.block.piston;

import net.minecraft.world.level.block.state.AbstractStateHolder;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.phys.Vec3;
import java.util.Iterator;
import java.util.List;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.state.properties.PistonType;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.util.Mth;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.entity.TickableBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;

public class PistonMovingBlockEntity extends BlockEntity implements TickableBlockEntity {
    private BlockState movedState;
    private Direction direction;
    private boolean extending;
    private boolean isSourcePiston;
    private static final ThreadLocal<Direction> NOCLIP;
    private float progress;
    private float progressO;
    private long lastTicked;
    
    public PistonMovingBlockEntity() {
        super(BlockEntityType.PISTON);
    }
    
    public PistonMovingBlockEntity(final BlockState byj, final Direction fp, final boolean boolean3, final boolean boolean4) {
        this();
        this.movedState = byj;
        this.direction = fp;
        this.extending = boolean3;
        this.isSourcePiston = boolean4;
    }
    
    @Override
    public CompoundTag getUpdateTag() {
        return this.save(new CompoundTag());
    }
    
    public boolean isExtending() {
        return this.extending;
    }
    
    public Direction getDirection() {
        return this.direction;
    }
    
    public boolean isSourcePiston() {
        return this.isSourcePiston;
    }
    
    public float getProgress(float float1) {
        if (float1 > 1.0f) {
            float1 = 1.0f;
        }
        return Mth.lerp(float1, this.progressO, this.progress);
    }
    
    public float getXOff(final float float1) {
        return this.direction.getStepX() * this.getExtendedProgress(this.getProgress(float1));
    }
    
    public float getYOff(final float float1) {
        return this.direction.getStepY() * this.getExtendedProgress(this.getProgress(float1));
    }
    
    public float getZOff(final float float1) {
        return this.direction.getStepZ() * this.getExtendedProgress(this.getProgress(float1));
    }
    
    private float getExtendedProgress(final float float1) {
        return this.extending ? (float1 - 1.0f) : (1.0f - float1);
    }
    
    private BlockState getCollisionRelatedBlockState() {
        if (!this.isExtending() && this.isSourcePiston() && this.movedState.getBlock() instanceof PistonBaseBlock) {
            return (((AbstractStateHolder<O, BlockState>)Blocks.PISTON_HEAD.defaultBlockState()).setValue(PistonHeadBlock.TYPE, (this.movedState.getBlock() == Blocks.STICKY_PISTON) ? PistonType.STICKY : PistonType.DEFAULT)).<Comparable, Comparable>setValue((Property<Comparable>)PistonHeadBlock.FACING, (Comparable)this.movedState.<V>getValue((Property<V>)PistonBaseBlock.FACING));
        }
        return this.movedState;
    }
    
    private void moveCollidedEntities(final float float1) {
        final Direction fp3 = this.getMovementDirection();
        final double double4 = float1 - this.progress;
        final VoxelShape cwf6 = this.getCollisionRelatedBlockState().getCollisionShape(this.level, this.getBlockPos());
        if (cwf6.isEmpty()) {
            return;
        }
        final List<AABB> list7 = cwf6.toAabbs();
        final AABB cvf8 = this.moveByPositionAndProgress(this.getMinMaxPiecesAABB(list7));
        final List<Entity> list8 = this.level.getEntities(null, PistonMath.getMovementArea(cvf8, fp3, double4).minmax(cvf8));
        if (list8.isEmpty()) {
            return;
        }
        final boolean boolean10 = this.movedState.getBlock() == Blocks.SLIME_BLOCK;
        for (final Entity akq12 : list8) {
            if (akq12.getPistonPushReaction() == PushReaction.IGNORE) {
                continue;
            }
            if (boolean10) {
                final Vec3 cvl13 = akq12.getDeltaMovement();
                double double5 = cvl13.x;
                double double6 = cvl13.y;
                double double7 = cvl13.z;
                switch (fp3.getAxis()) {
                    case X: {
                        double5 = fp3.getStepX();
                        break;
                    }
                    case Y: {
                        double6 = fp3.getStepY();
                        break;
                    }
                    case Z: {
                        double7 = fp3.getStepZ();
                        break;
                    }
                }
                akq12.setDeltaMovement(double5, double6, double7);
            }
            double double8 = 0.0;
            for (final AABB cvf9 : list7) {
                final AABB cvf10 = PistonMath.getMovementArea(this.moveByPositionAndProgress(cvf9), fp3, double4);
                final AABB cvf11 = akq12.getBoundingBox();
                if (!cvf10.intersects(cvf11)) {
                    continue;
                }
                double8 = Math.max(double8, getMovement(cvf10, fp3, cvf11));
                if (double8 >= double4) {
                    break;
                }
            }
            if (double8 <= 0.0) {
                continue;
            }
            double8 = Math.min(double8, double4) + 0.01;
            moveEntityByPiston(fp3, akq12, double8, fp3);
            if (this.extending || !this.isSourcePiston) {
                continue;
            }
            this.fixEntityWithinPistonBase(akq12, fp3, double4);
        }
    }
    
    private static void moveEntityByPiston(final Direction fp1, final Entity akq, final double double3, final Direction fp4) {
        PistonMovingBlockEntity.NOCLIP.set(fp1);
        akq.move(MoverType.PISTON, new Vec3(double3 * fp4.getStepX(), double3 * fp4.getStepY(), double3 * fp4.getStepZ()));
        PistonMovingBlockEntity.NOCLIP.set(null);
    }
    
    private void moveStuckEntities(final float float1) {
        if (!this.isStickyForEntities()) {
            return;
        }
        final Direction fp3 = this.getMovementDirection();
        if (!fp3.getAxis().isHorizontal()) {
            return;
        }
        final double double4 = this.movedState.getCollisionShape(this.level, this.worldPosition).max(Direction.Axis.Y);
        final AABB cvf6 = this.moveByPositionAndProgress(new AABB(0.0, double4, 0.0, 1.0, 1.5000000999999998, 1.0));
        final double double5 = float1 - this.progress;
        final List<Entity> list9 = this.level.getEntities(null, cvf6, akq -> matchesStickyCritera(cvf6, akq));
        for (final Entity akq2 : list9) {
            moveEntityByPiston(fp3, akq2, double5, fp3);
        }
    }
    
    private static boolean matchesStickyCritera(final AABB cvf, final Entity akq) {
        return akq.getPistonPushReaction() == PushReaction.NORMAL && akq.onGround && akq.getX() >= cvf.minX && akq.getX() <= cvf.maxX && akq.getZ() >= cvf.minZ && akq.getZ() <= cvf.maxZ;
    }
    
    private boolean isStickyForEntities() {
        return this.movedState.getBlock() == Blocks.HONEY_BLOCK;
    }
    
    public Direction getMovementDirection() {
        return this.extending ? this.direction : this.direction.getOpposite();
    }
    
    private AABB getMinMaxPiecesAABB(final List<AABB> list) {
        double double3 = 0.0;
        double double4 = 0.0;
        double double5 = 0.0;
        double double6 = 1.0;
        double double7 = 1.0;
        double double8 = 1.0;
        for (final AABB cvf16 : list) {
            double3 = Math.min(cvf16.minX, double3);
            double4 = Math.min(cvf16.minY, double4);
            double5 = Math.min(cvf16.minZ, double5);
            double6 = Math.max(cvf16.maxX, double6);
            double7 = Math.max(cvf16.maxY, double7);
            double8 = Math.max(cvf16.maxZ, double8);
        }
        return new AABB(double3, double4, double5, double6, double7, double8);
    }
    
    private static double getMovement(final AABB cvf1, final Direction fp, final AABB cvf3) {
        switch (fp) {
            case EAST: {
                return cvf1.maxX - cvf3.minX;
            }
            case WEST: {
                return cvf3.maxX - cvf1.minX;
            }
            default: {
                return cvf1.maxY - cvf3.minY;
            }
            case DOWN: {
                return cvf3.maxY - cvf1.minY;
            }
            case SOUTH: {
                return cvf1.maxZ - cvf3.minZ;
            }
            case NORTH: {
                return cvf3.maxZ - cvf1.minZ;
            }
        }
    }
    
    private AABB moveByPositionAndProgress(final AABB cvf) {
        final double double3 = this.getExtendedProgress(this.progress);
        return cvf.move(this.worldPosition.getX() + double3 * this.direction.getStepX(), this.worldPosition.getY() + double3 * this.direction.getStepY(), this.worldPosition.getZ() + double3 * this.direction.getStepZ());
    }
    
    private void fixEntityWithinPistonBase(final Entity akq, final Direction fp, final double double3) {
        final AABB cvf6 = akq.getBoundingBox();
        final AABB cvf7 = Shapes.block().bounds().move(this.worldPosition);
        if (cvf6.intersects(cvf7)) {
            final Direction fp2 = fp.getOpposite();
            double double4 = getMovement(cvf7, fp2, cvf6) + 0.01;
            final double double5 = getMovement(cvf7, fp2, cvf6.intersect(cvf7)) + 0.01;
            if (Math.abs(double4 - double5) < 0.01) {
                double4 = Math.min(double4, double3) + 0.01;
                moveEntityByPiston(fp, akq, double4, fp2);
            }
        }
    }
    
    public BlockState getMovedState() {
        return this.movedState;
    }
    
    public void finalTick() {
        if (this.progressO < 1.0f && this.level != null) {
            this.progress = 1.0f;
            this.progressO = this.progress;
            this.level.removeBlockEntity(this.worldPosition);
            this.setRemoved();
            if (this.level.getBlockState(this.worldPosition).getBlock() == Blocks.MOVING_PISTON) {
                BlockState byj2;
                if (this.isSourcePiston) {
                    byj2 = Blocks.AIR.defaultBlockState();
                }
                else {
                    byj2 = Block.updateFromNeighbourShapes(this.movedState, this.level, this.worldPosition);
                }
                this.level.setBlock(this.worldPosition, byj2, 3);
                this.level.neighborChanged(this.worldPosition, byj2.getBlock(), this.worldPosition);
            }
        }
    }
    
    @Override
    public void tick() {
        this.lastTicked = this.level.getGameTime();
        this.progressO = this.progress;
        if (this.progressO >= 1.0f) {
            this.level.removeBlockEntity(this.worldPosition);
            this.setRemoved();
            if (this.movedState != null && this.level.getBlockState(this.worldPosition).getBlock() == Blocks.MOVING_PISTON) {
                BlockState byj2 = Block.updateFromNeighbourShapes(this.movedState, this.level, this.worldPosition);
                if (byj2.isAir()) {
                    this.level.setBlock(this.worldPosition, this.movedState, 84);
                    Block.updateOrDestroy(this.movedState, byj2, this.level, this.worldPosition, 3);
                }
                else {
                    if (byj2.<Comparable>hasProperty((Property<Comparable>)BlockStateProperties.WATERLOGGED) && byj2.<Boolean>getValue((Property<Boolean>)BlockStateProperties.WATERLOGGED)) {
                        byj2 = ((AbstractStateHolder<O, BlockState>)byj2).<Comparable, Boolean>setValue((Property<Comparable>)BlockStateProperties.WATERLOGGED, false);
                    }
                    this.level.setBlock(this.worldPosition, byj2, 67);
                    this.level.neighborChanged(this.worldPosition, byj2.getBlock(), this.worldPosition);
                }
            }
            return;
        }
        final float float2 = this.progress + 0.5f;
        this.moveCollidedEntities(float2);
        this.moveStuckEntities(float2);
        this.progress = float2;
        if (this.progress >= 1.0f) {
            this.progress = 1.0f;
        }
    }
    
    @Override
    public void load(final CompoundTag jt) {
        super.load(jt);
        this.movedState = NbtUtils.readBlockState(jt.getCompound("blockState"));
        this.direction = Direction.from3DDataValue(jt.getInt("facing"));
        this.progress = jt.getFloat("progress");
        this.progressO = this.progress;
        this.extending = jt.getBoolean("extending");
        this.isSourcePiston = jt.getBoolean("source");
    }
    
    @Override
    public CompoundTag save(final CompoundTag jt) {
        super.save(jt);
        jt.put("blockState", NbtUtils.writeBlockState(this.movedState));
        jt.putInt("facing", this.direction.get3DDataValue());
        jt.putFloat("progress", this.progressO);
        jt.putBoolean("extending", this.extending);
        jt.putBoolean("source", this.isSourcePiston);
        return jt;
    }
    
    public VoxelShape getCollisionShape(final BlockGetter bjg, final BlockPos fk) {
        VoxelShape cwf4;
        if (!this.extending && this.isSourcePiston) {
            cwf4 = ((AbstractStateHolder<O, BlockState>)this.movedState).<Comparable, Boolean>setValue((Property<Comparable>)PistonBaseBlock.EXTENDED, true).getCollisionShape(bjg, fk);
        }
        else {
            cwf4 = Shapes.empty();
        }
        final Direction fp5 = PistonMovingBlockEntity.NOCLIP.get();
        if (this.progress < 1.0 && fp5 == this.getMovementDirection()) {
            return cwf4;
        }
        BlockState byj6;
        if (this.isSourcePiston()) {
            byj6 = (((AbstractStateHolder<O, BlockState>)Blocks.PISTON_HEAD.defaultBlockState()).setValue((Property<Comparable>)PistonHeadBlock.FACING, this.direction)).<Comparable, Boolean>setValue((Property<Comparable>)PistonHeadBlock.SHORT, this.extending != 1.0f - this.progress < 4.0f);
        }
        else {
            byj6 = this.movedState;
        }
        final float float7 = this.getExtendedProgress(this.progress);
        final double double8 = this.direction.getStepX() * float7;
        final double double9 = this.direction.getStepY() * float7;
        final double double10 = this.direction.getStepZ() * float7;
        return Shapes.or(cwf4, byj6.getCollisionShape(bjg, fk).move(double8, double9, double10));
    }
    
    public long getLastTicked() {
        return this.lastTicked;
    }
    
    static {
        NOCLIP = ThreadLocal.<Direction>withInitial(() -> null);
    }
}
