package net.minecraft.world.level.block;

import com.google.common.cache.CacheLoader;
import com.google.common.cache.CacheBuilder;
import org.apache.logging.log4j.LogManager;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.monster.piglin.PiglinAi;
import net.minecraft.core.NonNullList;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.Util;
import net.minecraft.core.Registry;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.stats.Stat;
import net.minecraft.stats.Stats;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockPlaceContext;
import net.minecraft.world.level.Explosion;
import net.minecraft.world.entity.ExperienceOrb;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.server.level.ServerLevel;
import java.util.Random;
import net.minecraft.world.level.LevelReader;
import net.minecraft.tags.BlockTags;
import net.minecraft.core.Vec3i;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.tags.Tag;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
import net.minecraft.core.Direction;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.item.BlockItem;
import it.unimi.dsi.fastutil.objects.Object2ByteLinkedOpenHashMap;
import net.minecraft.world.item.Item;
import javax.annotation.Nullable;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.phys.shapes.VoxelShape;
import com.google.common.cache.LoadingCache;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.core.IdMapper;
import org.apache.logging.log4j.Logger;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.block.state.BlockBehaviour;

public class Block extends BlockBehaviour implements ItemLike {
    protected static final Logger LOGGER;
    public static final IdMapper<BlockState> BLOCK_STATE_REGISTRY;
    private static final LoadingCache<VoxelShape, Boolean> SHAPE_FULL_BLOCK_CACHE;
    private static final VoxelShape RIGID_SUPPORT_SHAPE;
    private static final VoxelShape CENTER_SUPPORT_SHAPE;
    protected final StateDefinition<Block, BlockState> stateDefinition;
    private BlockState defaultBlockState;
    @Nullable
    private String descriptionId;
    @Nullable
    private Item item;
    private static final ThreadLocal<Object2ByteLinkedOpenHashMap<BlockStatePairKey>> OCCLUSION_CACHE;
    
    public static int getId(@Nullable final BlockState cfj) {
        if (cfj == null) {
            return 0;
        }
        final int integer2 = Block.BLOCK_STATE_REGISTRY.getId(cfj);
        return (integer2 == -1) ? 0 : integer2;
    }
    
    public static BlockState stateById(final int integer) {
        final BlockState cfj2 = Block.BLOCK_STATE_REGISTRY.byId(integer);
        return (cfj2 == null) ? Blocks.AIR.defaultBlockState() : cfj2;
    }
    
    public static Block byItem(@Nullable final Item bke) {
        if (bke instanceof BlockItem) {
            return ((BlockItem)bke).getBlock();
        }
        return Blocks.AIR;
    }
    
    public static BlockState pushEntitiesUp(final BlockState cfj1, final BlockState cfj2, final Level bqb, final BlockPos fu) {
        final VoxelShape dfg5 = Shapes.joinUnoptimized(cfj1.getCollisionShape(bqb, fu), cfj2.getCollisionShape(bqb, fu), BooleanOp.ONLY_SECOND).move(fu.getX(), fu.getY(), fu.getZ());
        final List<Entity> list6 = bqb.getEntities(null, dfg5.bounds());
        for (final Entity aom8 : list6) {
            final double double9 = Shapes.collide(Direction.Axis.Y, aom8.getBoundingBox().move(0.0, 1.0, 0.0), Stream.<VoxelShape>of(dfg5), -1.0);
            aom8.teleportTo(aom8.getX(), aom8.getY() + 1.0 + double9, aom8.getZ());
        }
        return cfj2;
    }
    
    public static VoxelShape box(final double double1, final double double2, final double double3, final double double4, final double double5, final double double6) {
        return Shapes.box(double1 / 16.0, double2 / 16.0, double3 / 16.0, double4 / 16.0, double5 / 16.0, double6 / 16.0);
    }
    
    public boolean is(final Tag<Block> adf) {
        return adf.contains(this);
    }
    
    public boolean is(final Block bvr) {
        return this == bvr;
    }
    
