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

package org.jline.builtins;

import java.util.ArrayList;
import java.util.Deque;
import java.util.function.Consumer;
import java.util.Collection;
import java.util.ArrayDeque;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.Iterator;
import java.util.Set;
import java.util.Objects;
import java.util.HashSet;
import java.util.List;
import java.util.function.BiFunction;

public class NfaMatcher<T>
{
    private final String regexp;
    private final BiFunction<T, String, Boolean> matcher;
    private volatile State start;
    
    public NfaMatcher(final String regexp, final BiFunction<T, String, Boolean> matcher) {
        this.regexp = regexp;
        this.matcher = matcher;
    }
    
    public void compile() {
        if (this.start == null) {
            this.start = toNfa(toPostFix(this.regexp));
        }
    }
    
    public boolean match(final List<T> args) {
        Set<State> clist = new HashSet<State>();
        this.compile();
        this.addState(clist, this.start);
        for (final T arg : args) {
            final Set<State> nlist = new HashSet<State>();
            clist.stream().filter(s -> !Objects.equals("++MATCH++", s.c) && !Objects.equals("++SPLIT++", s.c)).filter(s -> this.matcher.apply((T)arg, s.c)).forEach(s -> this.addState(nlist, s.out));
            clist = nlist;
        }
        return clist.stream().anyMatch(s -> Objects.equals("++MATCH++", s.c));
    }
    
    public Set<String> matchPartial(final List<T> args) {
        Set<State> clist = new HashSet<State>();
        this.compile();
        this.addState(clist, this.start);
        for (final T arg : args) {
            final Set<State> nlist = new HashSet<State>();
            clist.stream().filter(s -> !Objects.equals("++MATCH++", s.c) && !Objects.equals("++SPLIT++", s.c)).filter(s -> this.matcher.apply((T)arg, s.c)).forEach(s -> this.addState(nlist, s.out));
            clist = nlist;
        }
        return clist.stream().filter(s -> !Objects.equals("++MATCH++", s.c) && !Objects.equals("++SPLIT++", s.c)).map(s -> s.c).collect((Collector<? super Object, ?, Set<String>>)Collectors.toSet());
    }
    
    void addState(final Set<State> l, final State s) {
        if (s != null && l.add(s) && Objects.equals("++SPLIT++", s.c)) {
            this.addState(l, s.out);
            this.addState(l, s.out1);
        }
    }
    
    static State toNfa(final List<String> postfix) {
        final Deque<Frag> stack = new ArrayDeque<Frag>();
        for (final String s2 : postfix) {
            final String p = s2;
            switch (s2) {
                case ".": {
                    final Frag e2 = stack.pollLast();
                    final Frag e3 = stack.pollLast();
                    e3.patch(e2.start);
                    stack.offerLast(new Frag(e3.start, e2.out));
                    continue;
                }
                case "|": {
                    final Frag e2 = stack.pollLast();
                    final Frag e3 = stack.pollLast();
                    final State s = new State("++SPLIT++", e3.start, e2.start);
                    stack.offerLast(new Frag(s, e3.out, e2.out));
                    continue;
                }
                case "?": {
                    final Frag e4 = stack.pollLast();
                    final State s = new State("++SPLIT++", e4.start, null);
                    final Deque<Frag> deque = stack;
                    final State start = s;
                    final List<Consumer<State>> out = e4.out;
                    final State obj = s;
                    Objects.requireNonNull(obj);
                    deque.offerLast(new Frag(start, out, obj::setOut1));
                    continue;
                }
                case "*": {
                    final Frag e4 = stack.pollLast();
                    final State s = new State("++SPLIT++", e4.start, null);
                    e4.patch(s);
                    final Deque<Frag> deque2 = stack;
                    final State start2 = s;
                    final State obj2 = s;
                    Objects.requireNonNull(obj2);
                    deque2.offerLast(new Frag(start2, obj2::setOut1));
                    continue;
                }
                case "+": {
                    final Frag e4 = stack.pollLast();
                    final State s = new State("++SPLIT++", e4.start, null);
                    e4.patch(s);
                    final Deque<Frag> deque3 = stack;
                    final State start3 = e4.start;
                    final State obj3 = s;
                    Objects.requireNonNull(obj3);
                    deque3.offerLast(new Frag(start3, obj3::setOut1));
                    continue;
                }
                default: {
                    final State s = new State(p, null, null);
                    final Deque<Frag> deque4 = stack;
                    final State start4 = s;
                    final State obj4 = s;
                    Objects.requireNonNull(obj4);
                    deque4.offerLast(new Frag(start4, obj4::setOut));
                    continue;
                }
            }
        }
        final Frag e4 = stack.pollLast();
        if (!stack.isEmpty()) {
            throw new IllegalStateException("Wrong postfix expression, " + stack.size() + " elements remaining");
        }
        e4.patch(new State("++MATCH++", null, null));
        return e4.start;
    }
    
