public static void GenerateRangeUsageVisualization(TextWriter writer, PsbReader reader)
        {
            RangeUsageAnalyzer analyzer = new RangeUsageAnalyzer();

            foreach (var node in reader.GenerateNameNodes().Values)
            {
                RegularNameNode regularNode = node as RegularNameNode;
                if (regularNode != null)
                {
                    var regularChildren = regularNode.Children.Values.Where(x => x is RegularNameNode).OrderBy(x => x.Index);
                    if (regularChildren.Count() > 0)
                    {
                        var minIndex = regularChildren.First().Index;
                        var maxIndex = regularChildren.Last().Index;
                        analyzer.AddRange(node.Index, minIndex, maxIndex, false);
                    }

                    var terminator = regularNode.Children.Values.Where(x => x is TerminalNameNode).FirstOrDefault();
                    if (terminator != null)
                    {
                        analyzer.AddRange(node.Index, terminator.Index, terminator.Index, false);
                    }
                }
                else
                {
                    analyzer.AddRange(node.Index, node.Index, node.Index, true);
                }
            }

            analyzer.OrderNodes();
            analyzer.WriteVisualization(writer);
        }
        static void WriteRange(IndentedTextWriter writer, NameNode node)
        {
            RegularNameNode regularNode = node as RegularNameNode;

            if (regularNode != null)
            {
                string line            = $"{node.Index} {(char)node.Character} ";
                var    regularChildren = regularNode.Children.Values.Where(x => x is RegularNameNode).OrderBy(x => x.Index);
                var    terminator      = regularNode.Children.Values.Where(x => x is TerminalNameNode).FirstOrDefault();

                if (terminator != null)
                {
                    line += $"[{terminator.Index}] ";
                }

                if (regularChildren.Count() > 0)
                {
                    var minIndex = regularChildren.First().Index;
                    var maxIndex = regularChildren.Last().Index;
                    if (minIndex == maxIndex)
                    {
                        line += $"<{minIndex}> ";
                    }
                    else
                    {
                        line += $"<{minIndex} {maxIndex}> ";
                    }
                }

                writer.WriteLine(line.Trim());

                ++writer.Indent;
                foreach (var child in regularNode.Children.Values)
                {
                    WriteRange(writer, child);
                }
                --writer.Indent;
            }
        }
            public Dictionary <uint, NameNode> GenerateNodes()
            {
                if (tree == null)
                {
                    throw new InvalidOperationException("Names are stored flat, not in a tree.");
                }
                Dictionary <uint, NameNode> nodes = new Dictionary <uint, NameNode>();

                for (uint i = 0; i < tails.Length; ++i)
                {
                    TerminalNameNode tailNode = new TerminalNameNode();
                    tailNode.Index     = tails[i];
                    tailNode.TailIndex = i;
                    // Quick check to ensure
                    if (valueOffsets[tailNode.Index] != i)
                    {
                        throw new Exception();
                    }
                    tailNode.ParentIndex = tree[tailNode.Index];
                    tailNode.Character   = (byte)(tailNode.Index - valueOffsets[tailNode.ParentIndex]);
                    nodes.Add(tailNode.Index, tailNode);

                    uint parentIndex = tailNode.ParentIndex;
                    while (!nodes.ContainsKey(parentIndex))
                    {
                        RegularNameNode regularNode = new RegularNameNode();
                        regularNode.Index       = parentIndex;
                        regularNode.ParentIndex = tree[regularNode.Index];
                        regularNode.ValueOffset = valueOffsets[regularNode.Index];
                        if (regularNode.Index != 0)
                        {
                            regularNode.Character = (byte)(regularNode.Index - valueOffsets[regularNode.ParentIndex]);
                        }
                        nodes.Add(regularNode.Index, regularNode);
                        parentIndex = regularNode.ParentIndex;
                    }
                }

                // Link it up
                foreach (var node in nodes.Values)
                {
                    if (node.Index != 0)
                    {
                        node.Parent = nodes[node.ParentIndex];
                        var parent = node.Parent as RegularNameNode;
                        parent.Children.Add(node.Character, node);
                    }
                }

                if (nodes.Count == 0)
                {
                    // Create root node if we don't have any strings
                    RegularNameNode regularNode = new RegularNameNode();
                    regularNode.Index       = 0;
                    regularNode.ParentIndex = tree[regularNode.Index];
                    regularNode.ValueOffset = valueOffsets[regularNode.Index];
                    nodes.Add(regularNode.Index, regularNode);
                }

                return(nodes);
            }