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

import java.util.Arrays;
import net.minecraft.world.level.EmptyBlockGetter;
import java.util.Iterator;
import net.minecraft.resources.ResourceLocation;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.mojang.datafixers.util.Pair;
import java.util.Map;
import net.minecraft.core.Registry;
import com.mojang.datafixers.Dynamic;
import com.mojang.datafixers.types.DynamicOps;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.tags.Tag;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.item.BlockPlaceContext;
import net.minecraft.world.level.pathfinder.PathComputationType;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.InteractionHand;
import java.util.List;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.item.ItemStack;
import java.util.Random;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.material.MaterialColor;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.core.Direction;
import net.minecraft.world.entity.EntityType;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.material.Material;
import net.minecraft.world.level.block.state.properties.Property;
import com.google.common.collect.ImmutableMap;
import javax.annotation.Nullable;
import net.minecraft.world.level.block.Block;

public class BlockState extends AbstractStateHolder<Block, BlockState> implements StateHolder<BlockState> {
    @Nullable
    private Cache cache;
    private final int lightEmission;
    private final boolean useShapeForLightOcclusion;
    
    public BlockState(final Block bph, final ImmutableMap<Property<?>, Comparable<?>> immutableMap) {
        super(bph, immutableMap);
        this.lightEmission = bph.getLightEmission(this);
        this.useShapeForLightOcclusion = bph.useShapeForLightOcclusion(this);
    }
    
    public void initCache() {
        if (!this.getBlock().hasDynamicShape()) {
            this.cache = new Cache(this);
        }
    }
    
    public Block getBlock() {
        return (Block)this.owner;
    }
    
    public Material getMaterial() {
        return this.getBlock().getMaterial(this);
    }
    
    public boolean isValidSpawn(final BlockGetter bjg, final BlockPos fk, final EntityType<?> aku) {
        return this.getBlock().isValidSpawn(this, bjg, fk, aku);
    }
    
    public boolean propagatesSkylightDown(final BlockGetter bjg, final BlockPos fk) {
        if (this.cache != null) {
            return this.cache.propagatesSkylightDown;
        }
        return this.getBlock().propagatesSkylightDown(this, bjg, fk);
    }
    
    public int getLightBlock(final BlockGetter bjg, final BlockPos fk) {
        if (this.cache != null) {
            return this.cache.lightBlock;
        }
        return this.getBlock().getLightBlock(this, bjg, fk);
    }
    
    public VoxelShape getFaceOcclusionShape(final BlockGetter bjg, final BlockPos fk, final Direction fp) {
        if (this.cache != null && this.cache.occlusionShapes != null) {
            return this.cache.occlusionShapes[fp.ordinal()];
        }
        return Shapes.getFaceShape(this.getOcclusionShape(bjg, fk), fp);
    }
    
    public boolean hasLargeCollisionShape() {
        return this.cache == null || this.cache.largeCollisionShape;
    }
    
    public boolean useShapeForLightOcclusion() {
        return this.useShapeForLightOcclusion;
    }
    
    public int getLightEmission() {
        return this.lightEmission;
    }
    
    public boolean isAir() {
        return this.getBlock().isAir(this);
    }
    
    public MaterialColor getMapColor(final BlockGetter bjg, final BlockPos fk) {
        return this.getBlock().getMapColor(this, bjg, fk);
    }
    
    public BlockState rotate(final Rotation btu) {
        return this.getBlock().rotate(this, btu);
    }
    
    public BlockState mirror(final Mirror bsu) {
        return this.getBlock().mirror(this, bsu);
    }
    
    public RenderShape getRenderShape() {
        return this.getBlock().getRenderShape(this);
    }
    
    public boolean emissiveRendering() {
        return this.getBlock().emissiveRendering(this);
    }
    
    public float getShadeBrightness(final BlockGetter bjg, final BlockPos fk) {
        return this.getBlock().getShadeBrightness(this, bjg, fk);
    }
    
    public boolean isRedstoneConductor(final BlockGetter bjg, final BlockPos fk) {
        return this.getBlock().isRedstoneConductor(this, bjg, fk);
    }
    
    public boolean isSignalSource() {
        return this.getBlock().isSignalSource(this);
    }
    
