package net.minecraft.server.level;

import org.apache.logging.log4j.LogManager;
import com.google.common.annotations.VisibleForTesting;
import com.mojang.datafixers.util.Either;
import java.util.stream.Stream;
import net.minecraft.Util;
import java.util.stream.Collector;
import java.util.concurrent.CompletableFuture;
import java.util.Optional;
import java.util.function.IntConsumer;
import net.minecraft.world.level.ChunkPos;
import java.util.function.IntSupplier;
import com.google.common.collect.Sets;
import java.util.stream.Collectors;
import java.util.concurrent.Executor;
import java.util.List;
import net.minecraft.util.thread.StrictQueue;
import net.minecraft.util.thread.ProcessorMailbox;
import java.util.Set;
import net.minecraft.util.Unit;
import java.util.function.Function;
import net.minecraft.util.thread.ProcessorHandle;
import java.util.Map;
import org.apache.logging.log4j.Logger;

public class ChunkTaskPriorityQueueSorter implements AutoCloseable, ChunkHolder.LevelChangeListener {
    private static final Logger LOGGER;
    private final Map<ProcessorHandle<?>, ChunkTaskPriorityQueue<? extends Function<ProcessorHandle<Unit>, ?>>> queues;
    private final Set<ProcessorHandle<?>> sleeping;
    private final ProcessorMailbox<StrictQueue.IntRunnable> mailbox;
    
    public ChunkTaskPriorityQueueSorter(final List<ProcessorHandle<?>> list, final Executor executor, final int integer) {
        final ChunkTaskPriorityQueue chunkTaskPriorityQueue;
        this.queues = list.stream().collect(Collectors.toMap(Function.identity(), amp -> {
            new ChunkTaskPriorityQueue(amp.name() + "_queue", integer);
            return chunkTaskPriorityQueue;
        }));
        this.sleeping = Sets.newHashSet(list);
        this.mailbox = new ProcessorMailbox<StrictQueue.IntRunnable>(new StrictQueue.FixedPriorityQueue(4), executor, "sorter");
    }
    
    public static Message<Runnable> message(final Runnable runnable, final long long2, final IntSupplier intSupplier) {
        return new Message<Runnable>(amp -> () -> {
            runnable.run();
            amp.tell(Unit.INSTANCE);
        }, long2, intSupplier);
    }
    
    public static Message<Runnable> message(final ChunkHolder yo, final Runnable runnable) {
        return message(runnable, yo.getPos().toLong(), yo::getQueueLevel);
    }
    
    public static Release release(final Runnable runnable, final long long2, final boolean boolean3) {
        return new Release(runnable, long2, boolean3);
    }
    
    public <T> ProcessorHandle<Message<T>> getProcessor(final ProcessorHandle<T> amp, final boolean boolean2) {
        return this.mailbox.<ProcessorHandle<Message<T>>>ask(amp3 -> new StrictQueue.IntRunnable(0, () -> {
            this.<T>getQueue(amp);
            amp3.tell(ProcessorHandle.of("chunk priority sorter around " + amp.name(), a -> this.<T>submit(amp, a.task, a.pos, a.level, boolean2)));
        })).join();
    }
    
    public ProcessorHandle<Release> getReleaseProcessor(final ProcessorHandle<Runnable> amp) {
        return this.mailbox.<ProcessorHandle<Release>>ask(amp2 -> new StrictQueue.IntRunnable(0, () -> amp2.tell(ProcessorHandle.of("chunk priority sorter around " + amp.name(), b -> this.<Runnable>release(amp, b.pos, b.task, b.clearQueue))))).join();
    }
    
    @Override
    public void onLevelChange(final ChunkPos bph, final IntSupplier intSupplier, final int integer, final IntConsumer intConsumer) {
        final int integer2;
        this.mailbox.tell(new StrictQueue.IntRunnable(0, () -> {
            integer2 = intSupplier.getAsInt();
            this.queues.values().forEach(yq -> yq.resortChunkTasks(integer2, bph, integer));
            intConsumer.accept(integer);
        }));
    }
    