    public static BlockState updateFromNeighbourShapes(final BlockState cfj, final LevelAccessor bqc, final BlockPos fu) {
        BlockState cfj2 = cfj;
        final BlockPos.MutableBlockPos a5 = new BlockPos.MutableBlockPos();
        for (final Direction fz9 : Block.UPDATE_SHAPE_ORDER) {
            a5.setWithOffset(fu, fz9);
            cfj2 = cfj2.updateShape(fz9, bqc.getBlockState(a5), bqc, fu, a5);
        }
        return cfj2;
    }
    
    public static void updateOrDestroy(final BlockState cfj1, final BlockState cfj2, final LevelAccessor bqc, final BlockPos fu, final int integer) {
        updateOrDestroy(cfj1, cfj2, bqc, fu, integer, 512);
    }
    
    public static void updateOrDestroy(final BlockState cfj1, final BlockState cfj2, final LevelAccessor bqc, final BlockPos fu, final int integer5, final int integer6) {
        if (cfj2 != cfj1) {
            if (cfj2.isAir()) {
                if (!bqc.isClientSide()) {
                    bqc.destroyBlock(fu, (integer5 & 0x20) == 0x0, null, integer6);
                }
            }
            else {
                bqc.setBlock(fu, cfj2, integer5 & 0xFFFFFFDF, integer6);
            }
        }
    }
    
    public Block(final Properties c) {
        super(c);
        final StateDefinition.Builder<Block, BlockState> a3 = new StateDefinition.Builder<Block, BlockState>(this);
        this.createBlockStateDefinition(a3);
        this.stateDefinition = a3.create(Block::defaultBlockState, BlockState::new);
        this.registerDefaultState(this.stateDefinition.any());
    }
    
    public static boolean isExceptionForConnection(final Block bvr) {
        return bvr instanceof LeavesBlock || bvr == Blocks.BARRIER || bvr == Blocks.CARVED_PUMPKIN || bvr == Blocks.JACK_O_LANTERN || bvr == Blocks.MELON || bvr == Blocks.PUMPKIN || bvr.is(BlockTags.SHULKER_BOXES);
    }
    
    public boolean isRandomlyTicking(final BlockState cfj) {
        return this.isRandomlyTicking;
    }
    
    public static boolean shouldRenderFace(final BlockState cfj, final BlockGetter bpg, final BlockPos fu, final Direction fz) {
        final BlockPos fu2 = fu.relative(fz);
        final BlockState cfj2 = bpg.getBlockState(fu2);
        if (cfj.skipRendering(cfj2, fz)) {
            return false;
        }
        if (!cfj2.canOcclude()) {
            return true;
        }
        final BlockStatePairKey a7 = new BlockStatePairKey(cfj, cfj2, fz);
        final Object2ByteLinkedOpenHashMap<BlockStatePairKey> object2ByteLinkedOpenHashMap8 = Block.OCCLUSION_CACHE.get();
        final byte byte9 = object2ByteLinkedOpenHashMap8.getAndMoveToFirst(a7);
        if (byte9 != 127) {
            return byte9 != 0;
        }
        final VoxelShape dfg10 = cfj.getFaceOcclusionShape(bpg, fu, fz);
        final VoxelShape dfg11 = cfj2.getFaceOcclusionShape(bpg, fu2, fz.getOpposite());
        final boolean boolean12 = Shapes.joinIsNotEmpty(dfg10, dfg11, BooleanOp.ONLY_FIRST);
        if (object2ByteLinkedOpenHashMap8.size() == 2048) {
            object2ByteLinkedOpenHashMap8.removeLastByte();
        }
        object2ByteLinkedOpenHashMap8.putAndMoveToFirst(a7, (byte)(byte)(boolean12 ? 1 : 0));
        return boolean12;
    }
    
