package co.aikar.commands;

import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiPredicate;
import java.util.function.Predicate;

/**
 * Handles registering of commands into brigadier
 *
 * @param <S>
 * @author MiniDigger
 * @deprecated Unstable API
 */
@Deprecated
@UnstableAPI
public class ACFBrigadierManager<S> {

    protected final CommandManager<?, ?, ?, ?, ?, ?> manager;

    private final Map<Class<?>, ArgumentType<?>> arguments = new HashMap<>();

    /**
     * Constructs a new brigadier manager, utilizing the currently active command manager
     *
     * @param manager
     */
    ACFBrigadierManager(CommandManager<?, ?, ?, ?, ?, ?> manager) {
        manager.verifyUnstableAPI("brigadier");

        this.manager = manager;

        // TODO support stuff like min max via brigadier?
        registerArgument(String.class, StringArgumentType.word());
        registerArgument(float.class, FloatArgumentType.floatArg());
        registerArgument(Float.class, FloatArgumentType.floatArg());
        registerArgument(double.class, DoubleArgumentType.doubleArg());
        registerArgument(Double.class, DoubleArgumentType.doubleArg());
        registerArgument(boolean.class, BoolArgumentType.bool());
        registerArgument(Boolean.class, BoolArgumentType.bool());
        registerArgument(int.class, IntegerArgumentType.integer());
        registerArgument(Integer.class, IntegerArgumentType.integer());
        // We use integer for long due to Bungee bug, plus should really be considered same on client
        registerArgument(long.class, IntegerArgumentType.integer());
        registerArgument(Long.class, IntegerArgumentType.integer());
    }

    <T> void registerArgument(Class<T> clazz, ArgumentType<?> type) {
        arguments.put(clazz, type);
    }

    ArgumentType<Object> getArgumentTypeByClazz(CommandParameter param) {
        if (param.consumesRest) {
            //noinspection unchecked
            return (ArgumentType<Object>) (ArgumentType<?>) StringArgumentType.greedyString();
        }
        //noinspection unchecked
        return (ArgumentType<Object>) arguments.getOrDefault(param.getType(), StringArgumentType.string());
    }

    /**
     * Registers the given RootCommand into the given brigadir command node, utilizing the provided suggestion provider, executor and permission predicate.<br>
     * <p>
     * It recreates the root command node!
     */
    LiteralCommandNode<S> register(RootCommand rootCommand,
                                   LiteralCommandNode<S> root,
                                   SuggestionProvider<S> suggestionProvider,
                                   Command<S> executor,
                                   BiPredicate<RootCommand, S> permCheckerRoot,
                                   BiPredicate<RegisteredCommand, S> permCheckerSub) {
        // recreate root to get rid of bukkits default arg
        LiteralArgumentBuilder<S> rootBuilder = LiteralArgumentBuilder.<S>literal(root.getLiteral())
                .requires(sender -> permCheckerRoot.test(rootCommand, sender));

        RegisteredCommand defaultCommand = rootCommand.getDefaultRegisteredCommand();
        if (defaultCommand != null) {
            if (defaultCommand.requiredResolvers == 0) {
                rootBuilder.executes(executor);
            }
        }

        root = rootBuilder.build();
        boolean isForwardingCommand = rootCommand.getDefCommand() instanceof ForwardingCommand;

        if (defaultCommand != null) {
            registerParameters(defaultCommand, root, suggestionProvider, executor, permCheckerSub);
        }

        for (Map.Entry<String, RegisteredCommand> subCommand : rootCommand.getSubCommands().entries()) {
            if ((BaseCommand.isSpecialSubcommand(subCommand.getKey()) && !isForwardingCommand) || (!subCommand.getKey().equals("help") && subCommand.getValue().prefSubCommand.equals("help"))) {
                // don't register stuff like __catchunknown and don't help command aliases
                continue;
            }

            // handle sub sub commands
            String commandName = subCommand.getKey();
            CommandNode<S> currentParent = root;
            CommandNode<S> subCommandNode;
            Predicate<S> subPermChecker = sender -> permCheckerSub.test(subCommand.getValue(), sender);
            if (!isForwardingCommand) {
                if (commandName.contains(" ")) {
                    String[] split = ACFPatterns.SPACE.split(commandName);
                    for (int i = 0; i < split.length - 1; i++) {
                        if (currentParent.getChild(split[i]) == null) {
                            LiteralCommandNode<S> sub = LiteralArgumentBuilder.<S>literal(split[i])
                                    .requires(subPermChecker).build();
                            currentParent.addChild(sub);
                            currentParent = sub;
                        } else {
                            currentParent = currentParent.getChild(split[i]);
                        }
                    }
                    commandName = split[split.length - 1];
                }

                subCommandNode = currentParent.getChild(commandName);
                if (subCommandNode == null) {
                    LiteralArgumentBuilder<S> argumentBuilder = LiteralArgumentBuilder.<S>literal(commandName)
                            .requires(subPermChecker);

                    // if we have no required params, this command is actually executable
                    if (subCommand.getValue().requiredResolvers == 0) {
                        argumentBuilder.executes(executor);
                    }
                    subCommandNode = argumentBuilder.build();
                }
            } else {
                subCommandNode = root;
            }

            registerParameters(subCommand.getValue(), subCommandNode, suggestionProvider, executor, permCheckerSub);

            if (!isForwardingCommand) {
                currentParent.addChild(subCommandNode);
            }
        }

        return root;
    }

    void registerParameters(RegisteredCommand command,
                            CommandNode<S> node,
                            SuggestionProvider<S> suggestionProvider,
                            Command<S> executor,
                            BiPredicate<RegisteredCommand, S> permChecker) {
        for (int i = 0; i < command.parameters.length; i++) {
            CommandParameter param = command.parameters[i];
            CommandParameter nextParam = param.getNextParam();
            if (param.isCommandIssuer() || (param.canExecuteWithoutInput() && nextParam != null && !nextParam.canExecuteWithoutInput())) {
                continue;
            }
            RequiredArgumentBuilder<S, Object> builder = RequiredArgumentBuilder
                    .<S, Object>argument(param.getName(), getArgumentTypeByClazz(param))
                    .suggests(suggestionProvider)
                    .requires(sender -> permChecker.test(command, sender));

            if (nextParam == null || nextParam.canExecuteWithoutInput()) {
                builder.executes(executor);
            }

            CommandNode<S> subSubCommand = builder.build();
            node.addChild(subSubCommand);
            node = subSubCommand;
        }
    }

}
