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

package org.jline.reader.impl;

import org.jline.utils.AttributedString;
import java.util.HashMap;
import java.util.regex.Pattern;
import java.util.Arrays;
import java.util.stream.Stream;
import java.util.Collection;
import java.util.Iterator;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.Collections;
import org.jline.reader.CompletingParsedLine;
import org.jline.reader.LineReader;
import java.util.ArrayList;
import org.jline.reader.Candidate;
import java.util.Map;
import java.util.function.Function;
import java.util.List;
import java.util.function.Predicate;
import org.jline.reader.CompletionMatcher;

public class CompletionMatcherImpl implements CompletionMatcher
{
    protected Predicate<String> exact;
    protected List<Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>>> matchers;
    private Map<String, List<Candidate>> matching;
    private boolean caseInsensitive;
    
    protected void reset(final boolean caseInsensitive) {
        this.caseInsensitive = caseInsensitive;
        this.exact = (s -> false);
        this.matchers = new ArrayList<Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>>>();
        this.matching = null;
    }
    
    @Override
    public void compile(final Map<LineReader.Option, Boolean> options, final boolean prefix, final CompletingParsedLine line, final boolean caseInsensitive, final int errors, final String originalGroupName) {
        this.reset(caseInsensitive);
        this.defaultMatchers(options, prefix, line, caseInsensitive, errors, originalGroupName);
    }
    
    @Override
    public List<Candidate> matches(final List<Candidate> candidates) {
        this.matching = Collections.emptyMap();
        final Map<String, List<Candidate>> sortedCandidates = this.sort(candidates);
        for (final Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>> matcher : this.matchers) {
            this.matching = matcher.apply(sortedCandidates);
            if (!this.matching.isEmpty()) {
                break;
            }
        }
        return (List<Candidate>)(this.matching.isEmpty() ? new ArrayList<Object>() : this.matching.entrySet().stream().flatMap(e -> e.getValue().stream()).distinct().collect((Collector<? super Object, ?, List<? super Object>>)Collectors.toList()));
    }
    
    @Override
    public Candidate exactMatch() {
        if (this.matching == null) {
            throw new IllegalStateException();
        }
        return this.matching.values().stream().flatMap((Function<? super List<Candidate>, ? extends Stream<? extends Candidate>>)Collection::stream).filter(Candidate::complete).filter(c -> this.exact.test(c.value())).findFirst().orElse(null);
    }
    
    @Override
    public String getCommonPrefix() {
        if (this.matching == null) {
            throw new IllegalStateException();
        }
        String commonPrefix = null;
        for (final String key : this.matching.keySet()) {
            commonPrefix = ((commonPrefix == null) ? key : this.getCommonStart(commonPrefix, key, this.caseInsensitive));
        }
        return commonPrefix;
    }
    