    public static boolean canSupportRigidBlock(final BlockGetter bpg, final BlockPos fu) {
        final BlockState cfj3 = bpg.getBlockState(fu);
        return (cfj3.isCollisionShapeFullBlock(bpg, fu) && cfj3.isFaceSturdy(bpg, fu, Direction.UP)) || !Shapes.joinIsNotEmpty(cfj3.getBlockSupportShape(bpg, fu).getFaceShape(Direction.UP), Block.RIGID_SUPPORT_SHAPE, BooleanOp.ONLY_SECOND);
    }
    
    public static boolean canSupportCenter(final LevelReader bqd, final BlockPos fu, final Direction fz) {
        final BlockState cfj4 = bqd.getBlockState(fu);
        return (fz != Direction.DOWN || !cfj4.is(BlockTags.UNSTABLE_BOTTOM_CENTER)) && !Shapes.joinIsNotEmpty(cfj4.getBlockSupportShape(bqd, fu).getFaceShape(fz), Block.CENTER_SUPPORT_SHAPE, BooleanOp.ONLY_SECOND);
    }
    
    public static boolean isFaceSturdy(final BlockState cfj, final BlockGetter bpg, final BlockPos fu, final Direction fz) {
        return isFaceFull(cfj.getBlockSupportShape(bpg, fu), fz);
    }
    
    public static boolean isFaceFull(final VoxelShape dfg, final Direction fz) {
        final VoxelShape dfg2 = dfg.getFaceShape(fz);
        return isShapeFullBlock(dfg2);
    }
    
    public static boolean isShapeFullBlock(final VoxelShape dfg) {
        return Block.SHAPE_FULL_BLOCK_CACHE.getUnchecked(dfg);
    }
    
    public boolean propagatesSkylightDown(final BlockState cfj, final BlockGetter bpg, final BlockPos fu) {
        return !isShapeFullBlock(cfj.getShape(bpg, fu)) && cfj.getFluidState().isEmpty();
    }
    
    public void animateTick(final BlockState cfj, final Level bqb, final BlockPos fu, final Random random) {
    }
    
    public void destroy(final LevelAccessor bqc, final BlockPos fu, final BlockState cfj) {
    }
    
    public static List<ItemStack> getDrops(final BlockState cfj, final ServerLevel zd, final BlockPos fu, @Nullable final BlockEntity cdl) {
        final LootContext.Builder a5 = new LootContext.Builder(zd).withRandom(zd.random).<BlockPos>withParameter(LootContextParams.BLOCK_POS, fu).<ItemStack>withParameter(LootContextParams.TOOL, ItemStack.EMPTY).<BlockEntity>withOptionalParameter(LootContextParams.BLOCK_ENTITY, cdl);
        return cfj.getDrops(a5);
    }
    
    public static List<ItemStack> getDrops(final BlockState cfj, final ServerLevel zd, final BlockPos fu, @Nullable final BlockEntity cdl, @Nullable final Entity aom, final ItemStack bki) {
        final LootContext.Builder a7 = new LootContext.Builder(zd).withRandom(zd.random).<BlockPos>withParameter(LootContextParams.BLOCK_POS, fu).<ItemStack>withParameter(LootContextParams.TOOL, bki).<Entity>withOptionalParameter(LootContextParams.THIS_ENTITY, aom).<BlockEntity>withOptionalParameter(LootContextParams.BLOCK_ENTITY, cdl);
        return cfj.getDrops(a7);
    }
    
    public static void dropResources(final BlockState cfj, final Level bqb, final BlockPos fu) {
        if (bqb instanceof ServerLevel) {
            getDrops(cfj, (ServerLevel)bqb, fu, null).forEach(bki -> popResource(bqb, fu, bki));
        }
        cfj.spawnAfterBreak(bqb, fu, ItemStack.EMPTY);
    }
    
    public static void dropResources(final BlockState cfj, final Level bqb, final BlockPos fu, @Nullable final BlockEntity cdl) {
        if (bqb instanceof ServerLevel) {
            getDrops(cfj, (ServerLevel)bqb, fu, cdl).forEach(bki -> popResource(bqb, fu, bki));
        }
        cfj.spawnAfterBreak(bqb, fu, ItemStack.EMPTY);
    }
    
