package net.minecraft.world.level.material;

import net.minecraft.world.level.block.state.StateHolder;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.tags.Tag;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.block.DoorBlock;
import it.unimi.dsi.fastutil.shorts.Short2BooleanOpenHashMap;
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.shorts.Short2BooleanMap;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
import net.minecraft.world.level.block.LiquidBlockContainer;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.state.BlockState;
import java.util.Iterator;
import net.minecraft.core.Vec3i;
import net.minecraft.core.Direction;
import net.minecraft.world.phys.Vec3;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.state.StateDefinition;
import com.google.common.collect.Maps;
import net.minecraft.world.phys.shapes.VoxelShape;
import java.util.Map;
import net.minecraft.world.level.block.Block;
import it.unimi.dsi.fastutil.objects.Object2ByteLinkedOpenHashMap;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.block.state.properties.BooleanProperty;

public abstract class FlowingFluid extends Fluid {
    public static final BooleanProperty FALLING;
    public static final IntegerProperty LEVEL;
    private static final ThreadLocal<Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey>> OCCLUSION_CACHE;
    private final Map<FluidState, VoxelShape> shapes;
    
    public FlowingFluid() {
        this.shapes = Maps.newIdentityHashMap();
    }
    
    @Override
    protected void createFluidStateDefinition(final StateDefinition.Builder<Fluid, FluidState> a) {
        a.add(FlowingFluid.FALLING);
    }
    
    public Vec3 getFlow(final BlockGetter bpg, final BlockPos fu, final FluidState cxa) {
        double double5 = 0.0;
        double double6 = 0.0;
        final BlockPos.MutableBlockPos a9 = new BlockPos.MutableBlockPos();
        for (final Direction fz11 : Direction.Plane.HORIZONTAL) {
            a9.setWithOffset(fu, fz11);
            final FluidState cxa2 = bpg.getFluidState(a9);
            if (!this.affectsFlow(cxa2)) {
                continue;
            }
            float float13 = cxa2.getOwnHeight();
            float float14 = 0.0f;
            if (float13 == 0.0f) {
                if (!bpg.getBlockState(a9).getMaterial().blocksMotion()) {
                    final BlockPos fu2 = a9.below();
                    final FluidState cxa3 = bpg.getFluidState(fu2);
                    if (this.affectsFlow(cxa3)) {
                        float13 = cxa3.getOwnHeight();
                        if (float13 > 0.0f) {
                            float14 = cxa.getOwnHeight() - (float13 - 0.8888889f);
                        }
                    }
                }
            }
            else if (float13 > 0.0f) {
                float14 = cxa.getOwnHeight() - float13;
            }
            if (float14 == 0.0f) {
                continue;
            }
            double5 += fz11.getStepX() * float14;
            double6 += fz11.getStepZ() * float14;
        }
        Vec3 dem10 = new Vec3(double5, 0.0, double6);
        if (cxa.<Boolean>getValue((Property<Boolean>)FlowingFluid.FALLING)) {
            for (final Direction fz12 : Direction.Plane.HORIZONTAL) {
                a9.setWithOffset(fu, fz12);
                if (this.isSolidFace(bpg, a9, fz12) || this.isSolidFace(bpg, a9.above(), fz12)) {
                    dem10 = dem10.normalize().add(0.0, -6.0, 0.0);
                    break;
                }
            }
        }
        return dem10.normalize();
    }
    
    private boolean affectsFlow(final FluidState cxa) {
        return cxa.isEmpty() || cxa.getType().isSame(this);
    }
    
    protected boolean isSolidFace(final BlockGetter bpg, final BlockPos fu, final Direction fz) {
        final BlockState cfj5 = bpg.getBlockState(fu);
        final FluidState cxa6 = bpg.getFluidState(fu);
        return !cxa6.getType().isSame(this) && (fz == Direction.UP || (cfj5.getMaterial() != Material.ICE && cfj5.isFaceSturdy(bpg, fu, fz)));
    }
    
