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

package org.jline.builtins;

import org.jline.utils.AttributedString;
import org.jline.reader.impl.completer.NullCompleter;
import org.jline.reader.impl.completer.StringsCompleter;
import java.util.Collections;
import java.util.ArrayList;
import java.util.function.Function;
import java.util.Objects;
import java.util.HashMap;
import java.util.Arrays;
import org.jline.utils.OSUtils;
import org.jline.utils.AttributedStringBuilder;
import org.jline.terminal.Terminal;
import java.nio.file.Paths;
import org.jline.utils.StyleResolver;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.io.File;
import java.nio.file.Path;
import java.util.function.Supplier;
import java.util.Set;
import java.util.Iterator;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.UUID;
import java.util.Map;
import org.jline.reader.Candidate;
import org.jline.reader.ParsedLine;
import org.jline.reader.LineReader;
import org.jline.reader.Completer;
import java.util.List;

public class Completers
{
    public static class CompletionData
    {
        public final List<String> options;
        public final String description;
        public final String argument;
        public final String condition;
        
        public CompletionData(final List<String> options, final String description, final String argument, final String condition) {
            this.options = options;
            this.description = description;
            this.argument = argument;
            this.condition = condition;
        }
    }
    
    public static class Completer implements org.jline.reader.Completer
    {
        private final CompletionEnvironment environment;
        
        public Completer(final CompletionEnvironment environment) {
            this.environment = environment;
        }
        
        @Override
        public void complete(final LineReader reader, final ParsedLine line, final List<Candidate> candidates) {
            if (line.wordIndex() == 0) {
                this.completeCommand(candidates);
            }
            else {
                this.tryCompleteArguments(reader, line, candidates);
            }
        }
        
        protected void tryCompleteArguments(final LineReader reader, final ParsedLine line, final List<Candidate> candidates) {
            final String command = line.words().get(0);
            final String resolved = this.environment.resolveCommand(command);
            final Map<String, List<CompletionData>> comp = this.environment.getCompletions();
            if (comp != null) {
                final List<CompletionData> cmd = comp.get(resolved);
                if (cmd != null) {
                    this.completeCommandArguments(reader, line, candidates, cmd);
                }
            }
        }
        
        protected void completeCommandArguments(final LineReader reader, final ParsedLine line, final List<Candidate> candidates, final List<CompletionData> completions) {
            for (final CompletionData completion : completions) {
                final boolean isOption = line.word().startsWith("-");
                final String prevOption = (line.wordIndex() >= 2 && line.words().get(line.wordIndex() - 1).startsWith("-")) ? line.words().get(line.wordIndex() - 1) : null;
                final String key = UUID.randomUUID().toString();
                boolean conditionValue = true;
                if (completion.condition != null) {
                    Object res = Boolean.FALSE;
                    try {
                        res = this.environment.evaluate(reader, line, completion.condition);
                    }
                    catch (final Throwable t) {}
                    conditionValue = this.isTrue(res);
                }
                if (conditionValue && isOption && completion.options != null) {
                    for (final String opt : completion.options) {
                        candidates.add(new Candidate(opt, opt, "options", completion.description, null, key, true));
                    }
                }
                else if (!isOption && prevOption != null && completion.argument != null && completion.options != null && completion.options.contains(prevOption)) {
                    Object res = null;
                    try {
                        res = this.environment.evaluate(reader, line, completion.argument);
                    }
                    catch (final Throwable t2) {}
                    if (res instanceof Candidate) {
                        candidates.add((Candidate)res);
                    }
                    else if (res instanceof String) {
                        candidates.add(new Candidate((String)res, (String)res, null, null, null, null, true));
                    }
                    else if (res instanceof Collection) {
                        for (final Object s : (Collection)res) {
                            if (s instanceof Candidate) {
                                candidates.add((Candidate)s);
                            }
                            else {
                                if (!(s instanceof String)) {
                                    continue;
                                }
                                candidates.add(new Candidate((String)s, (String)s, null, null, null, null, true));
                            }
                        }
                    }
                    else {
                        if (res == null || !res.getClass().isArray()) {
                            continue;
                        }
                        for (int i = 0, l = Array.getLength(res); i < l; ++i) {
                            final Object s2 = Array.get(res, i);
                            if (s2 instanceof Candidate) {
                                candidates.add((Candidate)s2);
                            }
                            else if (s2 instanceof String) {
                                candidates.add(new Candidate((String)s2, (String)s2, null, null, null, null, true));
                            }
                        }
                    }
                }
                else {
                    if (isOption || completion.argument == null) {
                        continue;
                    }
                    Object res = null;
                    try {
                        res = this.environment.evaluate(reader, line, completion.argument);
                    }
                    catch (final Throwable t3) {}
                    if (res instanceof Candidate) {
                        candidates.add((Candidate)res);
                    }
                    else if (res instanceof String) {
                        candidates.add(new Candidate((String)res, (String)res, null, completion.description, null, null, true));
                    }
                    else {
                        if (!(res instanceof Collection)) {
                            continue;
                        }
                        for (final Object s : (Collection)res) {
                            if (s instanceof Candidate) {
                                candidates.add((Candidate)s);
                            }
                            else {
                                if (!(s instanceof String)) {
                                    continue;
                                }
                                candidates.add(new Candidate((String)s, (String)s, null, completion.description, null, null, true));
                            }
                        }
                    }
                }
            }
        }
        
