package net.minecraft.world.level.lighting;

import net.minecraft.world.level.chunk.DataLayer;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import org.apache.commons.lang3.mutable.MutableInt;
import java.util.Arrays;
import javax.annotation.Nullable;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.chunk.LightChunkGetter;
import net.minecraft.core.Direction;

public abstract class LayerLightEngine<M extends DataLayerStorageMap<M>, S extends LayerLightSectionStorage<M>> extends DynamicGraphMinFixedPoint implements LayerLightEventListener {
    private static final Direction[] DIRECTIONS;
    protected final LightChunkGetter chunkSource;
    protected final LightLayer layer;
    protected final S storage;
    private boolean runningLightUpdates;
    protected final BlockPos.MutableBlockPos pos;
    private final long[] lastChunkPos;
    private final BlockGetter[] lastChunk;
    
    public LayerLightEngine(final LightChunkGetter chl, final LightLayer bqi, final S cwq) {
        super(16, 256, 8192);
        this.pos = new BlockPos.MutableBlockPos();
        this.lastChunkPos = new long[2];
        this.lastChunk = new BlockGetter[2];
        this.chunkSource = chl;
        this.layer = bqi;
        this.storage = cwq;
        this.clearCache();
    }
    
    @Override
    protected void checkNode(final long long1) {
        this.storage.runAllUpdates();
        if (this.storage.storingLightForSection(SectionPos.blockToSection(long1))) {
            super.checkNode(long1);
        }
    }
    
    @Nullable
    private BlockGetter getChunk(final int integer1, final int integer2) {
        final long long4 = ChunkPos.asLong(integer1, integer2);
        for (int integer3 = 0; integer3 < 2; ++integer3) {
            if (long4 == this.lastChunkPos[integer3]) {
                return this.lastChunk[integer3];
            }
        }
        final BlockGetter bpg6 = this.chunkSource.getChunkForLighting(integer1, integer2);
        for (int integer4 = 1; integer4 > 0; --integer4) {
            this.lastChunkPos[integer4] = this.lastChunkPos[integer4 - 1];
            this.lastChunk[integer4] = this.lastChunk[integer4 - 1];
        }
        this.lastChunkPos[0] = long4;
        return this.lastChunk[0] = bpg6;
    }
    
    private void clearCache() {
        Arrays.fill(this.lastChunkPos, ChunkPos.INVALID_CHUNK_POS);
        Arrays.fill(this.lastChunk, null);
    }
    
    protected BlockState getStateAndOpacity(final long long1, @Nullable final MutableInt mutableInt) {
        if (long1 == Long.MAX_VALUE) {
            if (mutableInt != null) {
                mutableInt.setValue(0);
            }
            return Blocks.AIR.defaultBlockState();
        }
        final int integer5 = SectionPos.blockToSectionCoord(BlockPos.getX(long1));
        final int integer6 = SectionPos.blockToSectionCoord(BlockPos.getZ(long1));
        final BlockGetter bpg7 = this.getChunk(integer5, integer6);
        if (bpg7 == null) {
            if (mutableInt != null) {
                mutableInt.setValue(16);
            }
            return Blocks.BEDROCK.defaultBlockState();
        }
        this.pos.set(long1);
        final BlockState cfj8 = bpg7.getBlockState(this.pos);
        final boolean boolean9 = cfj8.canOcclude() && cfj8.useShapeForLightOcclusion();
        if (mutableInt != null) {
            mutableInt.setValue(cfj8.getLightBlock(this.chunkSource.getLevel(), this.pos));
        }
        return boolean9 ? cfj8 : Blocks.AIR.defaultBlockState();
    }
    
    protected VoxelShape getShape(final BlockState cfj, final long long2, final Direction fz) {
        return cfj.canOcclude() ? cfj.getFaceOcclusionShape(this.chunkSource.getLevel(), this.pos.set(long2), fz) : Shapes.empty();
    }
    
