package org.nwapw.abacus.lexing.pattern;

import java.util.Collection;
import java.util.HashMap;
import java.util.Stack;
import java.util.function.Function;

public class Pattern<T> {

    private T id;
    private PatternNode<T> head;
    private String source;
    private int index;

    private HashMap<Character, Function<PatternChain<T>, PatternChain<T>>> operations =
            new HashMap<Character, Function<PatternChain<T>, PatternChain<T>>>() {{
                put('+', Pattern.this::transformPlus);
                put('*', Pattern.this::transformStar);
                put('?', Pattern.this::transformQuestion);
            }};

    private PatternChain<T> transformPlus(PatternChain<T> chain){
        chain.tail.outputStates.add(chain.head);
        return chain;
    }

    private PatternChain<T> transformStar(PatternChain<T> chain){
        LinkNode<T> newTail = new LinkNode<>();
        LinkNode<T> newHead = new LinkNode<>();
        newHead.outputStates.add(chain.head);
        newHead.outputStates.add(newTail);
        chain.tail.outputStates.add(newTail);
        newTail.outputStates.add(newHead);
        chain.head = newHead;
        chain.tail = newTail;
        return chain;
    }

    private PatternChain<T> transformQuestion(PatternChain<T> chain){
        LinkNode<T> newTail = new LinkNode<>();
        LinkNode<T> newHead = new LinkNode<>();
        newHead.outputStates.add(chain.head);
        newHead.outputStates.add(newTail);
        chain.tail.outputStates.add(newTail);
        chain.head = newHead;
        chain.tail = newTail;
        return chain;
    }

    private PatternChain<T> combineChains(Collection<PatternChain<T>> collection){
        LinkNode<T> head = new LinkNode<>();
        LinkNode<T> tail = new LinkNode<>();
        PatternChain<T> newChain = new PatternChain<>(head, tail);
        for(PatternChain<T> chain : collection){
            head.outputStates.add(chain.head);
            chain.tail.outputStates.add(tail);
        }
        return newChain;
    }

    private PatternChain<T> parseValue(){
        if(index >= source.length()) return null;
        if(source.charAt(index) == '\\'){
            if(++index >= source.length()) return null;
        }
        return new PatternChain<>(new ValueNode<>(source.charAt(index++)));
    }

    private PatternChain<T> parseOr(){
        Stack<PatternChain<T>> orStack = new Stack<>();
        while(index < source.length() && source.charAt(index) != ']'){
            if(source.charAt(index) == '-'){
                index++;
                if(orStack.empty() || orStack.peek().tail.range() == '\0') return null;
                PatternChain<T> bottomRange = orStack.pop();
                PatternChain<T> topRange = parseValue();
                if(topRange == null || topRange.tail.range() == '\0') return null;

                orStack.push(new PatternChain<>(new RangeNode<>(bottomRange.tail.range(), topRange.tail.range())));
            } else {
                PatternChain<T> newChain = parseValue();
                if(newChain == null) return null;
                orStack.push(newChain);
            }
            index++;
        }
        if(index >= source.length()) return null;
        return combineChains(orStack);
    }

    private PatternChain<T> parseSegment(boolean isSubsegment){
        if(index >= source.length() || ((source.charAt(index) != '(') && isSubsegment)) return null;
        if(isSubsegment) index++;

        Stack<PatternChain<T>> orChain = new Stack<>();
        PatternChain<T> fullChain = new PatternChain<>();
        PatternChain<T> currentChain = null;
        while (index < source.length() && source.charAt(index) != ')'){
            char currentChar = source.charAt(index);
            if(operations.containsKey(currentChar)){
                if(currentChain == null) return null;

                currentChain = operations.get(currentChar).apply(currentChain);
                fullChain.append(currentChain);
                currentChain = null;
                index++;
            } else if(currentChar == '|'){
                if(currentChain == null) return null;

                fullChain.append(currentChain);
                orChain.push(fullChain);
                currentChain = null;
                fullChain = new PatternChain<>();
            } else if(currentChar == '('){
                if(currentChain != null) {
                    fullChain.append(currentChain);
                }

                currentChain = parseSegment(true);
                if(currentChain == null) return null;
            } else if(currentChar == '['){
                if(currentChain != null){
                    fullChain.append(currentChain);
                }
                currentChain = parseOr();
                if(currentChain == null) return null;
            } else {
                if(currentChain != null){
                    fullChain.append(currentChain);
                }
                currentChain = parseValue();
                if(currentChain == null) return null;
            }
        }

        if(!(!isSubsegment || (index < source.length() && source.charAt(index) == ')'))) return null;
        if(isSubsegment) index++;

        if(currentChain != null) fullChain.append(currentChain);
        if(!orChain.empty()){
            orChain.push(fullChain);
            fullChain = combineChains(orChain);
        }

        return fullChain;
    }

    public Pattern(String from, T id){
        this.id = id;
        index = 0;
        source = from;

        PatternChain<T> chain = parseSegment(false);
        if(chain == null) {
            head = null;
        } else {
            chain.append(new EndNode<>(id));
            head = chain.head;
        }
    }

}

@@ -2396,32 +2396,49 @@
  new Stack%3C%3E();%0A
+        index++;%0A
         while(in
@@ -3150,37 +3150,16 @@
       %7D%0A
-            index++;%0A
         
@@ -3168,32 +3168,34 @@
         if(index
+++
  %3E= source.lengt
@@ -3226,16 +3226,56 @@
   return
+ (orStack.size() == 1) ? orStack.pop() :
  combine
@@ -4859,32 +4859,280 @@
 l) return null;%0A
+            %7D else if(currentChar == '.')%7B%0A                if(currentChain != null)%7B%0A                    fullChain.append(currentChain);%0A                %7D%0A                currentChain = new PatternChain%3C%3E(new AnyNode%3C%3E());%0A                index++;%0A
             %7D el
@@ -6053,10 +6053,75 @@
 %0A    %7D%0A%0A
+    public PatternNode%3CT%3E getHead() %7B%0A        return head;%0A    %7D%0A
 %7D%0A

Fix a few bug in the pattern compilation code.