package net.minecraft.server;

import org.apache.logging.log4j.LogManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import net.minecraft.commands.CommandSource;
import net.minecraft.server.packs.resources.Resource;
import java.io.IOException;
import java.util.concurrent.CompletionException;
import org.apache.commons.io.IOUtils;
import java.nio.charset.StandardCharsets;
import javax.annotation.Nullable;
import net.minecraft.tags.Tag;
import java.util.concurrent.CompletableFuture;
import net.minecraft.server.packs.resources.SimpleResource;
import net.minecraft.server.packs.resources.ResourceManager;
import java.util.function.Consumer;
import java.util.Collection;
import java.util.Iterator;
import net.minecraft.commands.CommandSourceStack;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.world.level.GameRules;
import java.util.Optional;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import net.minecraft.tags.TagCollection;
import java.util.List;
import java.util.ArrayDeque;
import net.minecraft.commands.CommandFunction;
import java.util.Map;
import net.minecraft.resources.ResourceLocation;
import org.apache.logging.log4j.Logger;
import net.minecraft.server.packs.resources.ResourceManagerReloadListener;

public class ServerFunctionManager implements ResourceManagerReloadListener {
    private static final Logger LOGGER;
    private static final ResourceLocation TICK_FUNCTION_TAG;
    private static final ResourceLocation LOAD_FUNCTION_TAG;
    public static final int PATH_PREFIX_LENGTH;
    public static final int PATH_SUFFIX_LENGTH;
    private final MinecraftServer server;
    private final Map<ResourceLocation, CommandFunction> functions;
    private boolean isInFunction;
    private final ArrayDeque<QueuedCommand> commandQueue;
    private final List<QueuedCommand> nestedCalls;
    private final TagCollection<CommandFunction> tags;
    private final List<CommandFunction> ticking;
    private boolean postReload;
    
    public ServerFunctionManager(final MinecraftServer minecraftServer) {
        this.functions = Maps.newHashMap();
        this.commandQueue = new ArrayDeque<QueuedCommand>();
        this.nestedCalls = Lists.newArrayList();
        this.tags = new TagCollection<CommandFunction>(this::get, "tags/functions", true, "function");
        this.ticking = Lists.newArrayList();
        this.server = minecraftServer;
    }
    
    public Optional<CommandFunction> get(final ResourceLocation sm) {
        return Optional.<CommandFunction>ofNullable(this.functions.get(sm));
    }
    
    public MinecraftServer getServer() {
        return this.server;
    }
    
    public int getCommandLimit() {
        return this.server.getGameRules().getInt(GameRules.RULE_MAX_COMMAND_CHAIN_LENGTH);
    }
    
    public Map<ResourceLocation, CommandFunction> getFunctions() {
        return this.functions;
    }
    
    public CommandDispatcher<CommandSourceStack> getDispatcher() {
        return this.server.getCommands().getDispatcher();
    }
    
    public void tick() {
        this.server.getProfiler().push(ServerFunctionManager.TICK_FUNCTION_TAG::toString);
        for (final CommandFunction cn3 : this.ticking) {
            this.execute(cn3, this.getGameLoopSender());
        }
        this.server.getProfiler().pop();
        if (this.postReload) {
            this.postReload = false;
            final Collection<CommandFunction> collection2 = this.getTags().getTagOrEmpty(ServerFunctionManager.LOAD_FUNCTION_TAG).getValues();
            this.server.getProfiler().push(ServerFunctionManager.LOAD_FUNCTION_TAG::toString);
            for (final CommandFunction cn4 : collection2) {
                this.execute(cn4, this.getGameLoopSender());
            }
            this.server.getProfiler().pop();
        }
    }
    