        protected void completeCommand(final List<Candidate> candidates) {
            final Set<String> commands = this.environment.getCommands();
            for (final String command : commands) {
                final String name = this.environment.commandName(command);
                final boolean resolved = command.equals(this.environment.resolveCommand(name));
                if (!name.startsWith("_")) {
                    String desc = null;
                    final Map<String, List<CompletionData>> comp = this.environment.getCompletions();
                    if (comp != null) {
                        final List<CompletionData> completions = comp.get(command);
                        if (completions != null) {
                            for (final CompletionData completion : completions) {
                                if (completion.description != null && completion.options == null && completion.argument == null && completion.condition == null) {
                                    desc = completion.description;
                                }
                            }
                        }
                    }
                    final String key = UUID.randomUUID().toString();
                    if (desc != null) {
                        candidates.add(new Candidate(command, command, null, desc, null, key, true));
                        if (!resolved) {
                            continue;
                        }
                        candidates.add(new Candidate(name, name, null, desc, null, key, true));
                    }
                    else {
                        candidates.add(new Candidate(command, command, null, null, null, key, true));
                        if (!resolved) {
                            continue;
                        }
                        candidates.add(new Candidate(name, name, null, null, null, key, true));
                    }
                }
            }
        }
        
        private boolean isTrue(final Object result) {
            if (result == null) {
                return false;
            }
            if (result instanceof Boolean) {
                return (boolean)result;
            }
            return (!(result instanceof Number) || 0 != ((Number)result).intValue()) && !"".equals(result) && !"0".equals(result);
        }
    }
    
    public static class DirectoriesCompleter extends FileNameCompleter
    {
        private final Supplier<Path> currentDir;
        
        public DirectoriesCompleter(final File currentDir) {
            this(currentDir.toPath());
        }
        
        public DirectoriesCompleter(final Path currentDir) {
            this.currentDir = (() -> currentDir);
        }
        
        public DirectoriesCompleter(final Supplier<Path> currentDir) {
            this.currentDir = currentDir;
        }
        
        @Override
        protected Path getUserDir() {
            return this.currentDir.get();
        }
        
        @Override
        protected boolean accept(final Path path) {
            return Files.isDirectory(path, new LinkOption[0]) && super.accept(path);
        }
    }
    
    public static class FilesCompleter extends FileNameCompleter
    {
        private final Supplier<Path> currentDir;
        private final String namePattern;
        
        public FilesCompleter(final File currentDir) {
            this(currentDir.toPath(), null);
        }
        
        public FilesCompleter(final File currentDir, final String namePattern) {
            this(currentDir.toPath(), namePattern);
        }
        
        public FilesCompleter(final Path currentDir) {
            this(currentDir, null);
        }
        
        public FilesCompleter(final Path currentDir, final String namePattern) {
            this.currentDir = (() -> currentDir);
            this.namePattern = this.compilePattern(namePattern);
        }
        
        public FilesCompleter(final Supplier<Path> currentDir) {
            this(currentDir, null);
        }
        
        public FilesCompleter(final Supplier<Path> currentDir, final String namePattern) {
            this.currentDir = currentDir;
            this.namePattern = this.compilePattern(namePattern);
        }
        
