package net.minecraft.world.level.chunk;

import net.minecraft.world.level.block.state.StateHolder;
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 le) {
        this();
        if (le.contains("Indices", 10)) {
            final CompoundTag le2 = le.getCompound("Indices");
            for (int integer4 = 0; integer4 < this.index.length; ++integer4) {
                final String string5 = String.valueOf(integer4);
                if (le2.contains(string5, 11)) {
                    this.index[integer4] = le2.getIntArray(string5);
                }
            }
        }
        final int integer5 = le.getInt("Sides");
        for (final Direction8 ga7 : Direction8.values()) {
            if ((integer5 & 1 << ga7.ordinal()) != 0x0) {
                this.sides.add(ga7);
            }
        }
    }
    
    public void upgrade(final LevelChunk chj) {
        this.upgradeInside(chj);
        for (final Direction8 ga6 : UpgradeData.DIRECTIONS) {
            upgradeSides(chj, ga6);
        }
        final Level bqb3 = chj.getLevel();
        UpgradeData.CHUNKY_FIXERS.forEach(a -> a.processChunk(bqb3));
    }
    
    private static void upgradeSides(final LevelChunk chj, final Direction8 ga) {
        final Level bqb3 = chj.getLevel();
        if (!chj.getUpgradeData().sides.remove(ga)) {
            return;
        }
        final Set<Direction> set4 = ga.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 bph12 = chj.getPos();
        final int integer7 = bph12.getMinBlockX() + ((boolean11 && (boolean10 || boolean9)) ? 1 : (boolean8 ? 0 : 15));
        final int integer8 = bph12.getMinBlockX() + ((boolean11 && (boolean10 || boolean9)) ? 14 : (boolean8 ? 0 : 15));
        final int integer9 = bph12.getMinBlockZ() + ((boolean11 && (boolean7 || boolean8)) ? 1 : (boolean10 ? 0 : 15));
        final int integer10 = bph12.getMinBlockZ() + ((boolean11 && (boolean7 || boolean8)) ? 14 : (boolean10 ? 0 : 15));
        final Direction[] arr17 = Direction.values();
        final BlockPos.MutableBlockPos a18 = new BlockPos.MutableBlockPos();
        for (final BlockPos fu20 : BlockPos.betweenClosed(integer7, 0, integer9, integer8, bqb3.getMaxBuildHeight() - 1, integer10)) {
            BlockState cfj22;
            final BlockState cfj21 = cfj22 = bqb3.getBlockState(fu20);
            for (final Direction fz26 : arr17) {
                a18.setWithOffset(fu20, fz26);
                cfj22 = updateState(cfj22, fz26, bqb3, fu20, a18);
            }
            Block.updateOrDestroy(cfj21, cfj22, bqb3, fu20, 18);
        }
    }
    
    private static BlockState updateState(final BlockState cfj, final Direction fz, final LevelAccessor bqc, final BlockPos fu4, final BlockPos fu5) {
        return UpgradeData.MAP.getOrDefault(cfj.getBlock(), BlockFixers.DEFAULT).updateShape(cfj, fz, bqc.getBlockState(fu5), bqc, fu4, fu5);
    }
    
    private void upgradeInside(final LevelChunk chj) {
        final BlockPos.MutableBlockPos a3 = new BlockPos.MutableBlockPos();
        final BlockPos.MutableBlockPos a4 = new BlockPos.MutableBlockPos();
        final ChunkPos bph5 = chj.getPos();
        final LevelAccessor bqc6 = chj.getLevel();
        for (int integer7 = 0; integer7 < 16; ++integer7) {
            final LevelChunkSection chk8 = chj.getSections()[integer7];
            final int[] arr9 = this.index[integer7];
            this.index[integer7] = null;
            if (chk8 != null && arr9 != null) {
                if (arr9.length > 0) {
                    final Direction[] arr10 = Direction.values();
                    final PalettedContainer<BlockState> chq11 = chk8.getStates();
                    for (final int integer8 : arr9) {
                        final int integer9 = integer8 & 0xF;
                        final int integer10 = integer8 >> 8 & 0xF;
                        final int integer11 = integer8 >> 4 & 0xF;
                        a3.set(bph5.getMinBlockX() + integer9, (integer7 << 4) + integer10, bph5.getMinBlockZ() + integer11);
                        BlockState cfj20;
                        final BlockState cfj19 = cfj20 = chq11.get(integer8);
                        for (final Direction fz24 : arr10) {
                            a4.setWithOffset(a3, fz24);
                            if (a3.getX() >> 4 == bph5.x) {
                                if (a3.getZ() >> 4 == bph5.z) {
                                    cfj20 = updateState(cfj20, fz24, bqc6, a3, a4);
                                }
                            }
                        }
                        Block.updateOrDestroy(cfj19, cfj20, bqc6, a3, 18);
                    }
                }
            }
        }
        for (int integer7 = 0; integer7 < this.index.length; ++integer7) {
            if (this.index[integer7] != null) {
                UpgradeData.LOGGER.warn("Discarding update data for section {} for chunk ({} {})", integer7, bph5.x, bph5.z);
            }
            this.index[integer7] = null;
        }
    }
    
    public boolean isEmpty() {
        for (final int[] arr5 : this.index) {
            if (arr5 != null) {
                return false;
            }
        }
        return this.sides.isEmpty();
    }
    
    public CompoundTag write() {
        final CompoundTag le2 = new CompoundTag();
        final CompoundTag le3 = 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) {
                le3.putIntArray(string5, this.index[integer4]);
            }
        }
        if (!le3.isEmpty()) {
            le2.put("Indices", le3);
        }
        int integer4 = 0;
        for (final Direction8 ga6 : this.sides) {
            integer4 |= 1 << ga6.ordinal();
        }
        le2.putByte("Sides", (byte)integer4);
        return le2;
    }
    
    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 cfj1, final Direction fz, final BlockState cfj3, final LevelAccessor bqc, final BlockPos fu5, final BlockPos fu6);
        
        default void processChunk(final LevelAccessor bqc) {
        }
    }
    
    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 cfj1, final Direction fz, final BlockState cfj3, final LevelAccessor bqc, final BlockPos fu5, final BlockPos fu6) {
                return cfj1;
            }
        }, 
        DEFAULT(new Block[0]) {
            @Override
            public BlockState updateShape(final BlockState cfj1, final Direction fz, final BlockState cfj3, final LevelAccessor bqc, final BlockPos fu5, final BlockPos fu6) {
                return cfj1.updateShape(fz, bqc.getBlockState(fu6), bqc, fu5, fu6);
            }
        }, 
        CHEST(new Block[] { Blocks.CHEST, Blocks.TRAPPED_CHEST }) {
            @Override
            public BlockState updateShape(final BlockState cfj1, final Direction fz, final BlockState cfj3, final LevelAccessor bqc, final BlockPos fu5, final BlockPos fu6) {
                if (cfj3.is(cfj1.getBlock()) && fz.getAxis().isHorizontal() && cfj1.<ChestType>getValue(ChestBlock.TYPE) == ChestType.SINGLE && cfj3.<ChestType>getValue(ChestBlock.TYPE) == ChestType.SINGLE) {
                    final Direction fz2 = cfj1.<Direction>getValue((Property<Direction>)ChestBlock.FACING);
                    if (fz.getAxis() != fz2.getAxis() && fz2 == cfj3.<Direction>getValue((Property<Direction>)ChestBlock.FACING)) {
                        final ChestType cgb9 = (fz == fz2.getClockWise()) ? ChestType.LEFT : ChestType.RIGHT;
                        bqc.setBlock(fu6, ((StateHolder<O, BlockState>)cfj3).<ChestType, ChestType>setValue(ChestBlock.TYPE, cgb9.getOpposite()), 18);
                        if (fz2 == Direction.NORTH || fz2 == Direction.EAST) {
                            final BlockEntity cdl10 = bqc.getBlockEntity(fu5);
                            final BlockEntity cdl11 = bqc.getBlockEntity(fu6);
                            if (cdl10 instanceof ChestBlockEntity && cdl11 instanceof ChestBlockEntity) {
                                ChestBlockEntity.swapContents((ChestBlockEntity)cdl10, (ChestBlockEntity)cdl11);
                            }
                        }
                        return ((StateHolder<O, BlockState>)cfj1).<ChestType, ChestType>setValue(ChestBlock.TYPE, cgb9);
                    }
                }
                return cfj1;
            }
        }, 
        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 cfj1, final Direction fz, final BlockState cfj3, final LevelAccessor bqc, final BlockPos fu5, final BlockPos fu6) {
                final BlockState cfj4 = cfj1.updateShape(fz, bqc.getBlockState(fu6), bqc, fu5, fu6);
                if (cfj1 != cfj4) {
                    final int integer9 = cfj4.<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(fu5.immutable());
                }
                return cfj1;
            }
            
            @Override
            public void processChunk(final LevelAccessor bqc) {
                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 fu10 : objectSet7) {
                        final BlockState cfj11 = bqc.getBlockState(fu10);
                        if (cfj11.<Integer>getValue((Property<Integer>)BlockStateProperties.DISTANCE) < integer6) {
                            continue;
                        }
                        bqc.setBlock(fu10, ((StateHolder<O, BlockState>)cfj11).<Comparable, Integer>setValue((Property<Comparable>)BlockStateProperties.DISTANCE, integer6), 18);
                        if (integer5 == 7) {
                            continue;
                        }
                        for (final Direction fz15 : UpgradeData$BlockFixers$4.DIRECTIONS) {
                            a3.setWithOffset(fu10, fz15);
                            final BlockState cfj12 = bqc.getBlockState(a3);
                            if (cfj12.<Comparable>hasProperty((Property<Comparable>)BlockStateProperties.DISTANCE) && cfj11.<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 cfj1, final Direction fz, final BlockState cfj3, final LevelAccessor bqc, final BlockPos fu5, final BlockPos fu6) {
                if (cfj1.<Integer>getValue((Property<Integer>)StemBlock.AGE) == 7) {
                    final StemGrownBlock cbq8 = ((StemBlock)cfj1.getBlock()).getFruit();
                    if (cfj3.is(cbq8)) {
                        return ((StateHolder<O, BlockState>)cbq8.getAttachedStem().defaultBlockState()).<Comparable, Direction>setValue((Property<Comparable>)HorizontalDirectionalBlock.FACING, fz);
                    }
                }
                return cfj1;
            }
        };
        
        public static final Direction[] DIRECTIONS;
        
        private BlockFixers(final Block[] arr) {
            this(false, arr);
        }
        
        private BlockFixers(final boolean boolean3, final Block[] arr) {
            for (final Block bvr9 : arr) {
                UpgradeData.MAP.put(bvr9, this);
            }
            if (boolean3) {
                UpgradeData.CHUNKY_FIXERS.add(this);
            }
        }
        
        static {
            DIRECTIONS = Direction.values();
        }
    }
}
