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 co.aikar.locales.MessageKeyProvider;
027import com.google.common.collect.SetMultimap;
028import org.jetbrains.annotations.NotNull;
029
030import java.util.*;
031import java.util.regex.Pattern;
032import java.util.stream.Stream;
033
034@SuppressWarnings("WeakerAccess")
035public class CommandHelp {
036    private final CommandManager manager;
037    private final CommandIssuer issuer;
038    private final List<HelpEntry> helpEntries = new ArrayList<>();
039    private int page;
040    private List<String> search;
041
042    public CommandHelp(CommandManager manager, RootCommand rootCommand, CommandIssuer issuer) {
043        this.manager = manager;
044        this.issuer = issuer;
045
046        SetMultimap<String, RegisteredCommand> subCommands = rootCommand.getSubCommands();
047        Set<RegisteredCommand> seen = new HashSet<>();
048        subCommands.entries().forEach(e -> {
049            String key = e.getKey();
050            if (key.equals("__default") || key.equals("__unknown")){
051                return;
052            }
053
054            RegisteredCommand regCommand = e.getValue();
055            if (regCommand.hasPermission(issuer) && !seen.contains(regCommand)) {
056                this.helpEntries.add(new HelpEntry(regCommand));
057                seen.add(regCommand);
058            }
059        });
060    }
061
062    @UnstableAPI // Not sure on this one yet even when API becomes unstable
063    protected void updateSearchScore(HelpEntry help) {
064        if (this.search == null || this.search.isEmpty()) {
065            help.setSearchScore(1);
066            return;
067        }
068        final RegisteredCommand<?> cmd = help.getRegisteredCommand();
069
070        int searchScore = 0;
071        for (String word : this.search) {
072            Pattern pattern = Pattern.compile(".*" + Pattern.quote(word) + ".*", Pattern.CASE_INSENSITIVE);
073            for (String subCmd : cmd.registeredSubcommands) {
074                Pattern subCmdPattern = Pattern.compile(".*" + Pattern.quote(subCmd) + ".*", Pattern.CASE_INSENSITIVE);
075                if (pattern.matcher(subCmd).matches()) {
076                    searchScore += 3;
077                } else if (subCmdPattern.matcher(word).matches()) {
078                    searchScore++;
079                }
080            }
081
082
083            if (pattern.matcher(help.getDescription()).matches()) {
084                searchScore += 2;
085            }
086            if (pattern.matcher(help.getParameterSyntax()).matches()) {
087                searchScore++;
088            }
089            if (help.getSearchTags() != null && pattern.matcher(help.getSearchTags()).matches()) {
090                searchScore += 2;
091            }
092        }
093        help.setSearchScore(searchScore);
094    }
095
096    public CommandManager getManager() {
097        return manager;
098    }
099
100    public void showHelp() {
101        showHelp(issuer, MessageKeys.HELP_FORMAT);
102    }
103
104    public void showHelp(CommandIssuer issuer) {
105        showHelp(issuer, MessageKeys.HELP_FORMAT);
106    }
107
108    public void showHelp(CommandIssuer issuer, MessageKeyProvider format) {
109        Iterator<HelpEntry> results = getHelpEntries().stream()
110                .filter(HelpEntry::shouldShow)
111                .sorted(Comparator.comparingInt(helpEntry -> helpEntry.getSearchScore() * -1)).iterator();
112        if (!results.hasNext()) {
113            issuer.sendMessage(MessageType.ERROR, MessageKeys.NO_COMMAND_MATCHED_SEARCH, "{search}", ACFUtil.join(this.search, " "));
114            results = getHelpEntries().iterator();
115        }
116
117        while (results.hasNext()) {
118            HelpEntry e = results.next();
119            String formatted = this.manager.formatMessage(issuer, MessageType.HELP, format, getFormatReplacements(e));
120            for (String msg : ACFPatterns.NEWLINE.split(formatted)) {
121                issuer.sendMessageInternal(ACFUtil.rtrim(msg));
122            }
123        }
124    }
125
126    /**
127     * Override this to control replacements
128     * @param e
129     * @return
130     */
131    @NotNull
132    public String[] getFormatReplacements(HelpEntry e) {
133        //{command} {parameters} {seperator} {description}
134        return new String[] {
135                "{command}", e.getCommand(),
136                "{parameters}", e.getParameterSyntax(),
137                "{seperator}", e.getDescription().isEmpty() ? "" : "-",
138                "{description}", e.getDescription()
139        };
140    }
141
142    public List<HelpEntry> getHelpEntries() {
143        return helpEntries;
144    }
145
146    public void setPage(int page) {
147        this.page = page;
148    }
149
150    public void setSearch(List<String> search) {
151        this.search = search;
152        getHelpEntries().forEach(this::updateSearchScore);
153    }
154}