    public static int getLightBlockInto(final BlockGetter bpg, final BlockState cfj2, final BlockPos fu3, final BlockState cfj4, final BlockPos fu5, final Direction fz, final int integer) {
        final boolean boolean8 = cfj2.canOcclude() && cfj2.useShapeForLightOcclusion();
        final boolean boolean9 = cfj4.canOcclude() && cfj4.useShapeForLightOcclusion();
        if (!boolean8 && !boolean9) {
            return integer;
        }
        final VoxelShape dfg10 = boolean8 ? cfj2.getOcclusionShape(bpg, fu3) : Shapes.empty();
        final VoxelShape dfg11 = boolean9 ? cfj4.getOcclusionShape(bpg, fu5) : Shapes.empty();
        if (Shapes.mergedFaceOccludes(dfg10, dfg11, fz)) {
            return 16;
        }
        return integer;
    }
    
    @Override
    protected boolean isSource(final long long1) {
        return long1 == Long.MAX_VALUE;
    }
    
    @Override
    protected int getComputedLevel(final long long1, final long long2, final int integer) {
        return 0;
    }
    
    @Override
    protected int getLevel(final long long1) {
        if (long1 == Long.MAX_VALUE) {
            return 0;
        }
        return 15 - this.storage.getStoredLevel(long1);
    }
    
    protected int getLevel(final DataLayer chd, final long long2) {
        return 15 - chd.get(SectionPos.sectionRelative(BlockPos.getX(long2)), SectionPos.sectionRelative(BlockPos.getY(long2)), SectionPos.sectionRelative(BlockPos.getZ(long2)));
    }
    
    @Override
    protected void setLevel(final long long1, final int integer) {
        this.storage.setStoredLevel(long1, Math.min(15, 15 - integer));
    }
    
    @Override
    protected int computeLevelFromNeighbor(final long long1, final long long2, final int integer) {
        return 0;
    }
    
    public boolean hasLightWork() {
        return this.hasWork() || this.storage.hasWork() || this.storage.hasInconsistencies();
    }
    
    public int runUpdates(int integer, final boolean boolean2, final boolean boolean3) {
        if (!this.runningLightUpdates) {
            if (this.storage.hasWork()) {
                integer = this.storage.runUpdates(integer);
                if (integer == 0) {
                    return integer;
                }
            }
            this.storage.markNewInconsistencies(this, boolean2, boolean3);
        }
        this.runningLightUpdates = true;
        if (this.hasWork()) {
            integer = this.runUpdates(integer);
            this.clearCache();
            if (integer == 0) {
                return integer;
            }
        }
        this.runningLightUpdates = false;
        this.storage.swapSectionMap();
        return integer;
    }
    
    protected void queueSectionData(final long long1, @Nullable final DataLayer chd, final boolean boolean3) {
        this.storage.queueSectionData(long1, chd, boolean3);
    }
    
    @Nullable
    @Override
    public DataLayer getDataLayerData(final SectionPos go) {
        return this.storage.getDataLayerData(go.asLong());
    }
    
    @Override
    public int getLightValue(final BlockPos fu) {
        return this.storage.getLightValue(fu.asLong());
    }
    
    public String getDebugData(final long long1) {
        return "" + this.storage.getLevel(long1);
    }
    
    public void checkBlock(final BlockPos fu) {
        final long long3 = fu.asLong();
        this.checkNode(long3);
        for (final Direction fz8 : LayerLightEngine.DIRECTIONS) {
            this.checkNode(BlockPos.offset(long3, fz8));
        }
    }
    
    public void onBlockEmissionIncrease(final BlockPos fu, final int integer) {
    }
    
    @Override
    public void updateSectionStatus(final SectionPos go, final boolean boolean2) {
        this.storage.updateSectionStatus(go.asLong(), boolean2);
    }
    
    public void enableLightSources(final ChunkPos bph, final boolean boolean2) {
        final long long4 = SectionPos.getZeroNode(SectionPos.asLong(bph.x, 0, bph.z));
        this.storage.enableLightSources(long4, boolean2);
    }
    
    public void retainData(final ChunkPos bph, final boolean boolean2) {
        final long long4 = SectionPos.getZeroNode(SectionPos.asLong(bph.x, 0, bph.z));
        this.storage.retainData(long4, boolean2);
    }
    
    static {
        DIRECTIONS = Direction.values();
    }
}
