package net.minecraft.server.level;

import org.apache.logging.log4j.LogManager;
import it.unimi.dsi.fastutil.objects.ObjectListIterator;
import net.minecraft.world.level.chunk.LevelChunkSection;
import java.util.concurrent.CompletableFuture;
import net.minecraft.world.level.chunk.ChunkAccess;
import java.util.function.IntSupplier;
import javax.annotation.Nullable;
import net.minecraft.world.level.chunk.DataLayer;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.core.BlockPos;
import net.minecraft.Util;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.world.level.chunk.LightChunkGetter;
import java.util.concurrent.atomic.AtomicBoolean;
import net.minecraft.util.thread.ProcessorHandle;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.objects.ObjectList;
import net.minecraft.util.thread.ProcessorMailbox;
import org.apache.logging.log4j.Logger;
import net.minecraft.world.level.lighting.LevelLightEngine;

public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable {
    private static final Logger LOGGER;
    private final ProcessorMailbox<Runnable> taskMailbox;
    private final ObjectList<Pair<TaskType, Runnable>> lightTasks;
    private final ChunkMap chunkMap;
    private final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> sorterMailbox;
    private volatile int taskPerBatch;
    private final AtomicBoolean scheduled;
    
    public ThreadedLevelLightEngine(final LightChunkGetter chl, final ChunkMap yp, final boolean boolean3, final ProcessorMailbox<Runnable> amq, final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> amp) {
        super(chl, true, boolean3);
        this.lightTasks = (ObjectList<Pair<TaskType, Runnable>>)new ObjectArrayList();
        this.taskPerBatch = 5;
        this.scheduled = new AtomicBoolean();
        this.chunkMap = yp;
        this.sorterMailbox = amp;
        this.taskMailbox = amq;
    }
    
    @Override
    public void close() {
    }
    
    @Override
    public int runUpdates(final int integer, final boolean boolean2, final boolean boolean3) {
        throw Util.<UnsupportedOperationException>pauseInIde(new UnsupportedOperationException("Ran authomatically on a different thread!"));
    }
    
    @Override
    public void onBlockEmissionIncrease(final BlockPos fu, final int integer) {
        throw Util.<UnsupportedOperationException>pauseInIde(new UnsupportedOperationException("Ran authomatically on a different thread!"));
    }
    
    @Override
    public void checkBlock(final BlockPos fu) {
        final BlockPos fu2 = fu.immutable();
        this.addTask(fu.getX() >> 4, fu.getZ() >> 4, TaskType.POST_UPDATE, Util.name(() -> super.checkBlock(fu2), () -> "checkBlock " + fu2));
    }
    
    protected void updateChunkStatus(final ChunkPos bph) {
        int integer3;
        int integer4;
        this.addTask(bph.x, bph.z, () -> 0, TaskType.PRE_UPDATE, Util.name(() -> {
            super.retainData(bph, false);
            super.enableLightSources(bph, false);
            for (integer3 = -1; integer3 < 17; ++integer3) {
                super.queueSectionData(LightLayer.BLOCK, SectionPos.of(bph, integer3), null, true);
                super.queueSectionData(LightLayer.SKY, SectionPos.of(bph, integer3), null, true);
            }
            for (integer4 = 0; integer4 < 16; ++integer4) {
                super.updateSectionStatus(SectionPos.of(bph, integer4), true);
            }
        }, () -> "updateChunkStatus " + bph + " " + true));
    }
    
    @Override
    public void updateSectionStatus(final SectionPos go, final boolean boolean2) {
        this.addTask(go.x(), go.z(), () -> 0, TaskType.PRE_UPDATE, Util.name(() -> super.updateSectionStatus(go, boolean2), () -> "updateSectionStatus " + go + " " + boolean2));
    }
    
    @Override
    public void enableLightSources(final ChunkPos bph, final boolean boolean2) {
        this.addTask(bph.x, bph.z, TaskType.PRE_UPDATE, Util.name(() -> super.enableLightSources(bph, boolean2), () -> "enableLight " + bph + " " + boolean2));
    }
    
    @Override
    public void queueSectionData(final LightLayer bqi, final SectionPos go, @Nullable final DataLayer chd, final boolean boolean4) {
        this.addTask(go.x(), go.z(), () -> 0, TaskType.PRE_UPDATE, Util.name(() -> super.queueSectionData(bqi, go, chd, boolean4), () -> "queueData " + go));
    }
    
    private void addTask(final int integer1, final int integer2, final TaskType a, final Runnable runnable) {
        this.addTask(integer1, integer2, this.chunkMap.getChunkQueueLevel(ChunkPos.asLong(integer1, integer2)), a, runnable);
    }
    
    private void addTask(final int integer1, final int integer2, final IntSupplier intSupplier, final TaskType a, final Runnable runnable) {
        this.sorterMailbox.tell(ChunkTaskPriorityQueueSorter.message(() -> {
            this.lightTasks.add(Pair.of((Object)a, (Object)runnable));
            if (this.lightTasks.size() >= this.taskPerBatch) {
                this.runUpdate();
            }
        }, ChunkPos.asLong(integer1, integer2), intSupplier));
    }
    
    @Override
    public void retainData(final ChunkPos bph, final boolean boolean2) {
        this.addTask(bph.x, bph.z, () -> 0, TaskType.PRE_UPDATE, Util.name(() -> super.retainData(bph, boolean2), () -> "retainData " + bph));
    }
    
    public CompletableFuture<ChunkAccess> lightChunk(final ChunkAccess cgy, final boolean boolean2) {
        final ChunkPos bph4 = cgy.getPos();
        cgy.setLightCorrect(false);
        final LevelChunkSection[] arr5;
        int integer6;
        LevelChunkSection chk7;
        final ChunkPos bph5;
        this.addTask(bph4.x, bph4.z, TaskType.PRE_UPDATE, Util.name(() -> {
            arr5 = cgy.getSections();
            for (integer6 = 0; integer6 < 16; ++integer6) {
                chk7 = arr5[integer6];
                if (!LevelChunkSection.isEmpty(chk7)) {
                    super.updateSectionStatus(SectionPos.of(bph5, integer6), false);
                }
            }
            super.enableLightSources(bph5, true);
            if (!boolean2) {
                cgy.getLights().forEach(fu -> super.onBlockEmissionIncrease(fu, cgy.getLightEmission(fu)));
            }
            this.chunkMap.releaseLightTicket(bph5);
            return;
        }, () -> "lightChunk " + bph4 + " " + boolean2));
        final ChunkPos bph6;
        final ChunkPos chunkPos;
        return CompletableFuture.<ChunkAccess>supplyAsync(() -> {
            cgy.setLightCorrect(true);
            super.retainData(bph6, false);
            return cgy;
        }, runnable -> this.addTask(chunkPos.x, chunkPos.z, TaskType.POST_UPDATE, runnable));
    }
    
    public void tryScheduleUpdate() {
        if ((!this.lightTasks.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) {
            this.taskMailbox.tell(() -> {
                this.runUpdate();
                this.scheduled.set(false);
            });
        }
    }
    
    private void runUpdate() {
        int integer2;
        ObjectListIterator<Pair<TaskType, Runnable>> objectListIterator3;
        int integer3;
        Pair<TaskType, Runnable> pair5;
        for (integer2 = Math.min(this.lightTasks.size(), this.taskPerBatch), objectListIterator3 = (ObjectListIterator<Pair<TaskType, Runnable>>)this.lightTasks.iterator(), integer3 = 0; objectListIterator3.hasNext() && integer3 < integer2; ++integer3) {
            pair5 = (Pair<TaskType, Runnable>)objectListIterator3.next();
            if (pair5.getFirst() == TaskType.PRE_UPDATE) {
                ((Runnable)pair5.getSecond()).run();
            }
        }
        objectListIterator3.back(integer3);
        super.runUpdates(Integer.MAX_VALUE, true, true);
        for (integer3 = 0; objectListIterator3.hasNext() && integer3 < integer2; ++integer3) {
            pair5 = (Pair<TaskType, Runnable>)objectListIterator3.next();
            if (pair5.getFirst() == TaskType.POST_UPDATE) {
                ((Runnable)pair5.getSecond()).run();
            }
            objectListIterator3.remove();
        }
    }
    
    public void setTaskPerBatch(final int integer) {
        this.taskPerBatch = integer;
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
    
    enum TaskType {
        PRE_UPDATE, 
        POST_UPDATE;
    }
}