        private String compilePattern(final String pattern) {
            if (pattern == null) {
                return null;
            }
            final StringBuilder sb = new StringBuilder();
            for (int i = 0; i < pattern.length(); ++i) {
                char ch = pattern.charAt(i);
                if (ch == '\\') {
                    ch = pattern.charAt(++i);
                    sb.append(ch);
                }
                else if (ch == '.') {
                    sb.append('\\').append('.');
                }
                else if (ch == '*') {
                    sb.append('.').append('*');
                }
                else {
                    sb.append(ch);
                }
            }
            return sb.toString();
        }
        
        @Override
        protected Path getUserDir() {
            return this.currentDir.get();
        }
        
        @Override
        protected boolean accept(final Path path) {
            if (this.namePattern == null || Files.isDirectory(path, new LinkOption[0])) {
                return super.accept(path);
            }
            return path.getFileName().toString().matches(this.namePattern) && super.accept(path);
        }
    }
    
    public static class FileNameCompleter implements Completer
    {
        @Override
        public void complete(final LineReader reader, final ParsedLine commandLine, final List<Candidate> candidates) {
            assert commandLine != null;
            assert candidates != null;
            final String buffer = commandLine.word().substring(0, commandLine.wordCursor());
            final String sep = this.getSeparator(reader.isSet(LineReader.Option.USE_FORWARD_SLASH));
            final int lastSep = buffer.lastIndexOf(sep);
            try {
                String curBuf;
                Path current;
                if (lastSep >= 0) {
                    curBuf = buffer.substring(0, lastSep + 1);
                    if (curBuf.startsWith("~")) {
                        if (curBuf.startsWith("~" + sep)) {
                            current = this.getUserHome().resolve(curBuf.substring(2));
                        }
                        else {
                            current = this.getUserHome().getParent().resolve(curBuf.substring(1));
                        }
                    }
                    else {
                        current = this.getUserDir().resolve(curBuf);
                    }
                }
                else {
                    curBuf = "";
                    current = this.getUserDir();
                }
                final StyleResolver resolver = Styles.lsStyle();
                try (final DirectoryStream<Path> directory = Files.newDirectoryStream(current, this::accept)) {
                    directory.forEach(p -> {
                        final String value = curBuf + p.getFileName().toString();
                        if (Files.isDirectory(p, new LinkOption[0])) {
                            new Candidate(value + (reader.isSet(LineReader.Option.AUTO_PARAM_SLASH) ? sep : ""), this.getDisplay(reader.getTerminal(), p, resolver, sep), null, null, reader.isSet(LineReader.Option.AUTO_REMOVE_SLASH) ? sep : null, null, false);
                            final Candidate candidate;
                            candidates.add(candidate);
                        }
                        else {
                            candidates.add(new Candidate(value, this.getDisplay(reader.getTerminal(), p, resolver, sep), null, null, null, null, true));
                        }
                        return;
                    });
                }
                catch (final IOException ex) {}
            }
            catch (final Exception ex2) {}
        }
        
        protected boolean accept(final Path path) {
            try {
                return !Files.isHidden(path);
            }
            catch (final IOException e) {
                return false;
            }
        }
        
        protected Path getUserDir() {
            return Paths.get(System.getProperty("user.dir"), new String[0]);
        }
        
        protected Path getUserHome() {
            return Paths.get(System.getProperty("user.home"), new String[0]);
        }
        
        protected String getSeparator(final boolean useForwardSlash) {
            return useForwardSlash ? "/" : this.getUserDir().getFileSystem().getSeparator();
        }
        
        protected String getDisplay(final Terminal terminal, final Path p, final StyleResolver resolver, final String separator) {
            final AttributedStringBuilder sb = new AttributedStringBuilder();
            final String name = p.getFileName().toString();
            final int idx = name.lastIndexOf(".");
            final String type = (idx != -1) ? (".*" + name.substring(idx)) : null;
            if (Files.isSymbolicLink(p)) {
                sb.styled(resolver.resolve(".ln"), name).append("@");
            }
            else if (Files.isDirectory(p, new LinkOption[0])) {
                sb.styled(resolver.resolve(".di"), name).append(separator);
            }
            else if (Files.isExecutable(p) && !OSUtils.IS_WINDOWS) {
                sb.styled(resolver.resolve(".ex"), name).append("*");
            }
            else if (type != null && resolver.resolve(type).getStyle() != 0L) {
                sb.styled(resolver.resolve(type), name);
            }
            else if (Files.isRegularFile(p, new LinkOption[0])) {
                sb.styled(resolver.resolve(".fi"), name);
            }
            else {
                sb.append(name);
            }
            return sb.toAnsi(terminal);
        }
    }
    
