// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.command.system;

import java.util.concurrent.CompletionStage;
import java.util.Deque;
import java.util.concurrent.CompletionException;
import java.util.List;
import com.hypixel.hytale.server.core.command.system.exceptions.CommandException;
import com.hypixel.hytale.logger.sentry.SkipSentryException;
import com.hypixel.hytale.common.util.CompletableFutureUtil;
import com.hypixel.hytale.server.core.Message;
import java.util.concurrent.ForkJoinPool;
import java.util.Objects;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.core.entity.entities.Player;
import java.util.concurrent.CompletableFuture;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import javax.annotation.Nullable;
import java.util.logging.Level;
import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException;
import java.util.Iterator;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import com.hypixel.hytale.server.core.command.commands.utility.git.GitCommand;
import com.hypixel.hytale.server.core.command.commands.utility.metacommands.CommandsCommand;
import com.hypixel.hytale.server.core.command.commands.utility.net.NetworkCommand;
import com.hypixel.hytale.server.core.command.commands.utility.sleep.SleepCommand;
import com.hypixel.hytale.server.core.command.commands.utility.lighting.LightingCommand;
import com.hypixel.hytale.server.core.command.commands.player.PlayerCommand;
import com.hypixel.hytale.server.core.command.commands.world.entity.EntityCommand;
import com.hypixel.hytale.server.core.command.commands.world.SpawnBlockCommand;
import com.hypixel.hytale.server.core.command.commands.utility.StashCommand;
import com.hypixel.hytale.server.core.command.commands.utility.sound.SoundCommand;
import com.hypixel.hytale.server.core.command.commands.utility.worldmap.WorldMapCommand;
import com.hypixel.hytale.server.core.command.commands.utility.ValidateCPBCommand;
import com.hypixel.hytale.server.core.command.commands.utility.EventTitleCommand;
import com.hypixel.hytale.server.core.command.commands.utility.NotifyCommand;
import com.hypixel.hytale.server.core.command.commands.utility.help.HelpCommand;
import com.hypixel.hytale.server.core.command.commands.utility.ConvertPrefabsCommand;
import com.hypixel.hytale.server.core.command.commands.utility.BackupCommand;
import com.hypixel.hytale.server.core.command.commands.server.WhoCommand;
import com.hypixel.hytale.server.core.command.commands.server.StopCommand;
import com.hypixel.hytale.server.core.command.commands.server.MaxPlayersCommand;
import com.hypixel.hytale.server.core.command.commands.server.KickCommand;
import com.hypixel.hytale.server.core.command.commands.server.auth.AuthCommand;
import com.hypixel.hytale.server.core.command.commands.player.inventory.ItemStateCommand;
import com.hypixel.hytale.server.core.command.commands.player.inventory.InventoryCommand;
import com.hypixel.hytale.server.core.command.commands.player.inventory.GiveCommand;
import com.hypixel.hytale.server.core.command.commands.player.ToggleBlockPlacementOverrideCommand;
import com.hypixel.hytale.server.core.command.commands.player.ReferCommand;
import com.hypixel.hytale.server.core.command.commands.player.WhoAmICommand;
import com.hypixel.hytale.server.core.command.commands.player.WhereAmICommand;
import com.hypixel.hytale.server.core.command.commands.player.SudoCommand;
import com.hypixel.hytale.server.core.command.commands.player.DamageCommand;
import com.hypixel.hytale.server.core.command.commands.player.KillCommand;
import com.hypixel.hytale.server.core.command.commands.player.HideCommand;
import com.hypixel.hytale.server.core.command.commands.player.GameModeCommand;
import com.hypixel.hytale.server.core.command.commands.debug.TagPatternCommand;
import com.hypixel.hytale.server.core.asset.type.particle.commands.ParticleCommand;
import com.hypixel.hytale.server.core.command.commands.debug.VersionCommand;
import com.hypixel.hytale.server.core.command.commands.debug.ShowBuilderToolsHudCommand;
import com.hypixel.hytale.server.core.command.commands.debug.StopNetworkChunkSendingCommand;
import com.hypixel.hytale.server.core.command.commands.debug.component.repulsion.RepulsionCommand;
import com.hypixel.hytale.server.core.command.commands.debug.HudManagerTestCommand;
import com.hypixel.hytale.server.core.command.commands.debug.MessageTranslationTestCommand;
import com.hypixel.hytale.server.core.command.commands.debug.DebugPlayerPositionCommand;
import com.hypixel.hytale.server.core.command.commands.debug.component.hitboxcollision.HitboxCollisionCommand;
import com.hypixel.hytale.server.core.command.commands.debug.stresstest.StressTestCommand;
import com.hypixel.hytale.server.core.command.commands.debug.server.ServerCommand;
import com.hypixel.hytale.server.core.command.commands.debug.packs.PacksCommand;
import com.hypixel.hytale.server.core.command.commands.debug.AssetsCommand;
import com.hypixel.hytale.server.core.command.commands.debug.PacketStatsCommand;
import com.hypixel.hytale.server.core.command.commands.debug.HitDetectionCommand;
import com.hypixel.hytale.server.core.command.commands.world.worldgen.WorldGenCommand;
import com.hypixel.hytale.server.core.command.commands.debug.PingCommand;
import com.hypixel.hytale.server.core.command.commands.debug.PIDCheckCommand;
import com.hypixel.hytale.server.core.command.commands.debug.LogCommand;
import com.hypixel.hytale.server.core.command.commands.world.chunk.ChunkCommand;
import javax.annotation.Nonnull;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.util.Map;
import com.hypixel.hytale.logger.HytaleLogger;