    public int getSignal(final BlockGetter bjg, final BlockPos fk, final Direction fp) {
        return this.getBlock().getSignal(this, bjg, fk, fp);
    }
    
    public boolean hasAnalogOutputSignal() {
        return this.getBlock().hasAnalogOutputSignal(this);
    }
    
    public int getAnalogOutputSignal(final Level bjw, final BlockPos fk) {
        return this.getBlock().getAnalogOutputSignal(this, bjw, fk);
    }
    
    public float getDestroySpeed(final BlockGetter bjg, final BlockPos fk) {
        return this.getBlock().getDestroySpeed(this, bjg, fk);
    }
    
    public float getDestroyProgress(final Player ayj, final BlockGetter bjg, final BlockPos fk) {
        return this.getBlock().getDestroyProgress(this, ayj, bjg, fk);
    }
    
    public int getDirectSignal(final BlockGetter bjg, final BlockPos fk, final Direction fp) {
        return this.getBlock().getDirectSignal(this, bjg, fk, fp);
    }
    
    public PushReaction getPistonPushReaction() {
        return this.getBlock().getPistonPushReaction(this);
    }
    
    public boolean isSolidRender(final BlockGetter bjg, final BlockPos fk) {
        if (this.cache != null) {
            return this.cache.solidRender;
        }
        return this.getBlock().isSolidRender(this, bjg, fk);
    }
    
    public boolean canOcclude() {
        if (this.cache != null) {
            return this.cache.canOcclude;
        }
        return this.getBlock().canOcclude(this);
    }
    
    public boolean skipRendering(final BlockState byj, final Direction fp) {
        return this.getBlock().skipRendering(this, byj, fp);
    }
    
    public VoxelShape getShape(final BlockGetter bjg, final BlockPos fk) {
        return this.getShape(bjg, fk, CollisionContext.empty());
    }
    
    public VoxelShape getShape(final BlockGetter bjg, final BlockPos fk, final CollisionContext cvq) {
        return this.getBlock().getShape(this, bjg, fk, cvq);
    }
    
    public VoxelShape getCollisionShape(final BlockGetter bjg, final BlockPos fk) {
        if (this.cache != null) {
            return this.cache.collisionShape;
        }
        return this.getCollisionShape(bjg, fk, CollisionContext.empty());
    }
    
    public VoxelShape getCollisionShape(final BlockGetter bjg, final BlockPos fk, final CollisionContext cvq) {
        return this.getBlock().getCollisionShape(this, bjg, fk, cvq);
    }
    
    public VoxelShape getOcclusionShape(final BlockGetter bjg, final BlockPos fk) {
        return this.getBlock().getOcclusionShape(this, bjg, fk);
    }
    
    public VoxelShape getInteractionShape(final BlockGetter bjg, final BlockPos fk) {
        return this.getBlock().getInteractionShape(this, bjg, fk);
    }
    
    public final boolean entityCanStandOn(final BlockGetter bjg, final BlockPos fk, final Entity akq) {
        return Block.isFaceFull(this.getCollisionShape(bjg, fk, CollisionContext.of(akq)), Direction.UP);
    }
    
    public Vec3 getOffset(final BlockGetter bjg, final BlockPos fk) {
        return this.getBlock().getOffset(this, bjg, fk);
    }
    
    public boolean triggerEvent(final Level bjw, final BlockPos fk, final int integer3, final int integer4) {
        return this.getBlock().triggerEvent(this, bjw, fk, integer3, integer4);
    }
    
    public void neighborChanged(final Level bjw, final BlockPos fk2, final Block bph, final BlockPos fk4, final boolean boolean5) {
        this.getBlock().neighborChanged(this, bjw, fk2, bph, fk4, boolean5);
    }
    
    public void updateNeighbourShapes(final LevelAccessor bjx, final BlockPos fk, final int integer) {
        this.getBlock().updateNeighbourShapes(this, bjx, fk, integer);
    }
    
    public void updateIndirectNeighbourShapes(final LevelAccessor bjx, final BlockPos fk, final int integer) {
        this.getBlock().updateIndirectNeighbourShapes(this, bjx, fk, integer);
    }
    
    public void onPlace(final Level bjw, final BlockPos fk, final BlockState byj, final boolean boolean4) {
        this.getBlock().onPlace(this, bjw, fk, byj, boolean4);
    }
    
