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 can, final ChunkMap wp, final boolean boolean3, final ProcessorMailbox<Runnable> aiv, final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> aiu) {
        super(can, true, boolean3);
        this.lightTasks = (ObjectList<Pair<TaskType, Runnable>>)new ObjectArrayList();
        this.taskPerBatch = 5;
        this.scheduled = new AtomicBoolean();
        this.chunkMap = wp;
        this.sorterMailbox = aiu;
        this.taskMailbox = aiv;
    }
    
    @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 fk, final int integer) {
        throw Util.<UnsupportedOperationException>pauseInIde(new UnsupportedOperationException("Ran authomatically on a different thread!"));
    }
    
    @Override
    public void checkBlock(final BlockPos fk) {
        final BlockPos fk2 = fk.immutable();
        this.addTask(fk.getX() >> 4, fk.getZ() >> 4, TaskType.POST_UPDATE, Util.name(() -> super.checkBlock(fk2), () -> "checkBlock " + fk2));
    }
    
    protected void updateChunkStatus(final ChunkPos bjh) {
        int integer3;
        int integer4;
        this.addTask(bjh.x, bjh.z, () -> 0, TaskType.PRE_UPDATE, Util.name(() -> {
            super.retainData(bjh, false);
            super.enableLightSources(bjh, false);
            for (integer3 = -1; integer3 < 17; ++integer3) {
                super.queueSectionData(LightLayer.BLOCK, SectionPos.of(bjh, integer3), null);
                super.queueSectionData(LightLayer.SKY, SectionPos.of(bjh, integer3), null);
            }
            for (integer4 = 0; integer4 < 16; ++integer4) {
                super.updateSectionStatus(SectionPos.of(bjh, integer4), true);
            }
        }, () -> "updateChunkStatus " + bjh + " " + true));
    }
    
    @Override
    public void updateSectionStatus(final SectionPos gd, final boolean boolean2) {
        this.addTask(gd.x(), gd.z(), () -> 0, TaskType.PRE_UPDATE, Util.name(() -> super.updateSectionStatus(gd, boolean2), () -> "updateSectionStatus " + gd + " " + boolean2));
    }
    
    @Override
    public void enableLightSources(final ChunkPos bjh, final boolean boolean2) {
        this.addTask(bjh.x, bjh.z, TaskType.PRE_UPDATE, Util.name(() -> super.enableLightSources(bjh, boolean2), () -> "enableLight " + bjh + " " + boolean2));
    }
    
    @Override
    public void queueSectionData(final LightLayer bkf, final SectionPos gd, @Nullable final DataLayer caf) {
        this.addTask(gd.x(), gd.z(), () -> 0, TaskType.PRE_UPDATE, Util.name(() -> super.queueSectionData(bkf, gd, caf), () -> "queueData " + gd));
    }
    
    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 bjh, final boolean boolean2) {
        this.addTask(bjh.x, bjh.z, () -> 0, TaskType.PRE_UPDATE, Util.name(() -> super.retainData(bjh, boolean2), () -> "retainData " + bjh));
    }
    
    public CompletableFuture<ChunkAccess> lightChunk(final ChunkAccess bzy, final boolean boolean2) {
        final ChunkPos bjh4 = bzy.getPos();
        bzy.setLightCorrect(false);
        final LevelChunkSection[] arr5;
        int integer6;
        LevelChunkSection cam7;
        final ChunkPos bjh5;
        this.addTask(bjh4.x, bjh4.z, TaskType.PRE_UPDATE, Util.name(() -> {
            arr5 = bzy.getSections();
            for (integer6 = 0; integer6 < 16; ++integer6) {
                cam7 = arr5[integer6];
                if (!LevelChunkSection.isEmpty(cam7)) {
                    super.updateSectionStatus(SectionPos.of(bjh5, integer6), false);
                }
            }
            super.enableLightSources(bjh5, true);
            if (!boolean2) {
                bzy.getLights().forEach(fk -> super.onBlockEmissionIncrease(fk, bzy.getLightEmission(fk)));
            }
            this.chunkMap.releaseLightTicket(bjh5);
            return;
        }, () -> "lightChunk " + bjh4 + " " + boolean2));
        final ChunkPos bjh6;
        final ChunkPos chunkPos;
        return CompletableFuture.<ChunkAccess>supplyAsync(() -> {
            bzy.setLightCorrect(true);
            super.retainData(bjh6, false);
            return bzy;
        }, 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;
    }
}
