package net.minecraft.world.level.chunk;

import net.minecraft.world.level.block.state.AbstractStateHolder;
import net.minecraft.world.level.block.StemGrownBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.StemBlock;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import com.google.common.collect.Lists;
import it.unimi.dsi.fastutil.objects.ObjectSet;
import java.util.List;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.ChestBlockEntity;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.state.properties.ChestType;
import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.block.Blocks;
import com.google.common.collect.Sets;
import java.util.IdentityHashMap;
import org.apache.logging.log4j.LogManager;
import net.minecraft.nbt.Tag;
import net.minecraft.world.level.block.state.BlockState;
import java.util.Iterator;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.core.Vec3i;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.nbt.CompoundTag;
import java.util.Set;
import net.minecraft.world.level.block.Block;
import java.util.Map;
import java.util.EnumSet;
import net.minecraft.core.Direction8;
import org.apache.logging.log4j.Logger;

public class UpgradeData {
    private static final Logger LOGGER;
    public static final UpgradeData EMPTY;
    private static final Direction8[] DIRECTIONS;
    private final EnumSet<Direction8> sides;
    private final int[][] index;
    private static final Map<Block, BlockFixer> MAP;
    private static final Set<BlockFixer> CHUNKY_FIXERS;
    
    private UpgradeData() {
        this.sides = EnumSet.<Direction8>noneOf(Direction8.class);
        this.index = new int[16][];
    }
    
    public UpgradeData(final CompoundTag jt) {
        this();
        if (jt.contains("Indices", 10)) {
            final CompoundTag jt2 = jt.getCompound("Indices");
            for (int integer4 = 0; integer4 < this.index.length; ++integer4) {
                final String string5 = String.valueOf(integer4);
                if (jt2.contains(string5, 11)) {
                    this.index[integer4] = jt2.getIntArray(string5);
                }
            }
        }
        final int integer5 = jt.getInt("Sides");
        for (final Direction8 fq7 : Direction8.values()) {
            if ((integer5 & 1 << fq7.ordinal()) != 0x0) {
                this.sides.add(fq7);
            }
        }
    }
    
    public void upgrade(final LevelChunk cal) {
        this.upgradeInside(cal);
        for (final Direction8 fq6 : UpgradeData.DIRECTIONS) {
            upgradeSides(cal, fq6);
        }
        final Level bjw3 = cal.getLevel();
        UpgradeData.CHUNKY_FIXERS.forEach(a -> a.processChunk(bjw3));
    }
    
    private static void upgradeSides(final LevelChunk cal, final Direction8 fq) {
        final Level bjw3 = cal.getLevel();
        if (!cal.getUpgradeData().sides.remove(fq)) {
            return;
        }
        final Set<Direction> set4 = fq.getDirections();
        final int integer5 = 0;
        final int integer6 = 15;
        final boolean boolean7 = set4.contains(Direction.EAST);
        final boolean boolean8 = set4.contains(Direction.WEST);
        final boolean boolean9 = set4.contains(Direction.SOUTH);
        final boolean boolean10 = set4.contains(Direction.NORTH);
        final boolean boolean11 = set4.size() == 1;
        final ChunkPos bjh12 = cal.getPos();
        final int integer7 = bjh12.getMinBlockX() + ((boolean11 && (boolean10 || boolean9)) ? 1 : (boolean8 ? 0 : 15));
        final int integer8 = bjh12.getMinBlockX() + ((boolean11 && (boolean10 || boolean9)) ? 14 : (boolean8 ? 0 : 15));
        final int integer9 = bjh12.getMinBlockZ() + ((boolean11 && (boolean7 || boolean8)) ? 1 : (boolean10 ? 0 : 15));
        final int integer10 = bjh12.getMinBlockZ() + ((boolean11 && (boolean7 || boolean8)) ? 14 : (boolean10 ? 0 : 15));
        final Direction[] arr17 = Direction.values();
        final BlockPos.MutableBlockPos a18 = new BlockPos.MutableBlockPos();
        for (final BlockPos fk20 : BlockPos.betweenClosed(integer7, 0, integer9, integer8, bjw3.getMaxBuildHeight() - 1, integer10)) {
            BlockState byj22;
            final BlockState byj21 = byj22 = bjw3.getBlockState(fk20);
            for (final Direction fp26 : arr17) {
                a18.set(fk20).move(fp26);
                byj22 = updateState(byj22, fp26, bjw3, fk20, a18);
            }
            Block.updateOrDestroy(byj21, byj22, bjw3, fk20, 18);
        }
    }
    