    public static void dropResources(final BlockState cfj, final Level bqb, final BlockPos fu, @Nullable final BlockEntity cdl, final Entity aom, final ItemStack bki) {
        if (bqb instanceof ServerLevel) {
            getDrops(cfj, (ServerLevel)bqb, fu, cdl, aom, bki).forEach(bki -> popResource(bqb, fu, bki));
        }
        cfj.spawnAfterBreak(bqb, fu, bki);
    }
    
    public static void popResource(final Level bqb, final BlockPos fu, final ItemStack bki) {
        if (bqb.isClientSide || bki.isEmpty() || !bqb.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) {
            return;
        }
        final float float4 = 0.5f;
        final double double5 = bqb.random.nextFloat() * 0.5f + 0.25;
        final double double6 = bqb.random.nextFloat() * 0.5f + 0.25;
        final double double7 = bqb.random.nextFloat() * 0.5f + 0.25;
        final ItemEntity bbg11 = new ItemEntity(bqb, fu.getX() + double5, fu.getY() + double6, fu.getZ() + double7, bki);
        bbg11.setDefaultPickUpDelay();
        bqb.addFreshEntity(bbg11);
    }
    
    protected void popExperience(final Level bqb, final BlockPos fu, int integer) {
        if (!bqb.isClientSide && bqb.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) {
            while (integer > 0) {
                final int integer2 = ExperienceOrb.getExperienceValue(integer);
                integer -= integer2;
                bqb.addFreshEntity(new ExperienceOrb(bqb, fu.getX() + 0.5, fu.getY() + 0.5, fu.getZ() + 0.5, integer2));
            }
        }
    }
    
    public float getExplosionResistance() {
        return this.explosionResistance;
    }
    
    public void wasExploded(final Level bqb, final BlockPos fu, final Explosion bpt) {
    }
    
    public void stepOn(final Level bqb, final BlockPos fu, final Entity aom) {
    }
    
    @Nullable
    public BlockState getStateForPlacement(final BlockPlaceContext bin) {
        return this.defaultBlockState();
    }
    
    public void playerDestroy(final Level bqb, final Player bec, final BlockPos fu, final BlockState cfj, @Nullable final BlockEntity cdl, final ItemStack bki) {
        bec.awardStat(Stats.BLOCK_MINED.get(this));
        bec.causeFoodExhaustion(0.005f);
        dropResources(cfj, bqb, fu, cdl, bec, bki);
    }
    
    public void setPlacedBy(final Level bqb, final BlockPos fu, final BlockState cfj, @Nullable final LivingEntity aoy, final ItemStack bki) {
    }
    
    public boolean isPossibleToRespawnInThis() {
        return !this.material.isSolid() && !this.material.isLiquid();
    }
    
    public MutableComponent getName() {
        return new TranslatableComponent(this.getDescriptionId());
    }
    
    public String getDescriptionId() {
        if (this.descriptionId == null) {
            this.descriptionId = Util.makeDescriptionId("block", Registry.BLOCK.getKey(this));
        }
        return this.descriptionId;
    }
    
    public void fallOn(final Level bqb, final BlockPos fu, final Entity aom, final float float4) {
        aom.causeFallDamage(float4, 1.0f);
    }
    
    public void updateEntityAfterFallOn(final BlockGetter bpg, final Entity aom) {
        aom.setDeltaMovement(aom.getDeltaMovement().multiply(1.0, 0.0, 1.0));
    }
    
    public ItemStack getCloneItemStack(final BlockGetter bpg, final BlockPos fu, final BlockState cfj) {
        return new ItemStack(this);
    }
    
    public void fillItemCategory(final CreativeModeTab biy, final NonNullList<ItemStack> gi) {
        gi.add(new ItemStack(this));
    }
    
    public float getFriction() {
        return this.friction;
    }
    
    public float getSpeedFactor() {
        return this.speedFactor;
    }
    
    public float getJumpFactor() {
        return this.jumpFactor;
    }
    
