/*
 * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License
 *
 *  Permission is hereby granted, free of charge, to any person obtaining
 *  a copy of this software and associated documentation files (the
 *  "Software"), to deal in the Software without restriction, including
 *  without limitation the rights to use, copy, modify, merge, publish,
 *  distribute, sublicense, and/or sell copies of the Software, and to
 *  permit persons to whom the Software is furnished to do so, subject to
 *  the following conditions:
 *
 *  The above copyright notice and this permission notice shall be
 *  included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package co.aikar.commands;

import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil;
import com.google.common.collect.SetMultimap;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public interface RootCommand {
    void addChild(BaseCommand command);

    CommandManager getManager();

    SetMultimap<String, RegisteredCommand> getSubCommands();

    List<BaseCommand> getChildren();

    String getCommandName();

    default void addChildShared(List<BaseCommand> children, SetMultimap<String, RegisteredCommand> subCommands, BaseCommand command) {
        command.subCommands.entries().forEach(e -> {
            String key = e.getKey();
            RegisteredCommand registeredCommand = e.getValue();
            if (key.equals(BaseCommand.DEFAULT) || key.equals(BaseCommand.CATCHUNKNOWN)) {
                return;
            }
            Set<RegisteredCommand> registered = subCommands.get(key);
            if (!registered.isEmpty()) {
                BaseCommand prevBase = registered.iterator().next().scope;
                if (prevBase != registeredCommand.scope) {
                    this.getManager().log(LogLevel.ERROR, "ACF Error: " + command.getName() + " registered subcommand " + key + " for root command " + getCommandName() + " - but it is already defined in " + prevBase.getName());
                    this.getManager().log(LogLevel.ERROR, "2 subcommands of the same prefix may not be spread over 2 different classes. Ignoring this.");
                    return;
                }
            }
            subCommands.put(key, registeredCommand);
        });

        children.add(command);
    }

    /**
     * @return If this root command can be summarized to a single required permission node to use it, returns that value. If any RegisteredCommand is permission-less, or has multiple required permission nodes, null is returned.
     */
    default String getUniquePermission() {
        Set<String> permissions = new HashSet<>();
        for (BaseCommand child : getChildren()) {
            for (RegisteredCommand<?> value : child.subCommands.values()) {
                Set<String> requiredPermissions = value.getRequiredPermissions();
                if (requiredPermissions.isEmpty()) {
                    return null;
                } else {
                    permissions.addAll(requiredPermissions);
                }
            }
        }
        return permissions.size() == 1 ? permissions.iterator().next() : null;
    }

    default boolean hasAnyPermission(CommandIssuer issuer) {
        List<BaseCommand> children = getChildren();
        if (children.isEmpty()) {
            return true;
        }

        for (BaseCommand child : children) {
            if (!child.hasPermission(issuer)) {
                continue;
            }
            for (RegisteredCommand value : child.getRegisteredCommands()) {
                if (value.hasPermission(issuer)) {
                    return true;
                }
            }
        }
        return false;
    }

    default BaseCommand execute(CommandIssuer sender, String commandLabel, String[] args) {
        BaseCommand command = getBaseCommand(args);

        command.execute(sender, commandLabel, args);
        return command;
    }

    default BaseCommand getBaseCommand(String[] args) {
        BaseCommand command = getDefCommand();
        for (int i = args.length; i >= 0; i--) {
            String checkSub = ApacheCommonsLangUtil.join(args, " ", 0, i).toLowerCase();
            Set<RegisteredCommand> registeredCommands = getSubCommands().get(checkSub);
            if (!registeredCommands.isEmpty()) {
                command = registeredCommands.iterator().next().scope;
                break;
            }
        }
        return command;
    }

    default List<String> getTabCompletions(CommandIssuer sender, String alias, String[] args) {
        return getTabCompletions(sender, alias, args, false);
    }

    default List<String> getTabCompletions(CommandIssuer sender, String alias, String[] args, boolean commandsOnly) {
        return getTabCompletions(sender, alias, args, commandsOnly, false);
    }

    default List<String> getTabCompletions(CommandIssuer sender, String alias, String[] args, boolean commandsOnly, boolean isAsync) {
        Set<String> completions = new HashSet<>();
        getChildren().forEach(child -> {
            if (!commandsOnly) {
                completions.addAll(child.tabComplete(sender, alias, args, isAsync));
            }
            completions.addAll(child.getCommandsForCompletion(sender, args));
        });
        return new ArrayList<>(completions);
    }


    default RegisteredCommand getDefaultRegisteredCommand() {
        BaseCommand defCommand = this.getDefCommand();
        if (defCommand != null) {
            return defCommand.getDefaultRegisteredCommand();
        }
        return null;
    }

    default BaseCommand getDefCommand() {
        return null;
    }


    default String getDescription() {
        final RegisteredCommand cmd = this.getDefaultRegisteredCommand();
        if (cmd != null) {
            return cmd.getHelpText();
        }
        BaseCommand defCommand = getDefCommand();
        if (defCommand != null && defCommand.description != null) {
            return defCommand.description;
        }
        return "";
    }


    default String getUsage() {
        final RegisteredCommand cmd = this.getDefaultRegisteredCommand();
        if (cmd != null) {
            return cmd.syntaxText != null ? cmd.syntaxText : "";
        }
        return "";
    }
}

@@ -1398,16 +1398,128 @@
 l.Set;%0A%0A
+import static co.aikar.commands.BaseCommand.CATCHUNKNOWN;%0Aimport static co.aikar.commands.BaseCommand.DEFAULT;%0A%0A
 public i
@@ -2062,28 +2062,16 @@
 .equals(
-BaseCommand.
 DEFAULT)
@@ -4625,55 +4625,8 @@
 ) %7B%0A
-        BaseCommand command = getDefCommand();%0A
     
@@ -4912,25 +4912,281 @@
         
-command =
+return registeredCommands.iterator().next().scope;%0A            %7D%0A        %7D%0A        if (args.length == 0) %7B%0A            Set%3CRegisteredCommand%3E registeredCommands = getSubCommands().get(DEFAULT);%0A            if (!registeredCommands.isEmpty()) %7B%0A                return
  registe
@@ -5238,32 +5238,215 @@
     
+%7D%0A
     
-break;%0A            %7D
+    %7D%0A        Set%3CRegisteredCommand%3E registeredCommands = getSubCommands().get(CATCHUNKNOWN);%0A        if (!registeredCommands.isEmpty()) %7B%0A            return registeredCommands.iterator().next().scope;
 %0A   
@@ -5459,39 +5459,47 @@
 %0A        return 
-c
+getDefC
 ommand
+()
 ;%0A    %7D%0A%0A    def

Improve command handling from unknown commands (#195)