package chapter5.section4;

import chapter1.section3.Bag;
import chapter1.section3.Stack;
import chapter4.section2.Digraph;
import edu.princeton.cs.algs4.StdOut;

import java.util.StringJoiner;

/**
 * Created by Rene Argento on 25/03/18.
 */
@SuppressWarnings("unchecked")
public class Exercise22_Proof {

    private class State {
        private int id;
        private State previous;

        State(int id, State previous) {
            this.id = id;
            this.previous = previous;
        }
    }

    public class RegularExpressionMatcherWithProof extends RegularExpressionMatcher {

        public RegularExpressionMatcherWithProof(String regularExpression) {
            super(regularExpression);
        }

        @Override
        public boolean recognizes(String text) {
            Bag<State> allPossibleStates = new Bag<>();
            State sourceState = new State(0, null);
            DirectedDFS directedDFS = new DirectedDFS(digraph, sourceState);

            for (State newSate : directedDFS.getNewStates()) {
                allPossibleStates.add(newSate);
            }

            for (int i = 0; i < text.length(); i++) {
                // Compute possible NFA states for text[i + 1]
                Bag<State> states = new Bag<>();

                for (State state : allPossibleStates) {
                    if (state.id < numberOfStates) {

                        if (setsMatchMap.contains(state.id)) {
                            recognizeSet(text, i, state, states);
                        } else if (regularExpression[state.id] == text.charAt(i)
                                || regularExpression[state.id] == '.') {
                            addNextState(states, state, state.id + 1);
                        }
                    }
                }

                allPossibleStates = new Bag<>();
                directedDFS = new DirectedDFS(digraph, states);

                if (directedDFS.getNewStates() != null) {
                    for (State newSate : directedDFS.getNewStates()) {
                        allPossibleStates.add(newSate);
                    }
                } else {
                    // Optimization if no states are reachable
                    StdOut.println("Text was not recognized by the DFA");
                    return false;
                }
            }

            for (State state : allPossibleStates) {
                if (state.id == numberOfStates) {
                    printProof(state);
                    return true;
                }
            }

            StdOut.println("Text was not recognized by the DFA");
            return false;
        }

        private void recognizeSet(String text, int index, State state, Bag<State> states) {
            int indexOfRightSquareBracket = setsMatchMap.get(state.id);

            // Is it a range?
            if (regularExpression[state.id + 1] == '-') { // No need to worry about out of bounds indexes
                char leftRangeIndex = regularExpression[state.id];
                char rightRangeIndex = regularExpression[state.id + 2];

                if (leftRangeIndex <= text.charAt(index) && text.charAt(index) <= rightRangeIndex) {
                    if (!isCharPartOfComplementSet(text, index, state.id)) {
                        addNextState(states, state, indexOfRightSquareBracket);
                    }
                } else if (setsComplementMap.contains(state.id)
                        && !isCharPartOfComplementSet(text, index, state.id)) {
                    addNextState(states, state, indexOfRightSquareBracket);
                }
            } else if (regularExpression[state.id] == text.charAt(index) || regularExpression[state.id] == '.') {
                if (!isCharPartOfComplementSet(text, index, state.id)) {
                    addNextState(states, state, indexOfRightSquareBracket);
                }
            } else if (setsComplementMap.contains(state.id) && !isCharPartOfComplementSet(text, index, state.id)) {
                addNextState(states, state, indexOfRightSquareBracket);
            }
        }

        private void addNextState(Bag<State> states, State currentState, int nextStateId) {
            states.add(new State(nextStateId, currentState));
        }

        private void printProof(State state) {
            Stack<State> states = new Stack<>();

            states.push(state);
            while (state.previous != null) {
                states.push(state.previous);
                state = state.previous;
            }

            StringJoiner proof = new StringJoiner(" -> ");
            while (!states.isEmpty()) {
                proof.add(String.valueOf(states.pop().id));
            }

            StdOut.println(proof);
        }
    }

    public class DirectedDFS {

        private boolean[] visited;
        private Bag<State> newStates;

        public DirectedDFS(Digraph digraph, State source) {
            visited = new boolean[digraph.vertices()];
            newStates = new Bag<>();

            dfs(digraph, source);
        }

        public DirectedDFS(Digraph digraph, Iterable<State> sources) {
            visited = new boolean[digraph.vertices()];
            newStates = new Bag<>();

            for(State source : sources) {
                if (!visited[source.id]) {
                    dfs(digraph, source);
                }
            }
        }

        private void dfs(Digraph digraph, State source) {
            visited[source.id] = true;
            newStates.add(source);

            for(int neighbor : digraph.adjacent(source.id)) {
                if (!visited[neighbor]) {
                    State newState = new State(neighbor, source);
                    newStates.add(newState);

                    dfs(digraph, newState);
                }
            }
        }

        public boolean marked(int vertex) {
            return visited[vertex];
        }

        public Bag<State> getNewStates() {
            return newStates;
        }
    }