    protected void spread(final LevelAccessor bqc, final BlockPos fu, final FluidState cxa) {
        if (cxa.isEmpty()) {
            return;
        }
        final BlockState cfj5 = bqc.getBlockState(fu);
        final BlockPos fu2 = fu.below();
        final BlockState cfj6 = bqc.getBlockState(fu2);
        final FluidState cxa2 = this.getNewLiquid(bqc, fu2, cfj6);
        if (this.canSpreadTo(bqc, fu, cfj5, Direction.DOWN, fu2, cfj6, bqc.getFluidState(fu2), cxa2.getType())) {
            this.spreadTo(bqc, fu2, cfj6, Direction.DOWN, cxa2);
            if (this.sourceNeighborCount(bqc, fu) >= 3) {
                this.spreadToSides(bqc, fu, cxa, cfj5);
            }
        }
        else if (cxa.isSource() || !this.isWaterHole(bqc, cxa2.getType(), fu, cfj5, fu2, cfj6)) {
            this.spreadToSides(bqc, fu, cxa, cfj5);
        }
    }
    
    private void spreadToSides(final LevelAccessor bqc, final BlockPos fu, final FluidState cxa, final BlockState cfj) {
        int integer6 = cxa.getAmount() - this.getDropOff(bqc);
        if (cxa.<Boolean>getValue((Property<Boolean>)FlowingFluid.FALLING)) {
            integer6 = 7;
        }
        if (integer6 <= 0) {
            return;
        }
        final Map<Direction, FluidState> map7 = this.getSpread(bqc, fu, cfj);
        for (final Map.Entry<Direction, FluidState> entry9 : map7.entrySet()) {
            final Direction fz10 = entry9.getKey();
            final FluidState cxa2 = entry9.getValue();
            final BlockPos fu2 = fu.relative(fz10);
            final BlockState cfj2 = bqc.getBlockState(fu2);
            if (this.canSpreadTo(bqc, fu, cfj, fz10, fu2, cfj2, bqc.getFluidState(fu2), cxa2.getType())) {
                this.spreadTo(bqc, fu2, cfj2, fz10, cxa2);
            }
        }
    }
    
    protected FluidState getNewLiquid(final LevelReader bqd, final BlockPos fu, final BlockState cfj) {
        int integer5 = 0;
        int integer6 = 0;
        for (final Direction fz8 : Direction.Plane.HORIZONTAL) {
            final BlockPos fu2 = fu.relative(fz8);
            final BlockState cfj2 = bqd.getBlockState(fu2);
            final FluidState cxa11 = cfj2.getFluidState();
            if (cxa11.getType().isSame(this) && this.canPassThroughWall(fz8, bqd, fu, cfj, fu2, cfj2)) {
                if (cxa11.isSource()) {
                    ++integer6;
                }
                integer5 = Math.max(integer5, cxa11.getAmount());
            }
        }
        if (this.canConvertToSource() && integer6 >= 2) {
            final BlockState cfj3 = bqd.getBlockState(fu.below());
            final FluidState cxa12 = cfj3.getFluidState();
            if (cfj3.getMaterial().isSolid() || this.isSourceBlockOfThisType(cxa12)) {
                return this.getSource(false);
            }
        }
        final BlockPos fu3 = fu.above();
        final BlockState cfj4 = bqd.getBlockState(fu3);
        final FluidState cxa13 = cfj4.getFluidState();
        if (!cxa13.isEmpty() && cxa13.getType().isSame(this) && this.canPassThroughWall(Direction.UP, bqd, fu, cfj, fu3, cfj4)) {
            return this.getFlowing(8, true);
        }
        final int integer7 = integer5 - this.getDropOff(bqd);
        if (integer7 <= 0) {
            return Fluids.EMPTY.defaultFluidState();
        }
        return this.getFlowing(integer7, false);
    }
    
    private boolean canPassThroughWall(final Direction fz, final BlockGetter bpg, final BlockPos fu3, final BlockState cfj4, final BlockPos fu5, final BlockState cfj6) {
        Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey> object2ByteLinkedOpenHashMap8;
        if (cfj4.getBlock().hasDynamicShape() || cfj6.getBlock().hasDynamicShape()) {
            object2ByteLinkedOpenHashMap8 = null;
        }
        else {
            object2ByteLinkedOpenHashMap8 = FlowingFluid.OCCLUSION_CACHE.get();
        }
        Block.BlockStatePairKey a9;
        if (object2ByteLinkedOpenHashMap8 != null) {
            a9 = new Block.BlockStatePairKey(cfj4, cfj6, fz);
            final byte byte10 = object2ByteLinkedOpenHashMap8.getAndMoveToFirst(a9);
            if (byte10 != 127) {
                return byte10 != 0;
            }
        }
        else {
            a9 = null;
        }
        final VoxelShape dfg10 = cfj4.getCollisionShape(bpg, fu3);
        final VoxelShape dfg11 = cfj6.getCollisionShape(bpg, fu5);
        final boolean boolean12 = !Shapes.mergedFaceOccludes(dfg10, dfg11, fz);
        if (object2ByteLinkedOpenHashMap8 != null) {
            if (object2ByteLinkedOpenHashMap8.size() == 200) {
                object2ByteLinkedOpenHashMap8.removeLastByte();
            }
            object2ByteLinkedOpenHashMap8.putAndMoveToFirst(a9, (byte)(byte)(boolean12 ? 1 : 0));
        }
        return boolean12;
    }
    