    public static class TreeCompleter implements Completer
    {
        final Map<String, Completer> completers;
        final RegexCompleter completer;
        
        public TreeCompleter(final Node... nodes) {
            this(Arrays.asList(nodes));
        }
        
        public TreeCompleter(final List<Node> nodes) {
            this.completers = new HashMap<String, Completer>();
            final StringBuilder sb = new StringBuilder();
            this.addRoots(sb, nodes);
            final String string = sb.toString();
            final Map<String, Completer> completers = this.completers;
            Objects.requireNonNull(completers);
            this.completer = new RegexCompleter(string, (Function<String, Completer>)completers::get);
        }
        
        public static Node node(final Object... objs) {
            Completer comp = null;
            final List<Candidate> cands = new ArrayList<Candidate>();
            final List<Node> nodes = new ArrayList<Node>();
            for (final Object obj : objs) {
                if (obj instanceof String) {
                    cands.add(new Candidate((String)obj));
                }
                else if (obj instanceof Candidate) {
                    cands.add((Candidate)obj);
                }
                else if (obj instanceof Node) {
                    nodes.add((Node)obj);
                }
                else {
                    if (!(obj instanceof Completer)) {
                        throw new IllegalArgumentException();
                    }
                    comp = (Completer)obj;
                }
            }
            if (comp != null) {
                if (!cands.isEmpty()) {
                    throw new IllegalArgumentException();
                }
                return new Node(comp, nodes);
            }
            else {
                if (!cands.isEmpty()) {
                    return new Node((r, l, c) -> c.addAll(cands), nodes);
                }
                throw new IllegalArgumentException();
            }
        }
        
        void addRoots(final StringBuilder sb, final List<Node> nodes) {
            if (!nodes.isEmpty()) {
                sb.append(" ( ");
                boolean first = true;
                for (final Node n : nodes) {
                    if (first) {
                        first = false;
                    }
                    else {
                        sb.append(" | ");
                    }
                    final String name = "c" + this.completers.size();
                    this.completers.put(name, n.completer);
                    sb.append(name);
                    this.addRoots(sb, n.nodes);
                }
                sb.append(" ) ");
            }
        }
        
        @Override
        public void complete(final LineReader reader, final ParsedLine line, final List<Candidate> candidates) {
            this.completer.complete(reader, line, candidates);
        }
        
        public static class Node
        {
            final Completer completer;
            final List<Node> nodes;
            
            public Node(final Completer completer, final List<Node> nodes) {
                this.completer = completer;
                this.nodes = nodes;
            }
        }
    }
    
    public static class RegexCompleter implements Completer
    {
        private final NfaMatcher<String> matcher;
        private final Function<String, Completer> completers;
        private final ThreadLocal<LineReader> reader;
        
        public RegexCompleter(final String syntax, final Function<String, Completer> completers) {
            this.reader = new ThreadLocal<LineReader>();
            this.matcher = new NfaMatcher<String>(syntax, this::doMatch);
            this.completers = completers;
        }
        
        @Override
        public synchronized void complete(final LineReader reader, final ParsedLine line, final List<Candidate> candidates) {
            final List<String> words = line.words().subList(0, line.wordIndex());
            this.reader.set(reader);
            final Set<String> next = this.matcher.matchPartial(words);
            for (final String n : next) {
                this.completers.apply(n).complete(reader, new ArgumentLine(line.word(), line.wordCursor()), candidates);
            }
            this.reader.set(null);
        }
        
        private boolean doMatch(final String arg, final String name) {
            final List<Candidate> candidates = new ArrayList<Candidate>();
            final LineReader r = this.reader.get();
            final boolean caseInsensitive = r != null && r.isSet(LineReader.Option.CASE_INSENSITIVE);
            this.completers.apply(name).complete(r, new ArgumentLine(arg, arg.length()), candidates);
            return candidates.stream().anyMatch(c -> caseInsensitive ? c.value().equalsIgnoreCase(arg) : c.value().equals(arg));
        }
        
        public static class ArgumentLine implements ParsedLine
        {
            private final String word;
            private final int cursor;
            
            public ArgumentLine(final String word, final int cursor) {
                this.word = word;
                this.cursor = cursor;
            }
            