    protected void defaultMatchers(final Map<LineReader.Option, Boolean> options, final boolean prefix, final CompletingParsedLine line, final boolean caseInsensitive, final int errors, final String originalGroupName) {
        final String wd = line.word();
        final String wdi = caseInsensitive ? wd.toLowerCase() : wd;
        final String wp = wdi.substring(0, line.wordCursor());
        if (prefix) {
            this.matchers = new ArrayList<Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>>>(Arrays.asList(this.simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).startsWith(wp)), this.simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).contains(wp))));
            if (LineReader.Option.COMPLETE_MATCHER_TYPO.isSet(options)) {
                this.matchers.add(this.typoMatcher(wp, errors, caseInsensitive, originalGroupName));
            }
            this.exact = (s -> caseInsensitive ? s.equalsIgnoreCase(wp) : s.equals(wp));
        }
        else if (!LineReader.Option.EMPTY_WORD_OPTIONS.isSet(options) && wd.length() == 0) {
            this.matchers = new ArrayList<Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>>>(Collections.singletonList(this.simpleMatcher(s -> !s.startsWith("-"))));
            this.exact = (s -> caseInsensitive ? s.equalsIgnoreCase(wd) : s.equals(wd));
        }
        else {
            if (LineReader.Option.COMPLETE_IN_WORD.isSet(options)) {
                final String ws = wdi.substring(line.wordCursor());
                final Pattern p1 = Pattern.compile(Pattern.quote(wp) + ".*" + Pattern.quote(ws) + ".*");
                final Pattern p2 = Pattern.compile(".*" + Pattern.quote(wp) + ".*" + Pattern.quote(ws) + ".*");
                this.matchers = new ArrayList<Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>>>(Arrays.asList(this.simpleMatcher(s -> p1.matcher(caseInsensitive ? s.toLowerCase() : s).matches()), this.simpleMatcher(s -> p2.matcher(caseInsensitive ? s.toLowerCase() : s).matches())));
            }
            else {
                this.matchers = new ArrayList<Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>>>(Arrays.asList(this.simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).startsWith(wdi)), this.simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).contains(wdi))));
            }
            if (LineReader.Option.COMPLETE_MATCHER_CAMELCASE.isSet(options)) {
                this.matchers.add(this.simpleMatcher(s -> this.camelMatch(wd, 0, s, 0)));
            }
            if (LineReader.Option.COMPLETE_MATCHER_TYPO.isSet(options)) {
                this.matchers.add(this.typoMatcher(wdi, errors, caseInsensitive, originalGroupName));
            }
            this.exact = (s -> caseInsensitive ? s.equalsIgnoreCase(wd) : s.equals(wd));
        }
    }
    
    protected Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>> simpleMatcher(final Predicate<String> predicate) {
        return (Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>>)(m -> m.entrySet().stream().filter(e -> predicate.test(e.getKey())).collect(Collectors.toMap((Function<? super Object, ?>)Map.Entry::getKey, (Function<? super Object, ?>)Map.Entry::getValue)));
    }
    
    protected Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>> typoMatcher(final String word, final int errors, final boolean caseInsensitive, final String originalGroupName) {
        return (Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>>)(m -> {
            final Map map = (Map)m.entrySet().stream().filter(e -> ReaderUtils.distance(word, caseInsensitive ? e.getKey().toLowerCase() : e.getKey()) < errors).collect(Collectors.toMap((Function<? super Object, ?>)Map.Entry::getKey, (Function<? super Object, ?>)Map.Entry::getValue));
            if (map.size() > 1) {
                ((List<Candidate>)map.computeIfAbsent(word, w -> new ArrayList())).add(new Candidate(word, word, originalGroupName, null, null, null, false));
            }
            return map;
        });
    }
    
    protected boolean camelMatch(final String word, final int i, final String candidate, final int j) {
        if (word.length() <= i) {
            return true;
        }
        if (candidate.length() <= j) {
            return false;
        }
        final char c = word.charAt(i);
        if (c == candidate.charAt(j)) {
            return this.camelMatch(word, i + 1, candidate, j + 1);
        }
        for (int j2 = j; j2 < candidate.length(); ++j2) {
            if (Character.isUpperCase(candidate.charAt(j2)) && Character.toUpperCase(c) == candidate.charAt(j2) && this.camelMatch(word, i + 1, candidate, j2 + 1)) {
                return true;
            }
        }
        return false;
    }
    
    private Map<String, List<Candidate>> sort(final List<Candidate> candidates) {
        final Map<String, List<Candidate>> sortedCandidates = new HashMap<String, List<Candidate>>();
        for (final Candidate candidate : candidates) {
            sortedCandidates.computeIfAbsent(AttributedString.fromAnsi(candidate.value()).toString(), s -> new ArrayList()).add(candidate);
        }
        return sortedCandidates;
    }
    
    private String getCommonStart(final String str1, final String str2, final boolean caseInsensitive) {
        int[] s1;
        int[] s2;
        int len;
        int ch1;
        int ch2;
        for (s1 = str1.codePoints().toArray(), s2 = str2.codePoints().toArray(), len = 0; len < Math.min(s1.length, s2.length); ++len) {
            ch1 = s1[len];
            ch2 = s2[len];
            if (ch1 != ch2 && caseInsensitive) {
                ch1 = Character.toUpperCase(ch1);
                ch2 = Character.toUpperCase(ch2);
                if (ch1 != ch2) {
                    ch1 = Character.toLowerCase(ch1);
                    ch2 = Character.toLowerCase(ch2);
                }
            }
            if (ch1 != ch2) {
                break;
            }
        }
        return new String(s1, 0, len);
    }
}