    public static void main(String[] args) {
        Exercise22_Proof proof = new Exercise22_Proof();

        String pattern1 = "RENE[^ABC]";
        Exercise22_Proof.RegularExpressionMatcherWithProof regularExpressionMatcherWithProof1 =
                proof.new RegularExpressionMatcherWithProof(pattern1);
        String text1 = "RENED";
        StdOut.print("Proof 1: ");
        regularExpressionMatcherWithProof1.recognizes(text1);
        StdOut.println("Expected: 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 9 -> 10");

        String pattern2 = "A[^A-Z0]+Z";
        Exercise22_Proof.RegularExpressionMatcherWithProof regularExpressionMatcherWithProof2 =
                proof.new RegularExpressionMatcherWithProof(pattern2);
        String text2 = "AbZ";
        StdOut.print("\nProof 2: ");
        regularExpressionMatcherWithProof2.recognizes(text2);
        StdOut.println("Expected: 0 -> 1 -> 2 -> 3 -> 7 -> 8 -> 9 -> 10");

        String text3 = "AabcdeZ";
        StdOut.print("\nProof 3: ");
        regularExpressionMatcherWithProof2.recognizes(text3);
        StdOut.println("Expected: 0 -> 1 -> 2 -> 3 -> 7 -> 8 -> 1 -> 2 -> 3 -> 7 -> 8 " +
                "-> 1 -> 2 -> 3 -> 7 -> 8 -> 1 -> 2 -> 3 -> 7 -> 8 -> 1 -> 2 -> 3 -> 7 -> 8 -> 9 -> 10");

        String pattern3 = "A[^A-Z0]+ZZ";
        Exercise22_Proof.RegularExpressionMatcherWithProof regularExpressionMatcherWithProof3 =
                proof.new RegularExpressionMatcherWithProof(pattern3);
        String text4 = "AbcZZ";
        StdOut.print("\nProof 4: ");
        regularExpressionMatcherWithProof3.recognizes(text4);
        StdOut.println("Expected: 0 -> 1 -> 2 -> 3 -> 7 -> 8 -> 1 -> 2 -> 3 -> 7 -> 8 -> 9 -> 10 -> 11");

        String pattern4 = "A[^A-Z0]*ZZ";
        Exercise22_Proof.RegularExpressionMatcherWithProof regularExpressionMatcherWithProof4 =
                proof.new RegularExpressionMatcherWithProof(pattern4);
        String text5 = "AZZ";
        StdOut.print("\nProof 5: ");
        regularExpressionMatcherWithProof4.recognizes(text5);
        StdOut.println("Expected: 0 -> 1 -> 8 -> 9 -> 10 -> 11");

        String text6 = "Abcdef123ZZ";
        StdOut.print("\nProof 6: ");
        regularExpressionMatcherWithProof4.recognizes(text6);
        StdOut.println("Expected: 0 -> 1 -> 2 -> 3 -> 7 -> 8 " +
                "-> 1 -> 2 -> 3 -> 7 -> 8 " +
                "-> 1 -> 2 -> 3 -> 7 -> 8 " +
                "-> 1 -> 2 -> 3 -> 7 -> 8 " +
                "-> 1 -> 2 -> 3 -> 7 -> 8 " +
                "-> 1 -> 2 -> 3 -> 7 -> 8 " +
                "-> 1 -> 2 -> 3 -> 7 -> 8 " +
                "-> 1 -> 2 -> 3 -> 7 -> 8 " +
                "-> 9 -> 10 -> 11");

        String pattern5 = "A([^A-Z0]|[^a-f])+[^a-f]Z";
        Exercise22_Proof.RegularExpressionMatcherWithProof regularExpressionMatcherWithProof5 =
                proof.new RegularExpressionMatcherWithProof(pattern5);
        String text7 = "ABgZ";
        StdOut.print("\nProof 7: ");
        regularExpressionMatcherWithProof5.recognizes(text7);
        StdOut.println("Expected: 0 -> 1 -> 10 -> 11 -> 12 -> 15 -> 16 -> 17 -> 18 -> 19 -> 20 -> 23 -> 24 -> 25");

        String text8 = "ABCDEFGagZ";
        StdOut.print("\nProof 8: ");
        regularExpressionMatcherWithProof5.recognizes(text8);
        StdOut.println("Expected: 0 -> 1 -> 10 -> 11 -> 12 -> 15 -> 16 -> 17 -> " +
                "1 -> 10 -> 11 -> 12 -> 15 -> 16 -> 17 -> " +
                "1 -> 10 -> 11 -> 12 -> 15 -> 16 -> 17 -> " +
                "1 -> 10 -> 11 -> 12 -> 15 -> 16 -> 17 -> " +
                "1 -> 10 -> 11 -> 12 -> 15 -> 16 -> 17 -> " +
                "1 -> 10 -> 11 -> 12 -> 15 -> 16 -> 17 -> " +
                "1 -> 2 -> 3 -> 4 -> 8 -> 9 -> 16 -> 17 -> " +
                "18 -> 19 -> 20 -> 23 -> 24 -> 25");

        String text9 = "ABZ";
        StdOut.print("\nProof 9: ");
        regularExpressionMatcherWithProof5.recognizes(text9);
        StdOut.println("Expected: Text was not recognized by the DFA");
    }

}