    private static BlockState updateState(final BlockState byj, final Direction fp, final LevelAccessor bjx, final BlockPos fk4, final BlockPos fk5) {
        return UpgradeData.MAP.getOrDefault(byj.getBlock(), BlockFixers.DEFAULT).updateShape(byj, fp, bjx.getBlockState(fk5), bjx, fk4, fk5);
    }
    
    private void upgradeInside(final LevelChunk cal) {
        try (final BlockPos.PooledMutableBlockPos b3 = BlockPos.PooledMutableBlockPos.acquire();
             final BlockPos.PooledMutableBlockPos b4 = BlockPos.PooledMutableBlockPos.acquire()) {
            final ChunkPos bjh7 = cal.getPos();
            final LevelAccessor bjx8 = cal.getLevel();
            for (int integer9 = 0; integer9 < 16; ++integer9) {
                final LevelChunkSection cam10 = cal.getSections()[integer9];
                final int[] arr11 = this.index[integer9];
                this.index[integer9] = null;
                if (cam10 != null && arr11 != null) {
                    if (arr11.length > 0) {
                        final Direction[] arr12 = Direction.values();
                        final PalettedContainer<BlockState> cas13 = cam10.getStates();
                        for (final int integer10 : arr11) {
                            final int integer11 = integer10 & 0xF;
                            final int integer12 = integer10 >> 8 & 0xF;
                            final int integer13 = integer10 >> 4 & 0xF;
                            b3.set(bjh7.getMinBlockX() + integer11, (integer9 << 4) + integer12, bjh7.getMinBlockZ() + integer13);
                            BlockState byj22;
                            final BlockState byj21 = byj22 = cas13.get(integer10);
                            for (final Direction fp26 : arr12) {
                                b4.set((Vec3i)b3).move(fp26);
                                if (b3.getX() >> 4 == bjh7.x) {
                                    if (b3.getZ() >> 4 == bjh7.z) {
                                        byj22 = updateState(byj22, fp26, bjx8, b3, b4);
                                    }
                                }
                            }
                            Block.updateOrDestroy(byj21, byj22, bjx8, b3, 18);
                        }
                    }
                }
            }
            for (int integer9 = 0; integer9 < this.index.length; ++integer9) {
                if (this.index[integer9] != null) {
                    UpgradeData.LOGGER.warn("Discarding update data for section {} for chunk ({} {})", integer9, bjh7.x, bjh7.z);
                }
                this.index[integer9] = null;
            }
        }
    }
    
    public boolean isEmpty() {
        for (final int[] arr5 : this.index) {
            if (arr5 != null) {
                return false;
            }
        }
        return this.sides.isEmpty();
    }
    
    public CompoundTag write() {
        final CompoundTag jt2 = new CompoundTag();
        final CompoundTag jt3 = new CompoundTag();
        for (int integer4 = 0; integer4 < this.index.length; ++integer4) {
            final String string5 = String.valueOf(integer4);
            if (this.index[integer4] != null && this.index[integer4].length != 0) {
                jt3.putIntArray(string5, this.index[integer4]);
            }
        }
        if (!jt3.isEmpty()) {
            jt2.put("Indices", jt3);
        }
        int integer4 = 0;
        for (final Direction8 fq6 : this.sides) {
            integer4 |= 1 << fq6.ordinal();
        }
        jt2.putByte("Sides", (byte)integer4);
        return jt2;
    }
    
    static {
        LOGGER = LogManager.getLogger();
        EMPTY = new UpgradeData();
        DIRECTIONS = Direction8.values();
        MAP = new IdentityHashMap<Block, BlockFixer>();
        CHUNKY_FIXERS = Sets.newHashSet();
    }
    
    public interface BlockFixer {
        BlockState updateShape(final BlockState byj1, final Direction fp, final BlockState byj3, final LevelAccessor bjx, final BlockPos fk5, final BlockPos fk6);
        
        default void processChunk(final LevelAccessor bjx) {
        }
    }
    
