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

import net.minecraft.world.level.block.state.StateHolder;
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.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket;
import net.minecraft.server.level.ServerPlayer;
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.PistonType;
import net.minecraft.world.level.block.state.properties.Property;
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 cfj, final Direction fz, final boolean boolean3, final boolean boolean4) {
        this();
        this.movedState = cfj;
        this.direction = fz;
        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 ((((StateHolder<O, BlockState>)Blocks.PISTON_HEAD.defaultBlockState()).setValue((Property<Comparable>)PistonHeadBlock.SHORT, this.progress > 0.25f)).setValue(PistonHeadBlock.TYPE, this.movedState.is(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 fz3 = this.getMovementDirection();
        final double double4 = float1 - this.progress;
        final VoxelShape dfg6 = this.getCollisionRelatedBlockState().getCollisionShape(this.level, this.getBlockPos());
        if (dfg6.isEmpty()) {
            return;
        }
        final AABB deg7 = this.moveByPositionAndProgress(dfg6.bounds());
        final List<Entity> list8 = this.level.getEntities(null, PistonMath.getMovementArea(deg7, fz3, double4).minmax(deg7));
        if (list8.isEmpty()) {
            return;
        }
        final List<AABB> list9 = dfg6.toAabbs();
        final boolean boolean10 = this.movedState.is(Blocks.SLIME_BLOCK);
        for (final Entity aom12 : list8) {
            if (aom12.getPistonPushReaction() == PushReaction.IGNORE) {
                continue;
            }
            if (boolean10) {
                final Vec3 dem13 = aom12.getDeltaMovement();
                double double5 = dem13.x;
                double double6 = dem13.y;
                double double7 = dem13.z;
                switch (fz3.getAxis()) {
                    case X: {
                        double5 = fz3.getStepX();
                        break;
                    }
                    case Y: {
                        double6 = fz3.getStepY();
                        break;
                    }
                    case Z: {
                        double7 = fz3.getStepZ();
                        break;
                    }
                }
                aom12.setDeltaMovement(double5, double6, double7);
                if (aom12 instanceof ServerPlayer) {
                    ((ServerPlayer)aom12).connection.send(new ClientboundSetEntityMotionPacket(aom12));
                }
            }
            double double8 = 0.0;
            for (final AABB deg8 : list9) {
                final AABB deg9 = PistonMath.getMovementArea(this.moveByPositionAndProgress(deg8), fz3, double4);
                final AABB deg10 = aom12.getBoundingBox();
                if (!deg9.intersects(deg10)) {
                    continue;
                }
                double8 = Math.max(double8, getMovement(deg9, fz3, deg10));
                if (double8 >= double4) {
                    break;
                }
            }
            if (double8 <= 0.0) {
                continue;
            }
            double8 = Math.min(double8, double4) + 0.01;
            moveEntityByPiston(fz3, aom12, double8, fz3);
            if (this.extending || !this.isSourcePiston) {
                continue;
            }
            this.fixEntityWithinPistonBase(aom12, fz3, double4);
        }
    }
    
    private static void moveEntityByPiston(final Direction fz1, final Entity aom, final double double3, final Direction fz4) {
        PistonMovingBlockEntity.NOCLIP.set(fz1);
        aom.move(MoverType.PISTON, new Vec3(double3 * fz4.getStepX(), double3 * fz4.getStepY(), double3 * fz4.getStepZ()));
        PistonMovingBlockEntity.NOCLIP.set(null);
    }
    
    private void moveStuckEntities(final float float1) {
        if (!this.isStickyForEntities()) {
            return;
        }
        final Direction fz3 = this.getMovementDirection();
        if (!fz3.getAxis().isHorizontal()) {
            return;
        }
        final double double4 = this.movedState.getCollisionShape(this.level, this.worldPosition).max(Direction.Axis.Y);
        final AABB deg6 = 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, deg6, aom -> matchesStickyCritera(deg6, aom));
        for (final Entity aom2 : list9) {
            moveEntityByPiston(fz3, aom2, double5, fz3);
        }
    }
    
    private static boolean matchesStickyCritera(final AABB deg, final Entity aom) {
        return aom.getPistonPushReaction() == PushReaction.NORMAL && aom.isOnGround() && aom.getX() >= deg.minX && aom.getX() <= deg.maxX && aom.getZ() >= deg.minZ && aom.getZ() <= deg.maxZ;
    }
    
    private boolean isStickyForEntities() {
        return this.movedState.is(Blocks.HONEY_BLOCK);
    }
    
    public Direction getMovementDirection() {
        return this.extending ? this.direction : this.direction.getOpposite();
    }
    
    private static double getMovement(final AABB deg1, final Direction fz, final AABB deg3) {
        switch (fz) {
            case EAST: {
                return deg1.maxX - deg3.minX;
            }
            case WEST: {
                return deg3.maxX - deg1.minX;
            }
            default: {
                return deg1.maxY - deg3.minY;
            }
            case DOWN: {
                return deg3.maxY - deg1.minY;
            }
            case SOUTH: {
                return deg1.maxZ - deg3.minZ;
            }
            case NORTH: {
                return deg3.maxZ - deg1.minZ;
            }
        }
    }
    
    private AABB moveByPositionAndProgress(final AABB deg) {
        final double double3 = this.getExtendedProgress(this.progress);
        return deg.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 aom, final Direction fz, final double double3) {
        final AABB deg6 = aom.getBoundingBox();
        final AABB deg7 = Shapes.block().bounds().move(this.worldPosition);
        if (deg6.intersects(deg7)) {
            final Direction fz2 = fz.getOpposite();
            double double4 = getMovement(deg7, fz2, deg6) + 0.01;
            final double double5 = getMovement(deg7, fz2, deg6.intersect(deg7)) + 0.01;
            if (Math.abs(double4 - double5) < 0.01) {
                double4 = Math.min(double4, double3) + 0.01;
                moveEntityByPiston(fz, aom, double4, fz2);
            }
        }
    }
    
    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).is(Blocks.MOVING_PISTON)) {
                BlockState cfj2;
                if (this.isSourcePiston) {
                    cfj2 = Blocks.AIR.defaultBlockState();
                }
                else {
                    cfj2 = Block.updateFromNeighbourShapes(this.movedState, this.level, this.worldPosition);
                }
                this.level.setBlock(this.worldPosition, cfj2, 3);
                this.level.neighborChanged(this.worldPosition, cfj2.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).is(Blocks.MOVING_PISTON)) {
                BlockState cfj2 = Block.updateFromNeighbourShapes(this.movedState, this.level, this.worldPosition);
                if (cfj2.isAir()) {
                    this.level.setBlock(this.worldPosition, this.movedState, 84);
                    Block.updateOrDestroy(this.movedState, cfj2, this.level, this.worldPosition, 3);
                }
                else {
                    if (cfj2.<Comparable>hasProperty((Property<Comparable>)BlockStateProperties.WATERLOGGED) && cfj2.<Boolean>getValue((Property<Boolean>)BlockStateProperties.WATERLOGGED)) {
                        cfj2 = ((StateHolder<O, BlockState>)cfj2).<Comparable, Boolean>setValue((Property<Comparable>)BlockStateProperties.WATERLOGGED, false);
                    }
                    this.level.setBlock(this.worldPosition, cfj2, 67);
                    this.level.neighborChanged(this.worldPosition, cfj2.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 BlockState cfj, final CompoundTag le) {
        super.load(cfj, le);
        this.movedState = NbtUtils.readBlockState(le.getCompound("blockState"));
        this.direction = Direction.from3DDataValue(le.getInt("facing"));
        this.progress = le.getFloat("progress");
        this.progressO = this.progress;
        this.extending = le.getBoolean("extending");
        this.isSourcePiston = le.getBoolean("source");
    }
    
    @Override
    public CompoundTag save(final CompoundTag le) {
        super.save(le);
        le.put("blockState", NbtUtils.writeBlockState(this.movedState));
        le.putInt("facing", this.direction.get3DDataValue());
        le.putFloat("progress", this.progressO);
        le.putBoolean("extending", this.extending);
        le.putBoolean("source", this.isSourcePiston);
        return le;
    }
    
    public VoxelShape getCollisionShape(final BlockGetter bpg, final BlockPos fu) {
        VoxelShape dfg4;
        if (!this.extending && this.isSourcePiston) {
            dfg4 = ((StateHolder<O, BlockState>)this.movedState).<Comparable, Boolean>setValue((Property<Comparable>)PistonBaseBlock.EXTENDED, true).getCollisionShape(bpg, fu);
        }
        else {
            dfg4 = Shapes.empty();
        }
        final Direction fz5 = PistonMovingBlockEntity.NOCLIP.get();
        if (this.progress < 1.0 && fz5 == this.getMovementDirection()) {
            return dfg4;
        }
        BlockState cfj6;
        if (this.isSourcePiston()) {
            cfj6 = (((StateHolder<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 < 0.25f);
        }
        else {
            cfj6 = 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(dfg4, cfj6.getCollisionShape(bpg, fu).move(double8, double9, double10));
    }
    
    public long getLastTicked() {
        return this.lastTicked;
    }
    
    static {
        NOCLIP = ThreadLocal.<Direction>withInitial(() -> null);
    }
}
