001/*
002 * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License
003 *
004 *  Permission is hereby granted, free of charge, to any person obtaining
005 *  a copy of this software and associated documentation files (the
006 *  "Software"), to deal in the Software without restriction, including
007 *  without limitation the rights to use, copy, modify, merge, publish,
008 *  distribute, sublicense, and/or sell copies of the Software, and to
009 *  permit persons to whom the Software is furnished to do so, subject to
010 *  the following conditions:
011 *
012 *  The above copyright notice and this permission notice shall be
013 *  included in all copies or substantial portions of the Software.
014 *
015 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
016 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
017 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
018 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
019 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
020 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
021 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
022 */
023
024package co.aikar.commands;
025
026import com.google.common.collect.ImmutableList;
027import com.google.common.collect.Lists;
028import org.jetbrains.annotations.NotNull;
029
030import java.util.Collection;
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
034import java.util.stream.Collectors;
035import java.util.stream.IntStream;
036
037
038@SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
039public class CommandCompletions <C extends CommandCompletionContext> {
040    private final CommandManager manager;
041    private Map<String, CommandCompletionHandler> completionMap = new HashMap<>();
042
043    public CommandCompletions(CommandManager manager) {
044        this.manager = manager;
045        registerAsyncCompletion("nothing", c -> ImmutableList.of());
046        registerAsyncCompletion("range", (c) -> {
047            String config = c.getConfig();
048            if (config == null) {
049                return ImmutableList.of();
050            }
051            final String[] ranges = ACFPatterns.DASH.split(config);
052            int start;
053            int end;
054            if (ranges.length != 2) {
055                start = 0;
056                end = ACFUtil.parseInt(ranges[0], 0);
057            } else {
058                start = ACFUtil.parseInt(ranges[0], 0);
059                end = ACFUtil.parseInt(ranges[1], 0);
060            }
061            return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList());
062        });
063        registerAsyncCompletion("timeunits", (c) -> ImmutableList.of("minutes", "hours", "days", "weeks", "months", "years"));
064    }
065
066    public CommandCompletionHandler registerCompletion(String id, CommandCompletionHandler<C> handler) {
067        return this.completionMap.put("@" + id.toLowerCase(), handler);
068    }
069
070    public CommandCompletionHandler registerAsyncCompletion(String id, AsyncCommandCompletionHandler<C> handler) {
071        return this.completionMap.put("@" + id.toLowerCase(), handler);
072    }
073
074    @NotNull
075    List<String> of(RegisteredCommand cmd, CommandIssuer sender, String[] args, boolean isAsync) {
076        String[] completions = ACFPatterns.SPACE.split(cmd.complete);
077        final int argIndex = args.length - 1;
078
079        String input = args[argIndex];
080
081        String completion = argIndex < completions.length ? completions[argIndex] : null;
082        if (completion == null && completions.length > 0) {
083            completion = completions[completions.length - 1];
084        }
085        if (completion == null) {
086            return ImmutableList.of(input);
087        }
088
089        return getCompletionValues(cmd, sender, completion, args, isAsync);
090    }
091
092    List<String> getCompletionValues(RegisteredCommand command, CommandIssuer sender, String completion, String[] args, boolean isAsync) {
093        completion = manager.getCommandReplacements().replace(completion);
094
095        List<String> allCompletions = Lists.newArrayList();
096        String input = args.length > 0 ? args[args.length - 1] : "";
097
098        for (String value : ACFPatterns.PIPE.split(completion)) {
099            String[] complete = ACFPatterns.COLONEQUALS.split(value, 2);
100            CommandCompletionHandler handler = this.completionMap.get(complete[0].toLowerCase());
101            if (handler != null) {
102                if (isAsync && !(handler instanceof AsyncCommandCompletionHandler)) {
103                    ACFUtil.sneaky(new SyncCompletionRequired());
104                    return null;
105                }
106                String config = complete.length == 1 ? null : complete[1];
107                CommandCompletionContext context = manager.createCompletionContext(command, sender, input, config, args);
108
109                try {
110                    //noinspection unchecked
111                    Collection<String> completions = handler.getCompletions(context);
112                    if (completions != null) {
113                        allCompletions.addAll(completions);
114                        continue;
115                    }
116                    //noinspection ConstantIfStatement,ConstantConditions
117                    if (false) { // Hack to fool compiler. since its sneakily thrown.
118                        throw new CommandCompletionTextLookupException();
119                    }
120                } catch (CommandCompletionTextLookupException ignored) {
121                    // This should only happen if some other feedback error occured.
122                } catch (Exception e) {
123                    command.handleException(sender, Lists.newArrayList(args), e);
124                }
125                // Something went wrong in lookup, fall back to input
126                return ImmutableList.of(input);
127            } else {
128                // Plaintext value
129                allCompletions.add(value);
130            }
131        }
132        return allCompletions;
133    }
134
135    public interface CommandCompletionHandler <C extends CommandCompletionContext> {
136        Collection<String> getCompletions(C context) throws InvalidCommandArgument;
137    }
138    public interface AsyncCommandCompletionHandler <C extends CommandCompletionContext> extends  CommandCompletionHandler <C> {}
139    public static class SyncCompletionRequired extends Exception {}
140
141}