    public void onRemove(final Level bjw, final BlockPos fk, final BlockState byj, final boolean boolean4) {
        this.getBlock().onRemove(this, bjw, fk, byj, boolean4);
    }
    
    public void tick(final ServerLevel xd, final BlockPos fk, final Random random) {
        this.getBlock().tick(this, xd, fk, random);
    }
    
    public void randomTick(final ServerLevel xd, final BlockPos fk, final Random random) {
        this.getBlock().randomTick(this, xd, fk, random);
    }
    
    public void entityInside(final Level bjw, final BlockPos fk, final Entity akq) {
        this.getBlock().entityInside(this, bjw, fk, akq);
    }
    
    public void spawnAfterBreak(final Level bjw, final BlockPos fk, final ItemStack ben) {
        this.getBlock().spawnAfterBreak(this, bjw, fk, ben);
    }
    
    public List<ItemStack> getDrops(final LootContext.Builder a) {
        return this.getBlock().getDrops(this, a);
    }
    
    public InteractionResult use(final Level bjw, final Player ayj, final InteractionHand ajk, final BlockHitResult cvg) {
        return this.getBlock().use(this, bjw, cvg.getBlockPos(), ayj, ajk, cvg);
    }
    
    public void attack(final Level bjw, final BlockPos fk, final Player ayj) {
        this.getBlock().attack(this, bjw, fk, ayj);
    }
    
    public boolean isSuffocating(final BlockGetter bjg, final BlockPos fk) {
        return this.getBlock().isSuffocating(this, bjg, fk);
    }
    
    public boolean isViewBlocking(final BlockGetter bjg, final BlockPos fk) {
        return this.getBlock().isViewBlocking(this, bjg, fk);
    }
    
    public BlockState updateShape(final Direction fp, final BlockState byj, final LevelAccessor bjx, final BlockPos fk4, final BlockPos fk5) {
        return this.getBlock().updateShape(this, fp, byj, bjx, fk4, fk5);
    }
    
    public boolean isPathfindable(final BlockGetter bjg, final BlockPos fk, final PathComputationType cqr) {
        return this.getBlock().isPathfindable(this, bjg, fk, cqr);
    }
    
    public boolean canBeReplaced(final BlockPlaceContext bcq) {
        return this.getBlock().canBeReplaced(this, bcq);
    }
    
    public boolean canBeReplaced(final Fluid coi) {
        return this.getBlock().canBeReplaced(this, coi);
    }
    
    public boolean canSurvive(final LevelReader bjz, final BlockPos fk) {
        return this.getBlock().canSurvive(this, bjz, fk);
    }
    
    public boolean hasPostProcess(final BlockGetter bjg, final BlockPos fk) {
        return this.getBlock().hasPostProcess(this, bjg, fk);
    }
    
    @Nullable
    public MenuProvider getMenuProvider(final Level bjw, final BlockPos fk) {
        return this.getBlock().getMenuProvider(this, bjw, fk);
    }
    
    public boolean is(final Tag<Block> aaz) {
        return this.getBlock().is(aaz);
    }
    
    public FluidState getFluidState() {
        return this.getBlock().getFluidState(this);
    }
    
    public boolean isRandomlyTicking() {
        return this.getBlock().isRandomlyTicking(this);
    }
    
    public long getSeed(final BlockPos fk) {
        return this.getBlock().getSeed(this, fk);
    }
    
    public SoundType getSoundType() {
        return this.getBlock().getSoundType(this);
    }
    
    public void onProjectileHit(final Level bjw, final BlockState byj, final BlockHitResult cvg, final Entity akq) {
        this.getBlock().onProjectileHit(bjw, byj, cvg, akq);
    }
    
    public boolean isFaceSturdy(final BlockGetter bjg, final BlockPos fk, final Direction fp) {
        if (this.cache != null) {
            return this.cache.isFaceSturdy[fp.ordinal()];
        }
        return Block.isFaceSturdy(this, bjg, fk, fp);
    }
    
    public boolean isCollisionShapeFullBlock(final BlockGetter bjg, final BlockPos fk) {
        if (this.cache != null) {
            return this.cache.isCollisionShapeFullBlock;
        }
        return Block.isShapeFullBlock(this.getCollisionShape(bjg, fk));
    }
    