            @Override
            public String word() {
                return this.word;
            }
            
            @Override
            public int wordCursor() {
                return this.cursor;
            }
            
            @Override
            public int wordIndex() {
                return 0;
            }
            
            @Override
            public List<String> words() {
                return Collections.singletonList(this.word);
            }
            
            @Override
            public String line() {
                return this.word;
            }
            
            @Override
            public int cursor() {
                return this.cursor;
            }
        }
    }
    
    public static class OptDesc
    {
        private String shortOption;
        private String longOption;
        private String description;
        private org.jline.reader.Completer valueCompleter;
        
        protected static List<OptDesc> compile(final Map<String, List<String>> optionValues, final Collection<String> options) {
            final List<OptDesc> out = new ArrayList<OptDesc>();
            for (final Map.Entry<String, List<String>> entry : optionValues.entrySet()) {
                if (entry.getKey().startsWith("--")) {
                    out.add(new OptDesc(null, entry.getKey(), new StringsCompleter(entry.getValue())));
                }
                else {
                    if (!entry.getKey().matches("-[a-zA-Z]")) {
                        continue;
                    }
                    out.add(new OptDesc(entry.getKey(), null, new StringsCompleter(entry.getValue())));
                }
            }
            for (final String o : options) {
                if (o.startsWith("--")) {
                    out.add(new OptDesc(null, o));
                }
                else {
                    if (!o.matches("-[a-zA-Z]")) {
                        continue;
                    }
                    out.add(new OptDesc(o, null));
                }
            }
            return out;
        }
        
        public OptDesc(final String shortOption, final String longOption, final String description, final org.jline.reader.Completer valueCompleter) {
            this.shortOption = shortOption;
            this.longOption = longOption;
            this.description = description;
            this.valueCompleter = valueCompleter;
        }
        
        public OptDesc(final String shortOption, final String longOption, final org.jline.reader.Completer valueCompleter) {
            this(shortOption, longOption, null, valueCompleter);
        }
        
        public OptDesc(final String shortOption, final String longOption, final String description) {
            this(shortOption, longOption, description, null);
        }
        
        public OptDesc(final String shortOption, final String longOption) {
            this(shortOption, longOption, null, null);
        }
        
        protected OptDesc() {
        }
        
        public void setValueCompleter(final org.jline.reader.Completer valueCompleter) {
            this.valueCompleter = valueCompleter;
        }
        
        public String longOption() {
            return this.longOption;
        }
        
        public String shortOption() {
            return this.shortOption;
        }
        
        public String description() {
            return this.description;
        }
        
        protected boolean hasValue() {
            return this.valueCompleter != null && this.valueCompleter != NullCompleter.INSTANCE;
        }
        
        protected org.jline.reader.Completer valueCompleter() {
            return this.valueCompleter;
        }
        
        protected void completeOption(final LineReader reader, final ParsedLine commandLine, final List<Candidate> candidates, final boolean longOpt) {
            if (!longOpt) {
                if (this.shortOption != null) {
                    candidates.add(new Candidate(this.shortOption, this.shortOption, null, this.description, null, null, false));
                }
            }
            else if (this.longOption != null) {
                if (this.hasValue()) {
                    candidates.add(new Candidate(this.longOption + "=", this.longOption, null, this.description, null, null, false));
                }
                else {
                    candidates.add(new Candidate(this.longOption, this.longOption, null, this.description, null, null, true));
                }
            }
        }
        
        protected boolean completeValue(final LineReader reader, final ParsedLine commandLine, final List<Candidate> candidates, final String curBuf, final String partialValue) {
            boolean out = false;
            final List<Candidate> temp = new ArrayList<Candidate>();
            final ParsedLine pl = reader.getParser().parse(partialValue, partialValue.length());
            this.valueCompleter.complete(reader, pl, temp);
            for (final Candidate c : temp) {
                final String v = c.value();
                if (v.startsWith(partialValue)) {
                    out = true;
                    String val = c.value();
                    if (this.valueCompleter instanceof FileNameCompleter) {
                        final FileNameCompleter cc = (FileNameCompleter)this.valueCompleter;
                        final String sep = cc.getSeparator(reader.isSet(LineReader.Option.USE_FORWARD_SLASH));
                        val = cc.getDisplay(reader.getTerminal(), Paths.get(c.value(), new String[0]), Styles.lsStyle(), sep);
                    }
                    candidates.add(new Candidate(curBuf + v, val, null, null, null, null, c.complete()));
                }
            }
            return out;
        }
        
