package net.minecraft.server;

import net.minecraft.tags.Tag;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.Iterator;
import java.util.Collection;
import net.minecraft.commands.CommandSourceStack;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.world.level.GameRules;
import com.google.common.collect.Lists;
import net.minecraft.commands.CommandFunction;
import java.util.List;
import java.util.ArrayDeque;
import net.minecraft.resources.ResourceLocation;

public class ServerFunctionManager {
    private static final ResourceLocation TICK_FUNCTION_TAG;
    private static final ResourceLocation LOAD_FUNCTION_TAG;
    private final MinecraftServer server;
    private boolean isInFunction;
    private final ArrayDeque<QueuedCommand> commandQueue;
    private final List<QueuedCommand> nestedCalls;
    private final List<CommandFunction> ticking;
    private boolean postReload;
    private ServerFunctionLibrary library;
    
    public ServerFunctionManager(final MinecraftServer minecraftServer, final ServerFunctionLibrary ut) {
        this.commandQueue = new ArrayDeque<QueuedCommand>();
        this.nestedCalls = Lists.newArrayList();
        this.ticking = Lists.newArrayList();
        this.server = minecraftServer;
        this.postReload(this.library = ut);
    }
    
    public int getCommandLimit() {
        return this.server.getGameRules().getInt(GameRules.RULE_MAX_COMMAND_CHAIN_LENGTH);
    }
    
    public CommandDispatcher<CommandSourceStack> getDispatcher() {
        return this.server.getCommands().getDispatcher();
    }
    
    public void tick() {
        this.executeTagFunctions(this.ticking, ServerFunctionManager.TICK_FUNCTION_TAG);
        if (this.postReload) {
            this.postReload = false;
            final Collection<CommandFunction> collection2 = this.library.getTags().getTagOrEmpty(ServerFunctionManager.LOAD_FUNCTION_TAG).getValues();
            this.executeTagFunctions(collection2, ServerFunctionManager.LOAD_FUNCTION_TAG);
        }
    }
    
    private void executeTagFunctions(final Collection<CommandFunction> collection, final ResourceLocation uh) {
        this.server.getProfiler().push(uh::toString);
        for (final CommandFunction cw5 : collection) {
            this.execute(cw5, this.getGameLoopSender());
        }
        this.server.getProfiler().pop();
    }
    
    public int execute(final CommandFunction cw, final CommandSourceStack cz) {
        final int integer4 = this.getCommandLimit();
        if (this.isInFunction) {
            if (this.commandQueue.size() + this.nestedCalls.size() < integer4) {
                this.nestedCalls.add(new QueuedCommand(this, cz, new CommandFunction.FunctionEntry(cw)));
            }
            return 0;
        }
        try {
            this.isInFunction = true;
            int integer5 = 0;
            final CommandFunction.Entry[] arr6 = cw.getEntries();
            for (int integer6 = arr6.length - 1; integer6 >= 0; --integer6) {
                this.commandQueue.push(new QueuedCommand(this, cz, arr6[integer6]));
            }
            while (!this.commandQueue.isEmpty()) {
                try {
                    final QueuedCommand a7 = this.commandQueue.removeFirst();
                    this.server.getProfiler().push(a7::toString);
                    a7.execute(this.commandQueue, integer4);
                    if (!this.nestedCalls.isEmpty()) {
                        Lists.<QueuedCommand>reverse(this.nestedCalls).forEach(this.commandQueue::addFirst);
                        this.nestedCalls.clear();
                    }
                }
                finally {
                    this.server.getProfiler().pop();
                }
                if (++integer5 >= integer4) {
                    return integer5;
                }
            }
            return integer5;
        }
        finally {
            this.commandQueue.clear();
            this.nestedCalls.clear();
            this.isInFunction = false;
        }
    }
    
    public void replaceLibrary(final ServerFunctionLibrary ut) {
        this.postReload(this.library = ut);
    }
    
    private void postReload(final ServerFunctionLibrary ut) {
        this.ticking.clear();
        this.ticking.addAll(ut.getTags().getTagOrEmpty(ServerFunctionManager.TICK_FUNCTION_TAG).getValues());
        this.postReload = true;
    }
    
    public CommandSourceStack getGameLoopSender() {
        return this.server.createCommandSourceStack().withPermission(2).withSuppressedOutput();
    }
    
    public Optional<CommandFunction> get(final ResourceLocation uh) {
        return this.library.getFunction(uh);
    }
    
    public Tag<CommandFunction> getTag(final ResourceLocation uh) {
        return this.library.getTag(uh);
    }
    
    public Iterable<ResourceLocation> getFunctionNames() {
        return this.library.getFunctions().keySet();
    }
    
    public Iterable<ResourceLocation> getTagNames() {
        return this.library.getTags().getAvailableTags();
    }
    
    static {
        TICK_FUNCTION_TAG = new ResourceLocation("tick");
        LOAD_FUNCTION_TAG = new ResourceLocation("load");
    }
    
    public static class QueuedCommand {
        private final ServerFunctionManager manager;
        private final CommandSourceStack sender;
        private final CommandFunction.Entry entry;
        
        public QueuedCommand(final ServerFunctionManager uu, final CommandSourceStack cz, final CommandFunction.Entry c) {
            this.manager = uu;
            this.sender = cz;
            this.entry = c;
        }
        
        public void execute(final ArrayDeque<QueuedCommand> arrayDeque, final int integer) {
            try {
                this.entry.execute(this.manager, this.sender, arrayDeque, integer);
            }
            catch (Throwable t) {}
        }
        
        @Override
        public String toString() {
            return this.entry.toString();
        }
    }
}
