/// <summary>
        /// Assigns every node in the constraint graph a score that corresponds to its absolute position
        /// in the desired execution order. Score collisions are possible but unlikely.
        /// </summary>
        /// <param name="graph"></param>
        /// <returns></returns>
        private static Dictionary <Type, int> ScoreGraphNodes(Dictionary <Type, List <OrderConstraint> > graph, IEnumerable <Type> types)
        {
            Dictionary <Type, int> graphScores = new Dictionary <Type, int>(graph.Count);

            // Initialize graph scores for every node. Has to be done explicitly, so we
            // have proper scores for types that are unconstrained / not part of the graph.
            int baseScore = 0;

            foreach (Type type in types)
            {
                graphScores.Add(type, baseScore++);
            }

            Stack <Type> visitSchedule = new Stack <Type>();

            // For every node, traverse all other reachable nodes and propagate a score
            // value through them that increases with the number of visited nodes in
            // the chain.
            foreach (Type startNode in graph.Keys)
            {
                visitSchedule.Clear();
                visitSchedule.Push(startNode);
                while (visitSchedule.Count > 0)
                {
                    Type node = visitSchedule.Pop();

                    List <OrderConstraint> links;
                    if (!graph.TryGetValue(node, out links))
                    {
                        continue;
                    }

                    int nodeScore = graphScores[node];

                    // Push the current node's score to its neighbour nodes and schedule
                    // them for traversal.
                    for (int i = 0; i < links.Count; i++)
                    {
                        OrderConstraint link = links[i];

                        Type nextNode      = link.LastType;
                        int  nextNodeScore = graphScores[nextNode];

                        // Determine the minimum score the neighbour node should have
                        int minNextNodeScore = nodeScore + 1;

                        // Propagate scores when out of order
                        if (nextNodeScore < minNextNodeScore)
                        {
                            graphScores[nextNode] = minNextNodeScore;
                            visitSchedule.Push(nextNode);
                        }
                    }
                }
            }

            return(graphScores);
        }
            public override bool Equals(object obj)
            {
                if (!(obj is OrderConstraint))
                {
                    return(false);
                }
                OrderConstraint other = (OrderConstraint)obj;

                return
                    (other.FirstType == this.FirstType &&
                     other.LastType == this.LastType &&
                     other.Priority == this.Priority);
            }
        /// <summary>
        /// Clears the specified constraint graph of all loops.
        /// </summary>
        /// <param name="graph"></param>
        private static void ResolveConstraintLoops(Dictionary <Type, List <OrderConstraint> > graph)
        {
            while (true)
            {
                List <OrderConstraint> loop = FindConstraintLoop(graph);
                if (loop == null)
                {
                    return;
                }

                // Found a loop? Find the weakest link in it
                OrderConstraint weakestLink = loop[0];
                for (int i = 1; i < loop.Count; i++)
                {
                    OrderConstraint link = loop[i];
                    if ((int)link.Priority < (int)weakestLink.Priority)
                    {
                        weakestLink = link;
                    }
                }

                // If the loops weakest link was an explicit constraint, log a warning
                if ((int)weakestLink.Priority >= (int)ConstraintPriority.ExplicitWeak)
                {
                    Log.Core.WriteWarning(
                        "Found a loop in the component execution order constraint graph. Ignoring the weakest constraint " +
                        "({0} must be executed before {1}). Please check your ExecutionOrder attributes.",
                        Log.Type(weakestLink.FirstType),
                        Log.Type(weakestLink.LastType));
                }

                // Remove the weakest link
                List <OrderConstraint> links = graph[weakestLink.FirstType];
                links.Remove(weakestLink);
                if (links.Count == 0)
                {
                    graph.Remove(weakestLink.FirstType);
                }
            }
        }
        /// <summary>
        /// Searches for constraint loops in the specified constraint graph and returns the first one.
        /// The result is not necessarily the smallest loop. Returns null if no loop was found.
        /// </summary>
        /// <param name="graph"></param>
        /// <returns></returns>
        private static List <OrderConstraint> FindConstraintLoop(Dictionary <Type, List <OrderConstraint> > graph)
        {
            if (graph.Count == 0)
            {
                return(null);
            }

            // Note that in our specific case of a constraint graph, all valid graphs share
            // the property that there is a maximum of one connection between each two nodes,
            // and there are no loops within the graph, so it forms a propert tree.
            //
            // Thus, we can traverse a valid constraint graph in its entirety and never
            // encounter the same node twice.

            HashSet <Type> visitedNodes  = new HashSet <Type>();
            Stack <Type>   visitSchedule = new Stack <Type>();
            Dictionary <Type, OrderConstraint> prevLinks = new Dictionary <Type, OrderConstraint>();

            foreach (Type startNode in graph.Keys)
            {
                visitedNodes.Clear();
                visitSchedule.Clear();
                prevLinks.Clear();

                // Do a breadth-first graph traversal to see if we'll end up at the start
                visitSchedule.Push(startNode);
                while (visitSchedule.Count > 0)
                {
                    Type node = visitSchedule.Pop();

                    // Already visited this node in a previous run? Can't be part of a loop then.
                    if (!visitedNodes.Add(node))
                    {
                        continue;
                    }

                    List <OrderConstraint> links;
                    if (!graph.TryGetValue(node, out links))
                    {
                        continue;
                    }

                    for (int i = 0; i < links.Count; i++)
                    {
                        OrderConstraint link     = links[i];
                        Type            nextNode = link.LastType;

                        // Found the starting node again through traversal? Found a loop then!
                        if (nextNode == startNode)
                        {
                            List <OrderConstraint> loopPath = new List <OrderConstraint>();
                            while (true)
                            {
                                loopPath.Add(link);
                                if (!prevLinks.TryGetValue(link.FirstType, out link))
                                {
                                    break;
                                }
                            }
                            return(loopPath);
                        }

                        prevLinks[nextNode] = link;
                        visitSchedule.Push(nextNode);
                    }
                }
            }

            return(null);
        }