public class CommandManager implements CommandOwner
{
    private static final HytaleLogger LOGGER;
    private static CommandManager instance;
    private final Map<String, AbstractCommand> commandRegistration;
    private final Map<String, String> aliases;
    
    public static CommandManager get() {
        return CommandManager.instance;
    }
    
    public CommandManager() {
        this.commandRegistration = new Object2ObjectOpenHashMap<String, AbstractCommand>();
        this.aliases = new Object2ObjectOpenHashMap<String, String>();
        CommandManager.instance = this;
    }
    
    public void shutdown() {
        this.aliases.clear();
    }
    
    @Nonnull
    public Map<String, AbstractCommand> getCommandRegistration() {
        return this.commandRegistration;
    }
    
    public void registerCommands() {
        this.registerSystemCommand(new ChunkCommand());
        this.registerSystemCommand(new LogCommand());
        this.registerSystemCommand(new PIDCheckCommand());
        this.registerSystemCommand(new PingCommand());
        this.registerSystemCommand(new WorldGenCommand());
        this.registerSystemCommand(new HitDetectionCommand());
        this.registerSystemCommand(new PacketStatsCommand());
        this.registerSystemCommand(new AssetsCommand());
        this.registerSystemCommand(new PacksCommand());
        this.registerSystemCommand(new ServerCommand());
        this.registerSystemCommand(new StressTestCommand());
        this.registerSystemCommand(new HitboxCollisionCommand());
        this.registerSystemCommand(new DebugPlayerPositionCommand());
        this.registerSystemCommand(new MessageTranslationTestCommand());
        this.registerSystemCommand(new HudManagerTestCommand());
        this.registerSystemCommand(new RepulsionCommand());
        this.registerSystemCommand(new StopNetworkChunkSendingCommand());
        this.registerSystemCommand(new ShowBuilderToolsHudCommand());
        this.registerSystemCommand(new VersionCommand());
        this.registerSystemCommand(new ParticleCommand());
        this.registerSystemCommand(new TagPatternCommand());
        this.registerSystemCommand(new GameModeCommand());
        this.registerSystemCommand(new HideCommand());
        this.registerSystemCommand(new KillCommand());
        this.registerSystemCommand(new DamageCommand());
        this.registerSystemCommand(new SudoCommand());
        this.registerSystemCommand(new WhereAmICommand());
        this.registerSystemCommand(new WhoAmICommand());
        this.registerSystemCommand(new ReferCommand());
        this.registerSystemCommand(new ToggleBlockPlacementOverrideCommand());
        this.registerSystemCommand(new GiveCommand());
        this.registerSystemCommand(new InventoryCommand());
        this.registerSystemCommand(new ItemStateCommand());
        this.registerSystemCommand(new AuthCommand());
        this.registerSystemCommand(new KickCommand());
        this.registerSystemCommand(new MaxPlayersCommand());
        this.registerSystemCommand(new StopCommand());
        this.registerSystemCommand(new WhoCommand());
        this.registerSystemCommand(new BackupCommand());
        this.registerSystemCommand(new ConvertPrefabsCommand());
        this.registerSystemCommand(new HelpCommand());
        this.registerSystemCommand(new NotifyCommand());
        this.registerSystemCommand(new EventTitleCommand());
        this.registerSystemCommand(new ValidateCPBCommand());
        this.registerSystemCommand(new WorldMapCommand());
        this.registerSystemCommand(new SoundCommand());
        this.registerSystemCommand(new StashCommand());
        this.registerSystemCommand(new SpawnBlockCommand());
        this.registerSystemCommand(new EntityCommand());
        this.registerSystemCommand(new PlayerCommand());
        this.registerSystemCommand(new LightingCommand());
        this.registerSystemCommand(new SleepCommand());
        this.registerSystemCommand(new NetworkCommand());
        this.registerSystemCommand(new CommandsCommand());
        this.registerSystemCommand(new GitCommand());
    }
    