    public static <T> Dynamic<T> serialize(final DynamicOps<T> dynamicOps, final BlockState byj) {
        final ImmutableMap<Property<?>, Comparable<?>> immutableMap3 = byj.getValues();
        T object4;
        if (immutableMap3.isEmpty()) {
            object4 = (T)dynamicOps.createMap((Map)ImmutableMap.of(dynamicOps.createString("Name"), dynamicOps.createString(Registry.BLOCK.getKey(byj.getBlock()).toString())));
        }
        else {
            object4 = (T)dynamicOps.createMap((Map)ImmutableMap.of(dynamicOps.createString("Name"), dynamicOps.createString(Registry.BLOCK.getKey(byj.getBlock()).toString()), dynamicOps.createString("Properties"), dynamicOps.createMap((Map)immutableMap3.entrySet().stream().map(entry -> Pair.of(dynamicOps.createString(entry.getKey().getName()), dynamicOps.createString(StateHolder.<Comparable>getName((Property<Comparable>)entry.getKey(), entry.getValue())))).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond)))));
        }
        return (Dynamic<T>)new Dynamic((DynamicOps)dynamicOps, object4);
    }
    
    public static <T> BlockState deserialize(final Dynamic<T> dynamic) {
        final Block bph2 = Registry.BLOCK.get(new ResourceLocation(dynamic.getElement("Name").<String>flatMap(dynamic.getOps()::getStringValue).orElse("minecraft:air")));
        final Map<String, String> map3 = (Map<String, String>)dynamic.get("Properties").asMap(dynamic -> dynamic.asString(""), dynamic -> dynamic.asString(""));
        BlockState byj4 = bph2.defaultBlockState();
        final StateDefinition<Block, BlockState> byk5 = bph2.getStateDefinition();
        for (final Map.Entry<String, String> entry7 : map3.entrySet()) {
            final String string8 = entry7.getKey();
            final Property<?> bzm9 = byk5.getProperty(string8);
            if (bzm9 != null) {
                byj4 = StateHolder.setValueHelper(byj4, bzm9, string8, dynamic.toString(), entry7.getValue());
            }
        }
        return byj4;
    }
    
    static final class Cache {
        private static final Direction[] DIRECTIONS;
        private final boolean canOcclude;
        private final boolean solidRender;
        private final boolean propagatesSkylightDown;
        private final int lightBlock;
        private final VoxelShape[] occlusionShapes;
        private final VoxelShape collisionShape;
        private final boolean largeCollisionShape;
        private final boolean[] isFaceSturdy;
        private final boolean isCollisionShapeFullBlock;
        
        private Cache(final BlockState byj) {
            final Block bph3 = byj.getBlock();
            this.canOcclude = bph3.canOcclude(byj);
            this.solidRender = bph3.isSolidRender(byj, EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
            this.propagatesSkylightDown = bph3.propagatesSkylightDown(byj, EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
            this.lightBlock = bph3.getLightBlock(byj, EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
            if (!byj.canOcclude()) {
                this.occlusionShapes = null;
            }
            else {
                this.occlusionShapes = new VoxelShape[Cache.DIRECTIONS.length];
                final VoxelShape cwf4 = bph3.getOcclusionShape(byj, EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
                for (final Direction fp8 : Cache.DIRECTIONS) {
                    this.occlusionShapes[fp8.ordinal()] = Shapes.getFaceShape(cwf4, fp8);
                }
            }
            this.collisionShape = bph3.getCollisionShape(byj, EmptyBlockGetter.INSTANCE, BlockPos.ZERO, CollisionContext.empty());
            this.largeCollisionShape = Arrays.<Direction.Axis>stream(Direction.Axis.values()).anyMatch(a -> this.collisionShape.min(a) < 0.0 || this.collisionShape.max(a) > 1.0);
            this.isFaceSturdy = new boolean[6];
            for (final Direction fp9 : Cache.DIRECTIONS) {
                this.isFaceSturdy[fp9.ordinal()] = Block.isFaceSturdy(byj, EmptyBlockGetter.INSTANCE, BlockPos.ZERO, fp9);
            }
            this.isCollisionShapeFullBlock = Block.isShapeFullBlock(byj.getCollisionShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO));
        }
        
        static {
            DIRECTIONS = Direction.values();
        }
    }
}