    private <T> void release(final ProcessorHandle<T> amp, final long long2, final Runnable runnable, final boolean boolean4) {
        final ChunkTaskPriorityQueue<Function<ProcessorHandle<Unit>, Object>> yq7;
        this.mailbox.tell(new StrictQueue.IntRunnable(1, () -> {
            yq7 = this.getQueue(amp);
            yq7.release(long2, boolean4);
            if (this.sleeping.remove(amp)) {
                this.pollTask(yq7, (ProcessorHandle<Object>)amp);
            }
            runnable.run();
        }));
    }
    
    private <T> void submit(final ProcessorHandle<T> amp, final Function<ProcessorHandle<Unit>, T> function, final long long3, final IntSupplier intSupplier, final boolean boolean5) {
        final ChunkTaskPriorityQueue<Function<ProcessorHandle<Unit>, Object>> yq8;
        final int integer9;
        this.mailbox.tell(new StrictQueue.IntRunnable(2, () -> {
            yq8 = this.getQueue(amp);
            integer9 = intSupplier.getAsInt();
            yq8.submit(Optional.<Function<ProcessorHandle<Unit>, Object>>of((Function<ProcessorHandle<Unit>, Object>)function), long3, integer9);
            if (boolean5) {
                yq8.submit(Optional.<Function<ProcessorHandle<Unit>, Object>>empty(), long3, integer9);
            }
            if (this.sleeping.remove(amp)) {
                this.pollTask(yq8, (ProcessorHandle<Object>)amp);
            }
        }));
    }
    
    private <T> void pollTask(final ChunkTaskPriorityQueue<Function<ProcessorHandle<Unit>, T>> yq, final ProcessorHandle<T> amp) {
        final Stream<Either<Function<ProcessorHandle<Unit>, Object>, Runnable>> stream4;
        this.mailbox.tell(new StrictQueue.IntRunnable(3, () -> {
            stream4 = (Stream<Either<Function<ProcessorHandle<Unit>, Object>, Runnable>>)yq.pop();
            if (stream4 == null) {
                this.sleeping.add(amp);
            }
            else {
                Util.sequence(stream4.map(either -> (CompletableFuture)either.map((Function)amp::ask, runnable -> {
                    runnable.run();
                    return CompletableFuture.<Unit>completedFuture(Unit.INSTANCE);
                })).collect(Collectors.toList())).thenAccept(list -> this.pollTask((ChunkTaskPriorityQueue<Function<ProcessorHandle<Unit>, Object>>)yq, (ProcessorHandle<Object>)amp));
            }
        }));
    }
    
    private <T> ChunkTaskPriorityQueue<Function<ProcessorHandle<Unit>, T>> getQueue(final ProcessorHandle<T> amp) {
        final ChunkTaskPriorityQueue<? extends Function<ProcessorHandle<Unit>, ?>> yq3 = this.queues.get(amp);
        if (yq3 == null) {
            throw Util.<IllegalArgumentException>pauseInIde(new IllegalArgumentException("No queue for: " + amp));
        }
        return (ChunkTaskPriorityQueue<Function<ProcessorHandle<Unit>, T>>)yq3;
    }
    
    @VisibleForTesting
    public String getDebugStatus() {
        return this.queues.entrySet().stream().map(entry -> entry.getKey().name() + "=[" + ((ChunkTaskPriorityQueue)entry.getValue()).getAcquired().stream().map(long1 -> long1 + ":" + new ChunkPos(long1)).collect(Collectors.joining(",")) + "]").collect(Collectors.joining(",")) + ", s=" + this.sleeping.size();
    }
    
    @Override
    public void close() {
        this.queues.keySet().forEach(ProcessorHandle::close);
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
    
    public static final class Message<T> {
        private final Function<ProcessorHandle<Unit>, T> task;
        private final long pos;
        private final IntSupplier level;
        
        private Message(final Function<ProcessorHandle<Unit>, T> function, final long long2, final IntSupplier intSupplier) {
            this.task = function;
            this.pos = long2;
            this.level = intSupplier;
        }
    }
    
    public static final class Release {
        private final Runnable task;
        private final long pos;
        private final boolean clearQueue;
        
        private Release(final Runnable runnable, final long long2, final boolean boolean3) {
            this.task = runnable;
            this.pos = long2;
            this.clearQueue = boolean3;
        }
    }
}
