/// <summary>
    /// Label all nodes according to whether they are (1) on the shortest path, (2) are on a loop to the shortest path,
    /// (3) are on a path resulting in a dead end, and (4) are not connected to the maze.
    /// All labels are inclusive, i.e. the starting point of a dead end is a node labeled as on the shorted path.
    /// The shortest path is found using Dijkstra's algorithm.
    /// </summary>
    /// <returns>List of nodes with path labels.</returns>
    /// <param name="mazeFrame">List of nodes.</param>
    protected void LabelNodesWRTPath(ref MazeFrame mazeFrame)
    {
        //
        // This function labels each node according to the following booleans:
        //
        // onShortestPath - whether node is on the path found using psuedo Dijkstra's method
        // onLoop - whether node is on path that diverges from the shortest path, but returns to it eventually
        // onDeadEnd - whether the node is on a dead end, defined as a path that (1) diverges from the shortest path and doesn't return
        // notConnectedToPath - whether node has a connection on path or is superfluous visual filler
        // (junction nodes have more than one label)

        // Find shortest path from beginning to end using Dijkstra's algorithm
        // From wikipedia/other:

        /*
         * 1) Mark all nodes unvisited, mark selected initial node with a current distance of 0 and the rest with infinity.
         * 2) Set the non-visited node with the smallest current distance as the current node C.
         * 3) For each neighbour N of current node C: add the current distance of C with the weight of the edge
         *    connecting C-N. If it's smaller than the current distance of N, set it as the new current distance of N.
         * 4) Mark the current node C as visited.
         * 5) If end not touched yet, and if there are non-visited nodes, go to step 2.
         */

        // Check start/end node active status
        if (!mazeFrame.StartNode.IsActive || !mazeFrame.EndNode.IsActive)
        {
            throw new System.Exception("Start/end nodes are required to be active.");
        }

        // Prep storage of labels and initilaze as false
        int activeNodeCount = 0;

        foreach (MazeNode nd in mazeFrame.Nodes)
        {
            if (nd.IsActive)
            {
                activeNodeCount++;
            }
        }
        Dictionary <string, bool> onShortestPath     = new Dictionary <string, bool>(activeNodeCount);
        Dictionary <string, bool> onDeadEnd          = new Dictionary <string, bool>(activeNodeCount);
        Dictionary <string, bool> onLoop             = new Dictionary <string, bool>(activeNodeCount);
        Dictionary <string, bool> notConnectedToPath = new Dictionary <string, bool>(activeNodeCount);

        foreach (MazeNode nd in mazeFrame.Nodes)
        {
            if (nd.IsActive)
            {
                onShortestPath.Add(nd.Identifier, false);
                onDeadEnd.Add(nd.Identifier, false);
                onLoop.Add(nd.Identifier, false);
                notConnectedToPath.Add(nd.Identifier, false);
            }
        }

        // Find start and end node
        MazeNode startNode = mazeFrame.StartNode;
        MazeNode endNode   = mazeFrame.EndNode;

        // Create bookkeeping for visits and set all nodes to unvisitied
        Dictionary <string, bool> isVisited = new Dictionary <string, bool>(mazeFrame.Nodes.Count);

        foreach (MazeNode nd in mazeFrame.Nodes)
        {
            if (nd.IsActive)
            {
                isVisited.Add(nd.Identifier, false);
            }
            else
            {
                isVisited.Add(nd.Identifier, true);
            }
        }

        // Ignore nodes without neighbors
        foreach (MazeNode nd in mazeFrame.Nodes)
        {
            if (nd.ConnectedNeighbors.Count == 0)
            {
                isVisited[nd.Identifier] = true;
            }
        }

        // Set distance to start, and initialize at infinity
        Dictionary <string, float> distToStart = new Dictionary <string, float>(activeNodeCount);

        foreach (MazeNode nd in mazeFrame.Nodes)
        {
            if (nd.IsActive)
            {
                distToStart.Add(nd.Identifier, float.PositiveInfinity);
            }
        }

        // Start search
        MazeNode currentNode;

        distToStart[startNode.Identifier] = 0;
        int  count    = 0;
        bool endFound = false;

        while (!endFound)
        {
            // Set current unvisited node based on distance to start
            int   currentNodeInd = -1;
            float lastDist       = float.PositiveInfinity;
            for (int i = 0; i < mazeFrame.Nodes.Count; i++)
            {
                if (!isVisited[mazeFrame.Nodes[i].Identifier])
                {
                    float currDist = distToStart[mazeFrame.Nodes[i].Identifier];
                    if (currDist <= lastDist)
                    {
                        currentNodeInd = i; lastDist = currDist;
                    }
                }
            }
            currentNode = mazeFrame.Nodes[currentNodeInd];

            // Find unvisited neighbors of current node
            List <MazeNode> unvisitedNeighbors = new List <MazeNode>();
            foreach (MazeNode nb in currentNode.ConnectedNeighbors)
            {
                if (isVisited[nb.Identifier] == false)
                {
                    unvisitedNeighbors.Add(nb);
                }
            }

            // For each neighbor, set distToStart as min of (currentNode disttostart + dist to current) and neighbor disttostart
            foreach (MazeNode nb in unvisitedNeighbors)
            {
                // Distance in case of non-equidistant nodes
                //float distToCurrentNode = Vector3.Distance(currentNode.position, nb.position);
                // Distance in case of equidistant nodes
                float distToCurrentNode = 1;
                // Update neighbor
                distToStart[nb.Identifier] = Mathf.Min((distToStart[currentNode.Identifier] + distToCurrentNode), distToStart[nb.Identifier]);
            }

            // Set current node to visisted
            isVisited[currentNode.Identifier] = true;

            // end reached?
            endFound |= currentNode == endNode;

            // Safety check
            count++;
            if (count > mazeFrame.Nodes.Count)
            {
                throw new System.Exception("Finding shortest path loop did not terminate correctly.");
            }
        }

        // Store shortest path length in maze frame now that we know it
        int shortestPathLength = (int)distToStart[endNode.Identifier];

        // Backtrack along distToStart and label each node as being on the shortest path
        currentNode = endNode;
        onShortestPath[currentNode.Identifier] = true;
        count = 0;
        while (currentNode != startNode)
        {
            // Find neighbor with shortest distance to start
            MazeNode nextNodeOnPath = currentNode;
            foreach (MazeNode nb in currentNode.ConnectedNeighbors)
            {
                if (nb.IsActive && distToStart[nb.Identifier] < distToStart[nextNodeOnPath.Identifier]) // There is ALWAYS a node closer than the current one
                {
                    nextNodeOnPath = nb;
                }
            }

            // Update and continue
            currentNode = nextNodeOnPath;
            onShortestPath[currentNode.Identifier] = true;

            // Safety check
            count++;
            if (count > mazeFrame.Nodes.Count)
            {
                throw new System.Exception("Finding shortest path loop did not terminate correctly.");
            }
        }



        /* Label each node as onLoop/onDeadEnd/notConnectedToPath as follows:
         *
         * 1) Find all nodes that are adjacent to the path
         * 2) For each of these:
         *   3) Create stack to keep unlabeled nodes, and set allLabeled flag
         *   4) While !allLabeled
         *      5) Create list of unlabeled neighbors of current node that are not on the stack
         *      6) If current node has 1+ unlabeled neighbors --> explore
         *         7) Add current node to stack and set an unlabeled neighbor not on the stack as current node
         *      8) If current node has 0 unlabeled neighbors not on the stack --> label and backtrack
         *         9) If current node has only 1 connected neighbor --> onDeadEnd
         *        10) If more than one connected neighbors, and one was onLoop --> onLoop
         *        11) If more than one connected neighbors, and one of them was onShortestPath (or two if at seed node) --> onLoop (loop connection found!)
         *        12) If more than one connected neighbors, and no onLoop/onShortestPath --> onDeadEnd (backtracking from only dead ends)
         *     13) If stack is not empty, pop node and set as current node, go to 5)
         *     14) If stack is empty, all nodes are labeled, set allLabeled to true;
         *
         * 15) Set all junctions by finding all nodes adjacent to onDeadEnd/onLoop, and set them likewise.
         * 16) Set all unlabeled nodes to notConnectedToPath
         */

        // First, find unlabeled nodes that are connected to the path (1)
        List <MazeNode> unlabeledSeedNode = new List <MazeNode>();

        for (int i = 0; i < mazeFrame.Nodes.Count; i++)
        {
            if (mazeFrame.Nodes[i].IsActive && !onShortestPath[mazeFrame.Nodes[i].Identifier])
            {
                foreach (MazeNode nb in mazeFrame.Nodes[i].ConnectedNeighbors)
                {
                    if (nb.IsActive && onShortestPath[nb.Identifier])
                    {
                        unlabeledSeedNode.Add(mazeFrame.Nodes[i]); break;
                    }
                }
            }
        }

        // Start the search (2)
        for (int inode = 0; inode < unlabeledSeedNode.Count; inode++)
        {
            // set seed node
            MazeNode seedNode = unlabeledSeedNode[inode];
            if (onDeadEnd[seedNode.Identifier] || onLoop[seedNode.Identifier]) // check whether node was touched from a previous cycle below
            {
                continue;
            }

            // Create stack for to be labeled nodes
            Stack <MazeNode> nodesToLabel = new Stack <MazeNode>(); // FIXME should I initialize with conservative estimate?

            // Search from the starting node until the stack is empty (all are labeled) (4)
            count       = 0;
            currentNode = seedNode;
            bool allLabeled = false;
            while (!allLabeled)
            {
                // Parse current neighbors
                List <MazeNode> unlabeledNeighborsNotOnStack = new List <MazeNode>();
                bool            hasOnShortestPath            = false;
                bool            hasOnLoop         = false;
                int             shortestPathCount = 0;
                foreach (MazeNode nb in currentNode.ConnectedNeighbors)
                {
                    if (nb.IsActive)
                    {
                        if (onShortestPath[nb.Identifier])
                        {
                            shortestPathCount++;
                        }
                        else if (onLoop[nb.Identifier])
                        {
                            hasOnLoop = true;
                        }
                        else if (onDeadEnd[nb.Identifier])
                        {
                        }
                        else
                        {
                            if (!nodesToLabel.Contains(nb))
                            {
                                unlabeledNeighborsNotOnStack.Add(nb);
                            }
                        }
                    }
                }
                // If we're at the seed node, the first onShortestPath node is ignored (it's the hook), otherwise any one is fine
                if (currentNode == seedNode)
                {
                    hasOnShortestPath = shortestPathCount > 1;
                }
                else
                {
                    hasOnShortestPath = shortestPathCount > 0;
                }

                // Move forward or label and backtrack
                // If current node has an unlabeled node not on the stack --> explore (6)
                if (unlabeledNeighborsNotOnStack.Count > 0)
                {
                    // Add current node to stack and set first neighbor not on stack (order doesn't matter) as current node
                    foreach (MazeNode nb in unlabeledNeighborsNotOnStack)
                    {
                        nodesToLabel.Push(currentNode);
                        currentNode = nb;
                        break;
                    }
                }
                else
                // If there are no more unlabeled nodes that are not on the stack, we can label and backtrack
                {
                    int activeConnectedNeighborsCount = 0;
                    foreach (MazeNode nb in currentNode.ConnectedNeighbors)
                    {
                        if (nb.IsActive)
                        {
                            activeConnectedNeighborsCount++;
                        }
                    }
                    if (activeConnectedNeighborsCount <= 1) // easiest case
                    {
                        onDeadEnd[currentNode.Identifier] = true;
                    }
                    else if (hasOnLoop || (hasOnShortestPath && currentNode != seedNode))
                    {
                        onLoop[currentNode.Identifier] = true;
                    }
                    else
                    {
                        onDeadEnd[currentNode.Identifier] = true;
                    }

                    // Backtrack if there are still unlabeled, otherwise end
                    if (nodesToLabel.Count != 0)
                    {
                        currentNode = nodesToLabel.Pop();
                    }
                    else
                    {
                        allLabeled = true;
                    }
                }

                // Safety check
                count++;
                if (count > mazeFrame.Nodes.Count * 2)
                {
                    throw new System.Exception("Labeling nodes loop did not terminate correctly.");
                }
            }
        }

        // Set junctions
        // onDeadEnd junctions
        List <MazeNode> nodesNeighboringOnDeadEnd = new List <MazeNode>();
        List <MazeNode> nodesNeighboringOnLoop    = new List <MazeNode>();

        for (int i = 0; i < mazeFrame.Nodes.Count; i++)
        {
            if (!mazeFrame.Nodes[i].IsActive)
            {
                continue;
            }
            // Every node neighboring an onDeadEnd is a junction for a dead ending path
            foreach (MazeNode nb in mazeFrame.Nodes[i].ConnectedNeighbors)
            {
                if (nb.IsActive && onDeadEnd[nb.Identifier])
                {
                    nodesNeighboringOnDeadEnd.Add(mazeFrame.Nodes[i]); break;
                }
            }

            // Only nodes that are onShortestPath neighboring an onLoop can be an onLoop junction
            if (onShortestPath[mazeFrame.Nodes[i].Identifier])
            {
                foreach (MazeNode nb in mazeFrame.Nodes[i].ConnectedNeighbors)
                {
                    if (nb.IsActive && onLoop[nb.Identifier])
                    {
                        nodesNeighboringOnLoop.Add(mazeFrame.Nodes[i]); break;
                    }
                }
            }
        }
        foreach (MazeNode nd in nodesNeighboringOnDeadEnd)
        {
            onDeadEnd[nd.Identifier] = true;
        }
        foreach (MazeNode nd in nodesNeighboringOnLoop)
        {
            onLoop[nd.Identifier] = true;
        }

        // Find remaining nodes, and label them as notConnectedToPath
        foreach (MazeNode nd in mazeFrame.Nodes)
        {
            if (nd.IsActive && !onShortestPath[nd.Identifier] && !onLoop[nd.Identifier] && !onDeadEnd[nd.Identifier])
            {
                notConnectedToPath[nd.Identifier] = true;
            }
        }

        // Assign elements to maze frame
        int  shortestPathInd = 0;
        bool indNotFound     = true;

        while (indNotFound)
        {
            if (mazeFrame.ShortestPathInd.Contains(shortestPathInd))
            {
                shortestPathInd++;
            }
            else
            {
                indNotFound = false;
            }
        }
        mazeFrame.SetShortestPathLength(shortestPathLength, shortestPathInd);
        mazeFrame.SetOnShortestPath(onShortestPath, shortestPathInd);
        mazeFrame.SetOnDeadEnd(onDeadEnd);
        mazeFrame.SetOnLoop(onLoop);
        mazeFrame.SetNotConnectedToPath(notConnectedToPath);
    }