    public int execute(final CommandFunction cn, final CommandSourceStack cq) {
        final int integer4 = this.getCommandLimit();
        if (this.isInFunction) {
            if (this.commandQueue.size() + this.nestedCalls.size() < integer4) {
                this.nestedCalls.add(new QueuedCommand(this, cq, new CommandFunction.FunctionEntry(cn)));
            }
            return 0;
        }
        try {
            this.isInFunction = true;
            int integer5 = 0;
            final CommandFunction.Entry[] arr6 = cn.getEntries();
            for (int integer6 = arr6.length - 1; integer6 >= 0; --integer6) {
                this.commandQueue.push(new QueuedCommand(this, cq, 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;
        }
    }
    
    @Override
    public void onResourceManagerReload(final ResourceManager zb) {
        this.functions.clear();
        this.ticking.clear();
        final Collection<ResourceLocation> collection3 = zb.listResources("functions", string -> string.endsWith(".mcfunction"));
        final List<CompletableFuture<CommandFunction>> list2 = Lists.newArrayList();
        for (final ResourceLocation sm6 : collection3) {
            final String string2 = sm6.getPath();
            final ResourceLocation sm7 = new ResourceLocation(sm6.getNamespace(), string2.substring(ServerFunctionManager.PATH_PREFIX_LENGTH, string2.length() - ServerFunctionManager.PATH_SUFFIX_LENGTH));
            list2.add(CompletableFuture.<List<String>>supplyAsync(() -> readLinesAsync(zb, sm6), SimpleResource.IO_EXECUTOR).thenApplyAsync(list -> CommandFunction.fromLines(sm7, this, list), this.server.getBackgroundTaskExecutor()).<CommandFunction>handle((cn, throwable) -> this.addFunction(cn, throwable, sm6)));
        }
        CompletableFuture.allOf(list2.<CompletableFuture<?>>toArray(new CompletableFuture[0])).join();
        if (!this.functions.isEmpty()) {
            ServerFunctionManager.LOGGER.info("Loaded {} custom command functions", this.functions.size());
        }
        this.tags.load(this.tags.prepare(zb, this.server.getBackgroundTaskExecutor()).join());
        this.ticking.addAll(this.tags.getTagOrEmpty(ServerFunctionManager.TICK_FUNCTION_TAG).getValues());
        this.postReload = true;
    }
    
    @Nullable
    private CommandFunction addFunction(final CommandFunction cn, @Nullable final Throwable throwable, final ResourceLocation sm) {
        if (throwable != null) {
            ServerFunctionManager.LOGGER.error("Couldn't load function at {}", sm, throwable);
            return null;
        }
        synchronized (this.functions) {
            this.functions.put(cn.getId(), cn);
        }
        return cn;
    }
    
    private static List<String> readLinesAsync(final ResourceManager zb, final ResourceLocation sm) {
        try (final Resource za3 = zb.getResource(sm)) {
            return (List<String>)IOUtils.readLines(za3.getInputStream(), StandardCharsets.UTF_8);
        }
        catch (IOException iOException3) {
            throw new CompletionException(iOException3);
        }
    }
    
    public CommandSourceStack getGameLoopSender() {
        return this.server.createCommandSourceStack().withPermission(2).withSuppressedOutput();
    }
    
    public CommandSourceStack getCompilationContext() {
        return new CommandSourceStack(CommandSource.NULL, Vec3.ZERO, Vec2.ZERO, null, this.server.getFunctionCompilationLevel(), "", new TextComponent(""), this.server, null);
    }
    
    public TagCollection<CommandFunction> getTags() {
        return this.tags;
    }
    
    static {
        LOGGER = LogManager.getLogger();
        TICK_FUNCTION_TAG = new ResourceLocation("tick");
        LOAD_FUNCTION_TAG = new ResourceLocation("load");
        PATH_PREFIX_LENGTH = "functions/".length();
        PATH_SUFFIX_LENGTH = ".mcfunction".length();
    }
    
    public static class QueuedCommand {
        private final ServerFunctionManager manager;
        private final CommandSourceStack sender;
        private final CommandFunction.Entry entry;
        
        public QueuedCommand(final ServerFunctionManager sy, final CommandSourceStack cq, final CommandFunction.Entry c) {
            this.manager = sy;
            this.sender = cq;
            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();
        }
    }
}