    public void playerWillDestroy(final Level bqb, final BlockPos fu, final BlockState cfj, final Player bec) {
        bqb.levelEvent(bec, 2001, fu, getId(cfj));
        if (this.is(BlockTags.GUARDED_BY_PIGLINS)) {
            PiglinAi.angerNearbyPiglins(bec, false);
        }
    }
    
    public void handleRain(final Level bqb, final BlockPos fu) {
    }
    
    public boolean dropFromExplosion(final Explosion bpt) {
        return true;
    }
    
    protected void createBlockStateDefinition(final StateDefinition.Builder<Block, BlockState> a) {
    }
    
    public StateDefinition<Block, BlockState> getStateDefinition() {
        return this.stateDefinition;
    }
    
    protected final void registerDefaultState(final BlockState cfj) {
        this.defaultBlockState = cfj;
    }
    
    public final BlockState defaultBlockState() {
        return this.defaultBlockState;
    }
    
    public SoundType getSoundType(final BlockState cfj) {
        return this.soundType;
    }
    
    @Override
    public Item asItem() {
        if (this.item == null) {
            this.item = Item.byBlock(this);
        }
        return this.item;
    }
    
    public boolean hasDynamicShape() {
        return this.dynamicShape;
    }
    
    @Override
    public String toString() {
        return "Block{" + Registry.BLOCK.getKey(this) + "}";
    }
    
    public void appendHoverText(final ItemStack bki, @Nullable final BlockGetter bpg, final List<Component> list, final TooltipFlag bls) {
    }
    
    @Override
    protected Block asBlock() {
        return this;
    }
    
    static {
        LOGGER = LogManager.getLogger();
        BLOCK_STATE_REGISTRY = new IdMapper<BlockState>();
        SHAPE_FULL_BLOCK_CACHE = CacheBuilder.newBuilder().maximumSize(512L).weakKeys().<VoxelShape, Boolean>build(new CacheLoader<VoxelShape, Boolean>() {
            @Override
            public Boolean load(final VoxelShape dfg) {
                return !Shapes.joinIsNotEmpty(Shapes.block(), dfg, BooleanOp.NOT_SAME);
            }
        });
        RIGID_SUPPORT_SHAPE = Shapes.join(Shapes.block(), box(2.0, 0.0, 2.0, 14.0, 16.0, 14.0), BooleanOp.ONLY_FIRST);
        CENTER_SUPPORT_SHAPE = box(7.0, 0.0, 7.0, 9.0, 10.0, 9.0);
        final Object2ByteLinkedOpenHashMap<BlockStatePairKey> object2ByteLinkedOpenHashMap1;
        OCCLUSION_CACHE = ThreadLocal.<Object2ByteLinkedOpenHashMap<BlockStatePairKey>>withInitial(() -> {
            object2ByteLinkedOpenHashMap1 = new Object2ByteLinkedOpenHashMap<BlockStatePairKey>(2048, 0.25f) {
                protected void rehash(final int integer) {
                }
            };
            object2ByteLinkedOpenHashMap1.defaultReturnValue((byte)127);
            return object2ByteLinkedOpenHashMap1;
        });
    }
    
    public static final class BlockStatePairKey {
        private final BlockState first;
        private final BlockState second;
        private final Direction direction;
        
        public BlockStatePairKey(final BlockState cfj1, final BlockState cfj2, final Direction fz) {
            this.first = cfj1;
            this.second = cfj2;
            this.direction = fz;
        }
        
        @Override
        public boolean equals(final Object object) {
            if (this == object) {
                return true;
            }
            if (!(object instanceof BlockStatePairKey)) {
                return false;
            }
            final BlockStatePairKey a3 = (BlockStatePairKey)object;
            return this.first == a3.first && this.second == a3.second && this.direction == a3.direction;
        }
        
        @Override
        public int hashCode() {
            int integer2 = this.first.hashCode();
            integer2 = 31 * integer2 + this.second.hashCode();
            integer2 = 31 * integer2 + this.direction.hashCode();
            return integer2;
        }
    }
}