    enum BlockFixers implements BlockFixer {
        BLACKLIST(new Block[] { Blocks.OBSERVER, Blocks.NETHER_PORTAL, Blocks.WHITE_CONCRETE_POWDER, Blocks.ORANGE_CONCRETE_POWDER, Blocks.MAGENTA_CONCRETE_POWDER, Blocks.LIGHT_BLUE_CONCRETE_POWDER, Blocks.YELLOW_CONCRETE_POWDER, Blocks.LIME_CONCRETE_POWDER, Blocks.PINK_CONCRETE_POWDER, Blocks.GRAY_CONCRETE_POWDER, Blocks.LIGHT_GRAY_CONCRETE_POWDER, Blocks.CYAN_CONCRETE_POWDER, Blocks.PURPLE_CONCRETE_POWDER, Blocks.BLUE_CONCRETE_POWDER, Blocks.BROWN_CONCRETE_POWDER, Blocks.GREEN_CONCRETE_POWDER, Blocks.RED_CONCRETE_POWDER, Blocks.BLACK_CONCRETE_POWDER, Blocks.ANVIL, Blocks.CHIPPED_ANVIL, Blocks.DAMAGED_ANVIL, Blocks.DRAGON_EGG, Blocks.GRAVEL, Blocks.SAND, Blocks.RED_SAND, Blocks.OAK_SIGN, Blocks.SPRUCE_SIGN, Blocks.BIRCH_SIGN, Blocks.ACACIA_SIGN, Blocks.JUNGLE_SIGN, Blocks.DARK_OAK_SIGN, Blocks.OAK_WALL_SIGN, Blocks.SPRUCE_WALL_SIGN, Blocks.BIRCH_WALL_SIGN, Blocks.ACACIA_WALL_SIGN, Blocks.JUNGLE_WALL_SIGN, Blocks.DARK_OAK_WALL_SIGN }) {
            @Override
            public BlockState updateShape(final BlockState byj1, final Direction fp, final BlockState byj3, final LevelAccessor bjx, final BlockPos fk5, final BlockPos fk6) {
                return byj1;
            }
        }, 
        DEFAULT(new Block[0]) {
            @Override
            public BlockState updateShape(final BlockState byj1, final Direction fp, final BlockState byj3, final LevelAccessor bjx, final BlockPos fk5, final BlockPos fk6) {
                return byj1.updateShape(fp, bjx.getBlockState(fk6), bjx, fk5, fk6);
            }
        }, 
        CHEST(new Block[] { Blocks.CHEST, Blocks.TRAPPED_CHEST }) {
            @Override
            public BlockState updateShape(final BlockState byj1, final Direction fp, final BlockState byj3, final LevelAccessor bjx, final BlockPos fk5, final BlockPos fk6) {
                if (byj3.getBlock() == byj1.getBlock() && fp.getAxis().isHorizontal() && byj1.<ChestType>getValue(ChestBlock.TYPE) == ChestType.SINGLE && byj3.<ChestType>getValue(ChestBlock.TYPE) == ChestType.SINGLE) {
                    final Direction fp2 = byj1.<Direction>getValue((Property<Direction>)ChestBlock.FACING);
                    if (fp.getAxis() != fp2.getAxis() && fp2 == byj3.<Direction>getValue((Property<Direction>)ChestBlock.FACING)) {
                        final ChestType bzc9 = (fp == fp2.getClockWise()) ? ChestType.LEFT : ChestType.RIGHT;
                        bjx.setBlock(fk6, ((AbstractStateHolder<O, BlockState>)byj3).<ChestType, ChestType>setValue(ChestBlock.TYPE, bzc9.getOpposite()), 18);
                        if (fp2 == Direction.NORTH || fp2 == Direction.EAST) {
                            final BlockEntity bwl10 = bjx.getBlockEntity(fk5);
                            final BlockEntity bwl11 = bjx.getBlockEntity(fk6);
                            if (bwl10 instanceof ChestBlockEntity && bwl11 instanceof ChestBlockEntity) {
                                ChestBlockEntity.swapContents((ChestBlockEntity)bwl10, (ChestBlockEntity)bwl11);
                            }
                        }
                        return ((AbstractStateHolder<O, BlockState>)byj1).<ChestType, ChestType>setValue(ChestBlock.TYPE, bzc9);
                    }
                }
                return byj1;
            }
        }, 
        LEAVES(true, new Block[] { Blocks.ACACIA_LEAVES, Blocks.BIRCH_LEAVES, Blocks.DARK_OAK_LEAVES, Blocks.JUNGLE_LEAVES, Blocks.OAK_LEAVES, Blocks.SPRUCE_LEAVES }) {
            private final ThreadLocal<List<ObjectSet<BlockPos>>> queue;
            
            {
                this.queue = ThreadLocal.<List<ObjectSet<BlockPos>>>withInitial(() -> Lists.newArrayListWithCapacity(7));
            }
            
            @Override
            public BlockState updateShape(final BlockState byj1, final Direction fp, final BlockState byj3, final LevelAccessor bjx, final BlockPos fk5, final BlockPos fk6) {
                final BlockState byj4 = byj1.updateShape(fp, bjx.getBlockState(fk6), bjx, fk5, fk6);
                if (byj1 != byj4) {
                    final int integer9 = byj4.<Integer>getValue((Property<Integer>)BlockStateProperties.DISTANCE);
                    final List<ObjectSet<BlockPos>> list10 = this.queue.get();
                    if (list10.isEmpty()) {
                        for (int integer10 = 0; integer10 < 7; ++integer10) {
                            list10.add((ObjectSet<BlockPos>)new ObjectOpenHashSet());
                        }
                    }
                    list10.get(integer9).add(fk5.immutable());
                }
                return byj1;
            }
            
            @Override
            public void processChunk(final LevelAccessor bjx) {
                final BlockPos.MutableBlockPos a3 = new BlockPos.MutableBlockPos();
                final List<ObjectSet<BlockPos>> list4 = this.queue.get();
                for (int integer5 = 2; integer5 < list4.size(); ++integer5) {
                    final int integer6 = integer5 - 1;
                    final ObjectSet<BlockPos> objectSet7 = list4.get(integer6);
                    final ObjectSet<BlockPos> objectSet8 = list4.get(integer5);
                    for (final BlockPos fk10 : objectSet7) {
                        final BlockState byj11 = bjx.getBlockState(fk10);
                        if (byj11.<Integer>getValue((Property<Integer>)BlockStateProperties.DISTANCE) < integer6) {
                            continue;
                        }
                        bjx.setBlock(fk10, ((AbstractStateHolder<O, BlockState>)byj11).<Comparable, Integer>setValue((Property<Comparable>)BlockStateProperties.DISTANCE, integer6), 18);
                        if (integer5 == 7) {
                            continue;
                        }
                        for (final Direction fp15 : UpgradeData$BlockFixers$4.DIRECTIONS) {
                            a3.set(fk10).move(fp15);
                            final BlockState byj12 = bjx.getBlockState(a3);
                            if (byj12.<Comparable>hasProperty((Property<Comparable>)BlockStateProperties.DISTANCE) && byj11.<Integer>getValue((Property<Integer>)BlockStateProperties.DISTANCE) > integer5) {
                                objectSet8.add(a3.immutable());
                            }
                        }
                    }
                }
                list4.clear();
            }
        }, 
        STEM_BLOCK(new Block[] { Blocks.MELON_STEM, Blocks.PUMPKIN_STEM }) {
            @Override
            public BlockState updateShape(final BlockState byj1, final Direction fp, final BlockState byj3, final LevelAccessor bjx, final BlockPos fk5, final BlockPos fk6) {
                if (byj1.<Integer>getValue((Property<Integer>)StemBlock.AGE) == 7) {
                    final StemGrownBlock buv8 = ((StemBlock)byj1.getBlock()).getFruit();
                    if (byj3.getBlock() == buv8) {
                        return ((AbstractStateHolder<O, BlockState>)buv8.getAttachedStem().defaultBlockState()).<Comparable, Direction>setValue((Property<Comparable>)HorizontalDirectionalBlock.FACING, fp);
                    }
                }
                return byj1;
            }
        };
        
        public static final Direction[] DIRECTIONS;
        
        private BlockFixers(final Block[] arr) {
            this(false, arr);
        }
        
        private BlockFixers(final boolean boolean3, final Block[] arr) {
            for (final Block bph9 : arr) {
                UpgradeData.MAP.put(bph9, this);
            }
            if (boolean3) {
                UpgradeData.CHUNKY_FIXERS.add(this);
            }
        }
        
        static {
            DIRECTIONS = Direction.values();
        }
    }
}