    public Map<String, Set<String>> createVirtualPermissionGroups() {
        final Map<String, Set<String>> permissionsByGroup = new Object2ObjectOpenHashMap<String, Set<String>>();
        for (final AbstractCommand command : this.commandRegistration.values()) {
            for (final Map.Entry<String, Set<String>> entry : command.getPermissionGroupsRecursive().entrySet()) {
                final Set<String> permissionsForGroup = permissionsByGroup.computeIfAbsent((String)entry.getKey(), k -> new HashSet());
                permissionsForGroup.addAll(entry.getValue());
            }
        }
        return permissionsByGroup;
    }
    
    public void registerSystemCommand(@Nonnull final AbstractCommand command) {
        command.setOwner(this);
        this.register(command);
    }
    
    @Nullable
    public CommandRegistration register(@Nonnull final AbstractCommand command) {
        final String name = command.getName();
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("Registered commands must define a name");
        }
        this.commandRegistration.put(command.getName(), command);
        try {
            command.completeRegistration();
        }
        catch (final Exception e) {
            String errorMessage = e.getMessage();
            if (e instanceof final GeneralCommandException generalException) {
                final String messageText = generalException.getMessageText();
                if (messageText != null) {
                    errorMessage = messageText;
                }
            }
            HytaleLogger.getLogger().at(Level.SEVERE).withCause(e).log("Failed to register command: %s%s", (Object)command.getName(), (errorMessage != null) ? (" - " + errorMessage) : "");
            return null;
        }
        for (final String alias : command.getAliases()) {
            this.aliases.put(alias, name);
        }
        return new CommandRegistration(command, () -> true, () -> {
            final AbstractCommand remove = this.commandRegistration.remove(name);
            if (remove != null) {
                for (final String alias2 : remove.getAliases()) {
                    this.aliases.remove(alias2);
                }
            }
        });
    }
    
    @Nonnull
    public CompletableFuture<Void> handleCommand(@Nonnull final PlayerRef playerRef, @Nonnull final String command) {
        final Ref<EntityStore> ref = playerRef.getReference();
        if (ref == null) {
            return new CompletableFuture<Void>();
        }
        final Store<EntityStore> store = ref.getStore();
        final Player playerComponent = store.getComponent(ref, Player.getComponentType());
        return this.handleCommand(playerComponent, command);
    }
    
    @Nonnull
    public CompletableFuture<Void> handleCommand(@Nonnull final CommandSender commandSender, @Nonnull final String commandString) {
        Objects.requireNonNull(commandSender, "Command sender must not be null!");
        Objects.requireNonNull(commandString, "Command must not be null!");
        final CompletableFuture<Void> future = new CompletableFuture<Void>();
        ForkJoinPool.commonPool().execute(() -> {
            final Thread thread = Thread.currentThread();
            final String oldName = thread.getName();
            thread.setName(oldName + " -- Running: " + commandString);
            try {
                CommandManager.LOGGER.at(Level.FINE).log("%s sent command: %s", commandSender.getDisplayName(), commandString);
                final int endIndex = commandString.indexOf(32);
                final String commandName = ((endIndex < 0) ? commandString : commandString.substring(0, endIndex)).toLowerCase();
                AbstractCommand command = this.commandRegistration.get(commandName);
                if (command == null) {
                    final String key = this.aliases.get(commandName);
                    if (key != null && this.commandRegistration.containsKey(key)) {
                        command = this.commandRegistration.get(key);
                    }
                }
                if (command == null) {
                    commandSender.sendMessage(Message.translation("server.modules.command.notFound").param("cmd", commandString));
                    future.complete(null);
                }
                else {
                    this.runCommand(commandSender, commandString, command, future);
                }
            }
            finally {
                thread.setName(oldName);
            }
            return;
        });
        return future;
    }
    
    private void runCommand(@Nonnull final CommandSender commandSender, @Nonnull final String commandInput, @Nonnull final AbstractCommand abstractCommand, @Nonnull final CompletableFuture<Void> future) {
        try {
            CommandManager.LOGGER.at(Level.INFO).log("%s executed command: %s", commandSender.getDisplayName(), commandInput);
            final ParseResult parseResult = new ParseResult();
            final List<String> tokens = Tokenizer.parseArguments(commandInput, parseResult);
            if (parseResult.failed()) {
                parseResult.sendMessages(commandSender);
                future.complete(null);
                return;
            }
            final ParserContext parserContext = ParserContext.of(tokens, parseResult);
            if (parseResult.failed()) {
                parseResult.sendMessages(commandSender);
                future.complete(null);
                return;
            }
            final CompletableFuture<Void> commandFuture = abstractCommand.acceptCall(commandSender, parserContext, parseResult);
            if (parseResult.failed()) {
                parseResult.sendMessages(commandSender);
                future.complete(null);
                return;
            }
            if (commandFuture != null) {
                commandFuture.whenComplete((aVoid, throwable) -> {
                    if (throwable != null) {
                        if (!CompletableFutureUtil.isCanceled(throwable) && !isInternalException(throwable)) {
                            CommandManager.LOGGER.at(Level.SEVERE).withCause(new SkipSentryException(throwable)).log("Failed to execute command %s for %s", commandInput, commandSender.getDisplayName());
                            commandSender.sendMessage(Message.translation("server.modules.command.error").param("cmd", commandInput).param("msg", throwable.getMessage()));
                        }
                        future.completeExceptionally(throwable);
                    }
                    else {
                        future.complete(aVoid);
                    }
                });
            }
            else {
                future.complete(null);
            }
        }
        catch (final Throwable t) {
            if (t instanceof final CommandException commandException) {
                commandException.sendTranslatedMessage(commandSender);
            }
            else {
                CommandManager.LOGGER.at(Level.SEVERE).withCause(t).log("Failed to execute command %s for %s", commandInput, commandSender.getDisplayName());
                final Message errorMsg = (t.getMessage() == null) ? Message.translation("server.modules.command.noProvidedExceptionMessage") : Message.raw(t.getMessage());
                commandSender.sendMessage(Message.translation("server.modules.command.error").param("cmd", commandInput).param("msg", errorMsg));
            }
            future.completeExceptionally(t);
        }
    }
    
    private static boolean isInternalException(@Nonnull final Throwable throwable) {
        return throwable instanceof CommandException || (throwable instanceof CompletionException && throwable.getCause() != null && throwable.getCause() != throwable && isInternalException(throwable.getCause()));
    }
    
    @Nonnull
    public CompletableFuture<Void> handleCommands(@Nonnull final CommandSender sender, @Nonnull final Deque<String> commands) {
        return this.handleCommands0(sender, commands);
    }
    
    @Nonnull
    private CompletableFuture<Void> handleCommands0(@Nonnull final CommandSender sender, @Nonnull final Deque<String> commands) {
        if (commands.isEmpty()) {
            return CompletableFuture.completedFuture((Void)null);
        }
        return this.handleCommand(sender, commands.poll()).thenCompose(aVoid -> this.handleCommands0(sender, commands));
    }
    
    @Nonnull
    @Override
    public String getName() {
        return "HytaleServer";
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
    }
}