    public abstract Fluid getFlowing();
    
    public FluidState getFlowing(final int integer, final boolean boolean2) {
        return (((StateHolder<O, FluidState>)this.getFlowing().defaultFluidState()).setValue((Property<Comparable>)FlowingFluid.LEVEL, integer)).<Comparable, Boolean>setValue((Property<Comparable>)FlowingFluid.FALLING, boolean2);
    }
    
    public abstract Fluid getSource();
    
    public FluidState getSource(final boolean boolean1) {
        return ((StateHolder<O, FluidState>)this.getSource().defaultFluidState()).<Comparable, Boolean>setValue((Property<Comparable>)FlowingFluid.FALLING, boolean1);
    }
    
    protected abstract boolean canConvertToSource();
    
    protected void spreadTo(final LevelAccessor bqc, final BlockPos fu, final BlockState cfj, final Direction fz, final FluidState cxa) {
        if (cfj.getBlock() instanceof LiquidBlockContainer) {
            ((LiquidBlockContainer)cfj.getBlock()).placeLiquid(bqc, fu, cfj, cxa);
        }
        else {
            if (!cfj.isAir()) {
                this.beforeDestroyingBlock(bqc, fu, cfj);
            }
            bqc.setBlock(fu, cxa.createLegacyBlock(), 3);
        }
    }
    
    protected abstract void beforeDestroyingBlock(final LevelAccessor bqc, final BlockPos fu, final BlockState cfj);
    
    private static short getCacheKey(final BlockPos fu1, final BlockPos fu2) {
        final int integer3 = fu2.getX() - fu1.getX();
        final int integer4 = fu2.getZ() - fu1.getZ();
        return (short)((integer3 + 128 & 0xFF) << 8 | (integer4 + 128 & 0xFF));
    }
    
    protected int getSlopeDistance(final LevelReader bqd, final BlockPos fu2, final int integer, final Direction fz, final BlockState cfj, final BlockPos fu6, final Short2ObjectMap<Pair<BlockState, FluidState>> short2ObjectMap, final Short2BooleanMap short2BooleanMap) {
        int integer2 = 1000;
        for (final Direction fz2 : Direction.Plane.HORIZONTAL) {
            if (fz2 == fz) {
                continue;
            }
            final BlockPos fu7 = fu2.relative(fz2);
            final short short14 = getCacheKey(fu6, fu7);
            final BlockState cfj2;
            final Pair<BlockState, FluidState> pair15 = (Pair<BlockState, FluidState>)short2ObjectMap.computeIfAbsent(short14, integer -> {
                cfj2 = bqd.getBlockState(fu7);
                return Pair.of(cfj2, cfj2.getFluidState());
            });
            final BlockState cfj3 = (BlockState)pair15.getFirst();
            final FluidState cxa17 = (FluidState)pair15.getSecond();
            if (!this.canPassThrough(bqd, this.getFlowing(), fu2, cfj, fz2, fu7, cfj3, cxa17)) {
                continue;
            }
            final BlockPos fu9;
            final BlockPos fu8;
            final BlockState cfj4;
            final BlockState cfj5;
            final boolean boolean18 = short2BooleanMap.computeIfAbsent(short14, integer -> {
                fu8 = fu9.below();
                cfj4 = bqd.getBlockState(fu8);
                return this.isWaterHole(bqd, this.getFlowing(), fu9, cfj5, fu8, cfj4);
            });
            if (boolean18) {
                return integer;
            }
            if (integer >= this.getSlopeFindDistance(bqd)) {
                continue;
            }
            final int integer3 = this.getSlopeDistance(bqd, fu7, integer + 1, fz2.getOpposite(), cfj3, fu6, short2ObjectMap, short2BooleanMap);
            if (integer3 >= integer2) {
                continue;
            }
            integer2 = integer3;
        }
        return integer2;
    }
    