        protected boolean match(final String option) {
            return (this.shortOption != null && this.shortOption.equals(option)) || (this.longOption != null && this.longOption.equals(option));
        }
        
        protected boolean startsWith(final String option) {
            return (this.shortOption != null && this.shortOption.startsWith(option)) || (this.longOption != null && this.longOption.startsWith(option));
        }
    }
    
    public static class OptionCompleter implements Completer
    {
        private Function<String, Collection<OptDesc>> commandOptions;
        private Collection<OptDesc> options;
        private List<Completer> argsCompleters;
        private int startPos;
        
        public OptionCompleter(final Completer completer, final Function<String, Collection<OptDesc>> commandOptions, final int startPos) {
            this.argsCompleters = new ArrayList<Completer>();
            this.startPos = startPos;
            this.commandOptions = commandOptions;
            this.argsCompleters.add(completer);
        }
        
        public OptionCompleter(final List<Completer> completers, final Function<String, Collection<OptDesc>> commandOptions, final int startPos) {
            this.argsCompleters = new ArrayList<Completer>();
            this.startPos = startPos;
            this.commandOptions = commandOptions;
            this.argsCompleters = new ArrayList<Completer>(completers);
        }
        
        public OptionCompleter(final List<Completer> completers, final Map<String, List<String>> optionValues, final Collection<String> options, final int startPos) {
            this(optionValues, options, startPos);
            this.argsCompleters = new ArrayList<Completer>(completers);
        }
        
        public OptionCompleter(final Completer completer, final Map<String, List<String>> optionValues, final Collection<String> options, final int startPos) {
            this(optionValues, options, startPos);
            this.argsCompleters.add(completer);
        }
        
        public OptionCompleter(final Map<String, List<String>> optionValues, final Collection<String> options, final int startPos) {
            this(OptDesc.compile(optionValues, options), startPos);
        }
        
        public OptionCompleter(final Completer completer, final Collection<OptDesc> options, final int startPos) {
            this(options, startPos);
            this.argsCompleters.add(completer);
        }
        
        public OptionCompleter(final List<Completer> completers, final Collection<OptDesc> options, final int startPos) {
            this(options, startPos);
            this.argsCompleters = new ArrayList<Completer>(completers);
        }
        
        public OptionCompleter(final Collection<OptDesc> options, final int startPos) {
            this.argsCompleters = new ArrayList<Completer>();
            this.options = options;
            this.startPos = startPos;
        }
        
        public void setStartPos(final int startPos) {
            this.startPos = startPos;
        }
        
