/// <summary>
            /// Implements <a href="http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm">Tarjan's algorithm</a>
            /// for finding the strongly connected components of the automaton graph.
            /// </summary>
            /// <param name="currentState">The state currently being traversed.</param>
            /// <param name="traversalIndex">The traversal index (as defined by the Tarjan's algorithm).</param>
            /// <param name="stateIdToStateInfo">A dictionary mapping state indices to info records maintained by the Tarjan's algorithm.</param>
            /// <param name="stateIdStack">The traversal stack (as defined by the Tarjan's algorithm).</param>
            private void FindStronglyConnectedComponents(
                State currentState,
                ref int traversalIndex,
                Dictionary <int, TarjanStateInfo> stateIdToStateInfo,
                Stack <State> stateIdStack)
            {
                Debug.Assert(!stateIdToStateInfo.ContainsKey(currentState.Index), "Visited states must not be revisited.");

                var stateInfo = new TarjanStateInfo(traversalIndex);

                stateIdToStateInfo.Add(currentState.Index, stateInfo);
                ++traversalIndex;

                stateIdStack.Push(currentState);
                stateInfo.InStack = true;

                for (int transitionIndex = 0; transitionIndex < currentState.TransitionCount; ++transitionIndex)
                {
                    Transition transition = currentState.GetTransition(transitionIndex);
                    if (!this.transitionFilter(transition))
                    {
                        continue;
                    }

                    TarjanStateInfo destinationStateInfo;
                    if (!stateIdToStateInfo.TryGetValue(transition.DestinationStateIndex, out destinationStateInfo))
                    {
                        this.FindStronglyConnectedComponents(
                            this.Root.Owner.states[transition.DestinationStateIndex], ref traversalIndex, stateIdToStateInfo, stateIdStack);
                        stateInfo.Lowlink = Math.Min(stateInfo.Lowlink, stateIdToStateInfo[transition.DestinationStateIndex].Lowlink);
                    }
                    else if (destinationStateInfo.InStack)
                    {
                        stateInfo.Lowlink = Math.Min(stateInfo.Lowlink, destinationStateInfo.TraversalIndex);
                    }
                }

                if (stateInfo.Lowlink == stateInfo.TraversalIndex)
                {
                    var   statesInComponent = new List <State>();
                    State state;
                    do
                    {
                        state = stateIdStack.Pop();
                        stateIdToStateInfo[state.Index].InStack = false;
                        statesInComponent.Add(state);
                    }while (state.Index != currentState.Index);

                    this.components.Add(new StronglyConnectedComponent(this.transitionFilter, statesInComponent, this.useApproximateClosure));
                }
            }
            /// <summary>
            /// Implements <a href="http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm">Tarjan's algorithm</a>
            /// for finding the strongly connected components of the automaton graph.
            /// </summary>
            private List <StronglyConnectedComponent> FindStronglyConnectedComponents()
            {
                var components = new List <StronglyConnectedComponent>();

                var states             = this.automaton.States;
                var stateIdStack       = new Stack <int>();
                var stateIdToStateInfo = new Dictionary <int, TarjanStateInfo>();
                int traversalIndex     = 0;

                var traversalStack = new Stack <(TarjanStateInfo, int stateIndex, int currentTransitionIndex)>();

                traversalStack.Push((null, this.Root.Index, 0));
                while (traversalStack.Count > 0)
                {
                    var(stateInfo, currentStateIndex, currentTransitionIndex) = traversalStack.Pop();
                    var currentState = states[currentStateIndex];
                    var transitions  = currentState.Transitions;

                    if (stateInfo == null)
                    {
                        // Entered into processing of this state for the first time: create Tarjan info for it
                        // and push the state onto Tarjan stack.
                        stateInfo = new TarjanStateInfo(traversalIndex);
                        stateIdToStateInfo.Add(currentStateIndex, stateInfo);
                        ++traversalIndex;
                        stateIdStack.Push(currentStateIndex);
                        stateInfo.InStack = true;
                    }
                    else
                    {
                        // Just returned from recursion into 'currentTransitionIndex': update Lowlink
                        // and advance to next transition.
                        var lastDestination = transitions[currentTransitionIndex].DestinationStateIndex;
                        stateInfo.Lowlink = Math.Min(stateInfo.Lowlink, stateIdToStateInfo[lastDestination].Lowlink);
                        ++currentTransitionIndex;
                    }

                    for (; currentTransitionIndex < transitions.Count; ++currentTransitionIndex)
                    {
                        var transition = transitions[currentTransitionIndex];
                        if (!this.transitionFilter(transition))
                        {
                            continue;
                        }

                        var destinationStateIndex = transition.DestinationStateIndex;
                        if (!stateIdToStateInfo.TryGetValue(destinationStateIndex, out var destinationStateInfo))
                        {
                            // Return to this (state/transition) after destinationState is processed.
                            // Processing will resume from currentTransitionIndex.
                            traversalStack.Push((stateInfo, currentStateIndex, currentTransitionIndex));
                            // Process destination state.
                            traversalStack.Push((null, destinationStateIndex, 0));
                            // Do not process other transitions until destination state is processed.
                            break;
                        }

                        if (destinationStateInfo.InStack)
                        {
                            stateInfo.Lowlink = Math.Min(stateInfo.Lowlink, destinationStateInfo.TraversalIndex);
                        }
                    }

                    if (currentTransitionIndex < transitions.Count)
                    {
                        // Not all states processed: continue processing according to stack. Current state
                        // is already pushed onto it -> will be processed again.
                        continue;
                    }

                    if (stateInfo.Lowlink == stateInfo.TraversalIndex)
                    {
                        var statesInComponent = new List <State>();
                        int stateIndex;
                        do
                        {
                            stateIndex = stateIdStack.Pop();
                            stateIdToStateInfo[stateIndex].InStack = false;
                            statesInComponent.Add(states[stateIndex]);
                        } while (stateIndex != currentStateIndex);

                        components.Add(new StronglyConnectedComponent(
                                           this.automaton, this.transitionFilter, statesInComponent, this.useApproximateClosure));
                        this.TotalStatesCount += statesInComponent.Count;
                    }
                }

                return(components);
            }