    private boolean isWaterHole(final BlockGetter bpg, final Fluid cwz, final BlockPos fu3, final BlockState cfj4, final BlockPos fu5, final BlockState cfj6) {
        return this.canPassThroughWall(Direction.DOWN, bpg, fu3, cfj4, fu5, cfj6) && (cfj6.getFluidState().getType().isSame(this) || this.canHoldFluid(bpg, fu5, cfj6, cwz));
    }
    
    private boolean canPassThrough(final BlockGetter bpg, final Fluid cwz, final BlockPos fu3, final BlockState cfj4, final Direction fz, final BlockPos fu6, final BlockState cfj7, final FluidState cxa) {
        return !this.isSourceBlockOfThisType(cxa) && this.canPassThroughWall(fz, bpg, fu3, cfj4, fu6, cfj7) && this.canHoldFluid(bpg, fu6, cfj7, cwz);
    }
    
    private boolean isSourceBlockOfThisType(final FluidState cxa) {
        return cxa.getType().isSame(this) && cxa.isSource();
    }
    
    protected abstract int getSlopeFindDistance(final LevelReader bqd);
    
    private int sourceNeighborCount(final LevelReader bqd, final BlockPos fu) {
        int integer4 = 0;
        for (final Direction fz6 : Direction.Plane.HORIZONTAL) {
            final BlockPos fu2 = fu.relative(fz6);
            final FluidState cxa8 = bqd.getFluidState(fu2);
            if (this.isSourceBlockOfThisType(cxa8)) {
                ++integer4;
            }
        }
        return integer4;
    }
    
    protected Map<Direction, FluidState> getSpread(final LevelReader bqd, final BlockPos fu, final BlockState cfj) {
        int integer5 = 1000;
        final Map<Direction, FluidState> map6 = Maps.newEnumMap(Direction.class);
        final Short2ObjectMap<Pair<BlockState, FluidState>> short2ObjectMap7 = (Short2ObjectMap<Pair<BlockState, FluidState>>)new Short2ObjectOpenHashMap();
        final Short2BooleanMap short2BooleanMap8 = (Short2BooleanMap)new Short2BooleanOpenHashMap();
        for (final Direction fz10 : Direction.Plane.HORIZONTAL) {
            final BlockPos fu2 = fu.relative(fz10);
            final short short12 = getCacheKey(fu, fu2);
            final BlockState cfj2;
            final Pair<BlockState, FluidState> pair13 = (Pair<BlockState, FluidState>)short2ObjectMap7.computeIfAbsent(short12, integer -> {
                cfj2 = bqd.getBlockState(fu2);
                return Pair.of(cfj2, cfj2.getFluidState());
            });
            final BlockState cfj3 = (BlockState)pair13.getFirst();
            final FluidState cxa15 = (FluidState)pair13.getSecond();
            final FluidState cxa16 = this.getNewLiquid(bqd, fu2, cfj3);
            if (this.canPassThrough(bqd, cxa16.getType(), fu, cfj, fz10, fu2, cfj3, cxa15)) {
                final BlockPos fu3 = fu2.below();
                final BlockPos blockPos;
                final BlockState cfj4;
                final BlockPos fu4;
                final BlockState cfj5;
                final boolean boolean19 = short2BooleanMap8.computeIfAbsent(short12, integer -> {
                    cfj4 = bqd.getBlockState(blockPos);
                    return this.isWaterHole(bqd, this.getFlowing(), fu4, cfj5, blockPos, cfj4);
                });
                int integer6;
                if (boolean19) {
                    integer6 = 0;
                }
                else {
                    integer6 = this.getSlopeDistance(bqd, fu2, 1, fz10.getOpposite(), cfj3, fu, short2ObjectMap7, short2BooleanMap8);
                }
                if (integer6 < integer5) {
                    map6.clear();
                }
                if (integer6 > integer5) {
                    continue;
                }
                map6.put(fz10, cxa16);
                integer5 = integer6;
            }
        }
        return map6;
    }
    