    static List<String> toPostFix(final String regexp) {
        final List<String> postfix = new ArrayList<String>();
        int s = -1;
        int natom = 0;
        int nalt = 0;
        final Deque<Integer> natoms = new ArrayDeque<Integer>();
        final Deque<Integer> nalts = new ArrayDeque<Integer>();
        for (int i = 0; i < regexp.length(); ++i) {
            final char c = regexp.charAt(i);
            if (Character.isJavaIdentifierPart(c)) {
                if (s < 0) {
                    s = i;
                }
            }
            else {
                if (s >= 0) {
                    if (natom > 1) {
                        --natom;
                        postfix.add(".");
                    }
                    postfix.add(regexp.substring(s, i));
                    ++natom;
                    s = -1;
                }
                if (!Character.isWhitespace(c)) {
                    switch (c) {
                        case '(': {
                            if (natom > 1) {
                                --natom;
                                postfix.add(".");
                            }
                            nalts.offerLast(nalt);
                            natoms.offerLast(natom);
                            nalt = 0;
                            natom = 0;
                            break;
                        }
                        case '|': {
                            if (natom == 0) {
                                throw new IllegalStateException("unexpected '" + c + "' at pos " + i);
                            }
                            while (--natom > 0) {
                                postfix.add(".");
                            }
                            ++nalt;
                            break;
                        }
                        case ')': {
                            if (nalts.isEmpty() || natom == 0) {
                                throw new IllegalStateException("unexpected '" + c + "' at pos " + i);
                            }
                            while (--natom > 0) {
                                postfix.add(".");
                            }
                            while (nalt > 0) {
                                postfix.add("|");
                                --nalt;
                            }
                            nalt = nalts.pollLast();
                            natom = natoms.pollLast();
                            ++natom;
                            break;
                        }
                        case '*':
                        case '+':
                        case '?': {
                            if (natom == 0) {
                                throw new IllegalStateException("unexpected '" + c + "' at pos " + i);
                            }
                            postfix.add(String.valueOf(c));
                            break;
                        }
                        default: {
                            throw new IllegalStateException("unexpected '" + c + "' at pos " + i);
                        }
                    }
                }
            }
        }
        if (s >= 0) {
            if (natom > 1) {
                --natom;
                postfix.add(".");
            }
            postfix.add(regexp.substring(s));
            ++natom;
        }
        while (--natom > 0) {
            postfix.add(".");
        }
        while (nalt > 0) {
            postfix.add("|");
            --nalt;
        }
        return postfix;
    }
    
    static class State
    {
        static final String Match = "++MATCH++";
        static final String Split = "++SPLIT++";
        final String c;
        State out;
        State out1;
        
        public State(final String c, final State out, final State out1) {
            this.c = c;
            this.out = out;
            this.out1 = out1;
        }
        
        public void setOut(final State out) {
            this.out = out;
        }
        
        public void setOut1(final State out1) {
            this.out1 = out1;
        }
    }
    
    private static class Frag
    {
        final State start;
        final List<Consumer<State>> out;
        
        public Frag(final State start, final Collection<Consumer<State>> l) {
            this.out = new ArrayList<Consumer<State>>();
            this.start = start;
            this.out.addAll(l);
        }
        
        public Frag(final State start, final Collection<Consumer<State>> l1, final Collection<Consumer<State>> l2) {
            this.out = new ArrayList<Consumer<State>>();
            this.start = start;
            this.out.addAll(l1);
            this.out.addAll(l2);
        }
        
        public Frag(final State start, final Consumer<State> c) {
            this.out = new ArrayList<Consumer<State>>();
            this.start = start;
            this.out.add(c);
        }
        
        public Frag(final State start, final Collection<Consumer<State>> l, final Consumer<State> c) {
            this.out = new ArrayList<Consumer<State>>();
            this.start = start;
            this.out.addAll(l);
            this.out.add(c);
        }
        
        public void patch(final State s) {
            this.out.forEach(c -> c.accept(s));
        }
    }
}
