/// <summary>
        /// Gets a ComplexityData object for the given source file, or creates it if it does not exist.
        /// </summary>
        /// <param name="SourceFile">The source file to check for</param>
        /// <param name="FileToComplexityData">Mapping of source file to complexity data</param>
        /// <returns>The complexity data object for the given file</returns>
        static ComplexityData FindOrAddComplexityData(SourceFile SourceFile, Dictionary <SourceFile, ComplexityData> FileToComplexityData)
        {
            ComplexityData ReportData;

            if (!FileToComplexityData.TryGetValue(SourceFile, out ReportData))
            {
                // Add the new data
                ReportData = new ComplexityData();
                FileToComplexityData.Add(SourceFile, ReportData);
            }
            return(ReportData);
        }
        /// <summary>
        /// Generate a report showing the number of preprocessed lines in the selected files
        /// </summary>
        /// <param name="Files">The files to include in the report</param>
        /// <param name="ReportFileLocation">Output file for the report</param>
        /// <param name="Log">Writer for log output</param>
        public static void Generate(FileReference ReportFileLocation, IEnumerable <SourceFile> Files, LineBasedTextWriter Log)
        {
            Log.WriteLine("Writing {0}...", ReportFileLocation.FullName);

            // Build a list of all the files in the report
            SourceFile[] AllFiles = Files.SelectMany(x => FindIncludedFiles(x)).Distinct().ToArray();

            // Find a map of file to the number of preprocessed lines in it
            Dictionary <SourceFile, ComplexityData> FileToReportData = new Dictionary <SourceFile, ComplexityData>();

            foreach (SourceFile File in AllFiles)
            {
                // Create the complexity data for this file
                ComplexityData ReportData = FindOrAddComplexityData(File, FileToReportData);
                Debug.Assert(ReportData.NumPreprocessedLines == 0);

                // Calculate the preprocessed line count, and update each file it includes with this file
                foreach (SourceFile IncludedFile in FindIncludedFiles(File))
                {
                    ComplexityData IncludedFileReportData = FindOrAddComplexityData(IncludedFile, FileToReportData);
                    IncludedFileReportData.IncludedBy.Add(File);
                    ReportData.NumPreprocessedLines += IncludedFile.Text.Lines.Length;
                }

                // Add this file to each file it directly includes
                foreach (PreprocessorMarkup Markup in File.Markup)
                {
                    if (Markup.Type == PreprocessorMarkupType.Include && Markup.IsActive)
                    {
                        foreach (SourceFile IncludedFile in Markup.OutputIncludedFiles)
                        {
                            ComplexityData IncludedFileReportData = FindOrAddComplexityData(IncludedFile, FileToReportData);
                            IncludedFileReportData.DirectlyIncludedBy.Add(File);
                        }
                    }
                }
            }

            // Write out a CSV report containing the list of files and their line counts
            using (StreamWriter Writer = new StreamWriter(ReportFileLocation.FullName))
            {
                Writer.WriteLine("File,Line Count,Num Indirect Includes,Num Direct Includes,Direct Includes");
                foreach (KeyValuePair <SourceFile, ComplexityData> Pair in FileToReportData.OrderByDescending(x => x.Value.NumPreprocessedLines))
                {
                    string IncludedByList = String.Join(", ", Pair.Value.DirectlyIncludedBy.Select(x => GetDisplayName(x)).OrderBy(x => x));
                    Writer.WriteLine("{0},{1},{2},{3},\"{4}\"", GetDisplayName(Pair.Key), Pair.Value.NumPreprocessedLines, Pair.Value.IncludedBy.Count, Pair.Value.DirectlyIncludedBy.Count, IncludedByList);
                }
            }
        }