    private boolean canHoldFluid(final BlockGetter bpg, final BlockPos fu, final BlockState cfj, final Fluid cwz) {
        final Block bvr6 = cfj.getBlock();
        if (bvr6 instanceof LiquidBlockContainer) {
            return ((LiquidBlockContainer)bvr6).canPlaceLiquid(bpg, fu, cfj, cwz);
        }
        if (bvr6 instanceof DoorBlock || bvr6.is(BlockTags.SIGNS) || bvr6 == Blocks.LADDER || bvr6 == Blocks.SUGAR_CANE || bvr6 == Blocks.BUBBLE_COLUMN) {
            return false;
        }
        final Material cxd7 = cfj.getMaterial();
        return cxd7 != Material.PORTAL && cxd7 != Material.STRUCTURAL_AIR && cxd7 != Material.WATER_PLANT && cxd7 != Material.REPLACEABLE_WATER_PLANT && !cxd7.blocksMotion();
    }
    
    protected boolean canSpreadTo(final BlockGetter bpg, final BlockPos fu2, final BlockState cfj3, final Direction fz, final BlockPos fu5, final BlockState cfj6, final FluidState cxa, final Fluid cwz) {
        return cxa.canBeReplacedWith(bpg, fu5, cwz, fz) && this.canPassThroughWall(fz, bpg, fu2, cfj3, fu5, cfj6) && this.canHoldFluid(bpg, fu5, cfj6, cwz);
    }
    
    protected abstract int getDropOff(final LevelReader bqd);
    
    protected int getSpreadDelay(final Level bqb, final BlockPos fu, final FluidState cxa3, final FluidState cxa4) {
        return this.getTickDelay(bqb);
    }
    
    public void tick(final Level bqb, final BlockPos fu, FluidState cxa) {
        if (!cxa.isSource()) {
            final FluidState cxa2 = this.getNewLiquid(bqb, fu, bqb.getBlockState(fu));
            final int integer6 = this.getSpreadDelay(bqb, fu, cxa, cxa2);
            if (cxa2.isEmpty()) {
                cxa = cxa2;
                bqb.setBlock(fu, Blocks.AIR.defaultBlockState(), 3);
            }
            else if (!cxa2.equals(cxa)) {
                cxa = cxa2;
                final BlockState cfj7 = cxa.createLegacyBlock();
                bqb.setBlock(fu, cfj7, 2);
                bqb.getLiquidTicks().scheduleTick(fu, cxa.getType(), integer6);
                bqb.updateNeighborsAt(fu, cfj7.getBlock());
            }
        }
        this.spread(bqb, fu, cxa);
    }
    
    protected static int getLegacyLevel(final FluidState cxa) {
        if (cxa.isSource()) {
            return 0;
        }
        return 8 - Math.min(cxa.getAmount(), 8) + (cxa.<Boolean>getValue((Property<Boolean>)FlowingFluid.FALLING) ? 8 : 0);
    }
    
    private static boolean hasSameAbove(final FluidState cxa, final BlockGetter bpg, final BlockPos fu) {
        return cxa.getType().isSame(bpg.getFluidState(fu.above()).getType());
    }
    
    @Override
    public float getHeight(final FluidState cxa, final BlockGetter bpg, final BlockPos fu) {
        if (hasSameAbove(cxa, bpg, fu)) {
            return 1.0f;
        }
        return cxa.getOwnHeight();
    }
    
    @Override
    public float getOwnHeight(final FluidState cxa) {
        return cxa.getAmount() / 9.0f;
    }
    
    @Override
    public VoxelShape getShape(final FluidState cxa, final BlockGetter bpg, final BlockPos fu) {
        if (cxa.getAmount() == 9 && hasSameAbove(cxa, bpg, fu)) {
            return Shapes.block();
        }
        return this.shapes.computeIfAbsent(cxa, cxa -> Shapes.box(0.0, 0.0, 0.0, 1.0, cxa.getHeight(bpg, fu), 1.0));
    }
    
    static {
        FALLING = BlockStateProperties.FALLING;
        LEVEL = BlockStateProperties.LEVEL_FLOWING;
        final Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey> object2ByteLinkedOpenHashMap1;
        OCCLUSION_CACHE = ThreadLocal.<Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey>>withInitial(() -> {
            object2ByteLinkedOpenHashMap1 = new Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey>(200) {
                protected void rehash(final int integer) {
                }
            };
            object2ByteLinkedOpenHashMap1.defaultReturnValue((byte)127);
            return object2ByteLinkedOpenHashMap1;
        });
    }
}