        @Override
        public void complete(final LineReader reader, final ParsedLine commandLine, final List<Candidate> candidates) {
            assert commandLine != null;
            assert candidates != null;
            final List<String> words = commandLine.words();
            final String buffer = commandLine.word().substring(0, commandLine.wordCursor());
            if (this.startPos >= words.size()) {
                candidates.add(new Candidate(buffer, buffer, null, null, null, null, true));
                return;
            }
            final String command = reader.getParser().getCommand(words.get(this.startPos - 1));
            if (buffer.startsWith("-")) {
                boolean addbuff = true;
                boolean valueCandidates = false;
                final boolean longOption = buffer.startsWith("--");
                final int eq = buffer.matches("-[a-zA-Z][a-zA-Z0-9]+") ? 2 : buffer.indexOf(61);
                if (eq < 0) {
                    final List<String> usedOptions = new ArrayList<String>();
                    for (int i = this.startPos; i < words.size(); ++i) {
                        if (words.get(i).startsWith("-")) {
                            final String w = words.get(i);
                            final int ind = w.indexOf(61);
                            if (ind < 0) {
                                usedOptions.add(w);
                            }
                            else {
                                usedOptions.add(w.substring(0, ind));
                            }
                        }
                    }
                    for (final OptDesc o : (this.commandOptions == null) ? this.options : this.commandOptions.apply(command)) {
                        if (!usedOptions.contains(o.shortOption())) {
                            if (usedOptions.contains(o.longOption())) {
                                continue;
                            }
                            if (o.startsWith(buffer)) {
                                addbuff = false;
                            }
                            o.completeOption(reader, commandLine, candidates, longOption);
                        }
                    }
                }
                else {
                    addbuff = false;
                    final int nb = buffer.contains("=") ? 1 : 0;
                    final String value = buffer.substring(eq + nb);
                    final String curBuf = buffer.substring(0, eq + nb);
                    final String opt = buffer.substring(0, eq);
                    final OptDesc option = this.findOptDesc(command, opt);
                    if (option.hasValue()) {
                        valueCandidates = option.completeValue(reader, commandLine, candidates, curBuf, value);
                    }
                }
                if ((buffer.contains("=") && !buffer.endsWith("=") && !valueCandidates) || addbuff) {
                    candidates.add(new Candidate(buffer, buffer, null, null, null, null, true));
                }
            }
            else if (words.size() > 1 && this.shortOptionValueCompleter(command, words.get(words.size() - 2)) != null) {
                this.shortOptionValueCompleter(command, words.get(words.size() - 2)).complete(reader, commandLine, candidates);
            }
            else if (words.size() > 1 && this.longOptionValueCompleter(command, words.get(words.size() - 2)) != null) {
                this.longOptionValueCompleter(command, words.get(words.size() - 2)).complete(reader, commandLine, candidates);
            }
            else if (!this.argsCompleters.isEmpty()) {
                int args = -1;
                for (int j = this.startPos; j < words.size(); ++j) {
                    if (!words.get(j).startsWith("-") && j > 0 && this.shortOptionValueCompleter(command, words.get(j - 1)) == null && this.longOptionValueCompleter(command, words.get(j - 1)) == null) {
                        ++args;
                    }
                }
                if (args == -1) {
                    candidates.add(new Candidate(buffer, buffer, null, null, null, null, true));
                }
                else if (args < this.argsCompleters.size()) {
                    this.argsCompleters.get(args).complete(reader, commandLine, candidates);
                }
                else {
                    this.argsCompleters.get(this.argsCompleters.size() - 1).complete(reader, commandLine, candidates);
                }
            }
        }
        
        private Completer longOptionValueCompleter(final String command, final String opt) {
            if (!opt.matches("--[a-zA-Z]+")) {
                return null;
            }
            final Collection<OptDesc> optDescs = (this.commandOptions == null) ? this.options : this.commandOptions.apply(command);
            final OptDesc option = this.findOptDesc(optDescs, opt);
            return option.hasValue() ? option.valueCompleter() : null;
        }
        
        private Completer shortOptionValueCompleter(final String command, final String opt) {
            if (!opt.matches("-[a-zA-Z]+")) {
                return null;
            }
            Completer out = null;
            final Collection<OptDesc> optDescs = (this.commandOptions == null) ? this.options : this.commandOptions.apply(command);
            if (opt.length() == 2) {
                out = this.findOptDesc(optDescs, opt).valueCompleter();
            }
            else if (opt.length() > 2) {
                for (int i = 1; i < opt.length(); ++i) {
                    final OptDesc o = this.findOptDesc(optDescs, "-" + opt.charAt(i));
                    if (o.shortOption() == null) {
                        return null;
                    }
                    if (out == null) {
                        out = o.valueCompleter();
                    }
                }
            }
            return out;
        }
        
        private OptDesc findOptDesc(final String command, final String opt) {
            return this.findOptDesc((this.commandOptions == null) ? this.options : this.commandOptions.apply(command), opt);
        }
        
        private OptDesc findOptDesc(final Collection<OptDesc> optDescs, final String opt) {
            for (final OptDesc o : optDescs) {
                if (o.match(opt)) {
                    return o;
                }
            }
            return new OptDesc();
        }
    }
    
    public static class AnyCompleter implements Completer
    {
        public static final AnyCompleter INSTANCE;
        
        @Override
        public void complete(final LineReader reader, final ParsedLine commandLine, final List<Candidate> candidates) {
            assert commandLine != null;
            assert candidates != null;
            final String buffer = commandLine.word().substring(0, commandLine.wordCursor());
            candidates.add(new Candidate(AttributedString.stripAnsi(buffer), buffer, null, null, null, null, true));
        }
        
        static {
            INSTANCE = new AnyCompleter();
        }
    }
    
    public interface CompletionEnvironment
    {
        Map<String, List<CompletionData>> getCompletions();
        
        Set<String> getCommands();
        
        String resolveCommand(final String p0);
        
        String commandName(final String p0);
        
        Object evaluate(final LineReader p0, final ParsedLine p1, final String p2) throws Exception;
    }
}
