//
        // We should have the same number of reviews as we do commits
        //
        private static bool CheckReviewInformation(ReviewState.GetCommitStatsResult commitStats)
        {
            // Check we have the right number of issues
            int commitCount = commitStats.CommitCount[(int)RB_Tools.Shared.Review.Properties.Level.FullReview];

            // If they are not the same, we have an issue
            if (commitCount != commitStats.Reviews.Length)
            {
                s_errorMessage = @"Unable to find the reviews listed in the commits, which means the Reviewboard server does not match the the one authenticated against";
                return(false);
            }

            // We're OK
            return(true);
        }
        //
        // Updates the commit stats for the report
        //
        private static string UpdateCommitStatistics(string outputContent, ReviewState.GetCommitStatsResult commitStats)
        {
            // Get the total commit count
            int totalCommits = commitStats.UnknownCommits;

            foreach (int commitNumber in commitStats.CommitCount)
            {
                totalCommits += commitNumber;
            }

            // Update our values
            outputContent = outputContent.Replace(@"___COMMIT_COUNT_TOTAL___", totalCommits.ToString(NumberFormat));

            var reviewedCount = GetPercentageBreakdown(commitStats.CommitCount[(int)RB_Tools.Shared.Review.Properties.Level.FullReview], totalCommits);

            outputContent = outputContent.Replace(@"___COMMIT_COUNT_REVIEWED___", reviewedCount.First);
            outputContent = outputContent.Replace(@"___COMMIT_COUNT_REVIEWED_PERCENTAGE___", reviewedCount.Second);

            var laterCount = GetPercentageBreakdown(commitStats.CommitCount[(int)RB_Tools.Shared.Review.Properties.Level.PendingReview], totalCommits);

            outputContent = outputContent.Replace(@"___COMMIT_COUNT_LATER___", laterCount.First);
            outputContent = outputContent.Replace(@"___COMMIT_COUNT_LATER_PERCENTAGE___", laterCount.Second);

            var noCount = GetPercentageBreakdown(commitStats.CommitCount[(int)RB_Tools.Shared.Review.Properties.Level.NoReview], totalCommits);

            outputContent = outputContent.Replace(@"___COMMIT_COUNT_NO_REVIEW___", noCount.First);
            outputContent = outputContent.Replace(@"___COMMIT_COUNT_NO_REVIEW_PERCENTAGE___", noCount.Second);

            var assetCount = GetPercentageBreakdown(commitStats.CommitCount[(int)RB_Tools.Shared.Review.Properties.Level.AssetCommit], totalCommits);

            outputContent = outputContent.Replace(@"___COMMIT_COUNT_ASSET_ONLY___", assetCount.First);
            outputContent = outputContent.Replace(@"___COMMIT_COUNT_ASSET_ONLY_PERCENTAGE___", assetCount.Second);

            var unknownCount = GetPercentageBreakdown(commitStats.UnknownCommits, totalCommits);

            outputContent = outputContent.Replace(@"___COMMIT_COUNT_NONE___", unknownCount.First);
            outputContent = outputContent.Replace(@"___COMMIT_COUNT_NONE_PERCENTAGE___", unknownCount.Second);

            int ignoredCommitCount = commitStats.CommitCount[(int)RB_Tools.Shared.Review.Properties.Level.PreviouslyReviewed] + commitStats.CommitCount[(int)RB_Tools.Shared.Review.Properties.Level.VersionChange];
            var ignoredCount       = GetPercentageBreakdown(ignoredCommitCount, totalCommits);

            outputContent = outputContent.Replace(@"___COMMIT_COUNT_IGNORED___", ignoredCount.First);
            outputContent = outputContent.Replace(@"___COMMIT_COUNT_IGNORED_PERCENTAGE___", ignoredCount.Second);

            // Return our new report
            return(outputContent);
        }
        //
        // Gets the state of various commits
        //
        private static ReviewState.GetCommitStatsResult GetCommitStats(SvnLogs.Log[] revisionLogs)
        {
            // Starting to pasring
            s_logger.Log(@"Starting to generate the commit statistics");
            Display.Start(Display.State.ParsingLogs, revisionLogs.Length);

            // Get the stats about these commits
            ReviewState.GetCommitStatsResult results = ReviewState.GetCommitStatistics(revisionLogs, s_logger, (currentCount) =>
            {
                Display.Update(currentCount, revisionLogs.Length);
            });

            // Did we fail?
            if (results == null)
            {
                s_errorMessage = @"Unable to generate the commit stats";
            }

            // Return our results
            s_logger.Log(@"Commit stats generated");
            return(results);
        }
        //
        // Generates the final report
        //
        public static bool Generate(string reportName, RevisionList.Revisions revisions, SvnLogs.Log[] revisionLogs, ReviewState.GetCommitStatsResult commitStats, ReviewState.ReviewStatistics[] reviewStats, JiraState.JiraStatistics jiraStats, Logging logger, TimeSpan reviewTime)
        {
            // Start with the clean template
            string outputContent = Properties.Resources.ReportTemplate;

            try
            {
                // Spin through and update various sections
                outputContent = UpdateOverview(outputContent, reportName, revisionLogs, revisions.Url, reviewTime);
                outputContent = UpdateCommitStatistics(outputContent, commitStats);
                outputContent = UpdateReviewStatistics(outputContent, reviewStats);
                outputContent = UpdateAverageResults(outputContent, reviewStats);
                outputContent = UpdateJiraStatistics(outputContent, jiraStats);
                outputContent = UpdateReviewLists(outputContent, reviewStats);
                outputContent = UpdateJiraLists(outputContent, jiraStats);
                outputContent = UpdateCopyrightSection(outputContent);

                // Display the content
                DisplayReport(outputContent, reportName, revisions.Url);
            }
            catch (Exception e)
            {
                logger.Log("Exception raised when generating report\n\n{0}\n", e.Message);
            }

            // Done
            return(true);
        }
        //
        // Generates the report
        //
        private static bool CreateReviewReport(string reportName, RevisionList.Revisions revisions, SvnLogs.Log[] revisionLogs, ReviewState.GetCommitStatsResult commitStats, ReviewState.ReviewStatistics[] reviewStats, JiraState.JiraStatistics jiraStats, Stopwatch reviewTimer)
        {
            // We're now generating
            s_logger.Log("Starting to generate review report");
            Display.Start(Display.State.CreatingResults);

            // Try and generate the report
            bool generated = Report.Generate(reportName, revisions, revisionLogs, commitStats, reviewStats, jiraStats, s_logger, reviewTimer.Elapsed);

            if (generated == false)
            {
                s_errorMessage = @"Unable to generate the Review Report";
            }

            // Return our results
            return(generated);
        }
        //
        // Start the process of generating the stats
        //
        public static void Start(Form owner, string fileList, string reportName, Logging logger, bool injectPaths, GenerationFinished generationFinished)
        {
            // Track our properties
            s_logger = logger;

            // Kick off the background threads
            BackgroundWorker updateThread = new BackgroundWorker();

            // Does the work of the request
            updateThread.DoWork += (object objectSender, DoWorkEventArgs args) =>
            {
                logger.Log(@"Starting stats generation");
                try
                {
                    // Check if we're authenticated
                    Simple reviewBoardCredentials = CheckRBServerAuthentication(owner);
                    if (reviewBoardCredentials == null)
                    {
                        return;
                    }
                    Simple jiraCredentials = CheckJiraServerAuthentication(owner);
                    if (reviewBoardCredentials == null)
                    {
                        return;
                    }

                    // Get the list of paths to review
                    string[] pathsToReview = ParseFileList(fileList, injectPaths);
                    if (pathsToReview == null)
                    {
                        return;
                    }

                    // Get the revision list for each path
                    RevisionList.Revisions[] revisionLists = RequestRevisionLists(pathsToReview);
                    if (revisionLists == null)
                    {
                        return;
                    }

                    // Spin through each revision list and do the work for each path selected
                    foreach (RevisionList.Revisions thisRevisionList in revisionLists)
                    {
                        // Identify which path we'll go through
                        s_logger.Log(@"Generating stats for\n{0}", thisRevisionList.Path);

                        // We need to time this review
                        Stopwatch stopWatch = new Stopwatch();
                        stopWatch.Start();

                        // Get the logs for the given set of revisions
                        SvnLogs.Log[] revisionLogs = GetLogsFromRevisions(thisRevisionList);
                        if (revisionLogs == null)
                        {
                            return;
                        }

                        ReviewState.GetCommitStatsResult commitStats = GetCommitStats(revisionLogs);
                        if (commitStats == null)
                        {
                            return;
                        }

                        // Verify would could get the review information
                        bool reviewInformationValid = CheckReviewInformation(commitStats);
                        if (reviewInformationValid == false)
                        {
                            return;
                        }

                        // Now generate information about the reviews
                        ReviewState.ReviewStatistics[] reviewStats = GetReviewStats(commitStats.Reviews, thisRevisionList.Path, reviewBoardCredentials);
                        if (reviewStats == null)
                        {
                            return;
                        }

                        // Get the Jira information from the commits
                        JiraState.JiraStatistics jiraStats = GetJiraStats(revisionLogs, jiraCredentials);
                        if (jiraStats == null)
                        {
                            return;
                        }

                        // Create the review
                        bool reportGenerated = CreateReviewReport(reportName, thisRevisionList, revisionLogs, commitStats, reviewStats, jiraStats, stopWatch);
                        if (reportGenerated == false)
                        {
                            return;
                        }
                    }
                }
                catch (Exception e)
                {
                    s_logger.Log("Exception raised when generating review stats\n\n{0}\n", e.Message);
                    s_errorMessage = string.Format("Unable to generate review statistics for the given path\n\n{0}", e.Message);
                }
            };

            // Called when it's all been generated
            updateThread.RunWorkerCompleted += (object objectSender, RunWorkerCompletedEventArgs args) =>
            {
                s_logger.Log("Review generation complete");

                // Inform the user something went wrong?
                if (string.IsNullOrEmpty(s_errorMessage) == false)
                {
                    s_logger.Log(@"* Error encountered when running\n{0}\n", s_errorMessage);
                    MessageBox.Show(s_errorMessage, @"Stats Generation Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }

                // Done
                generationFinished();
            };

            // Off it goes
            updateThread.RunWorkerAsync();
        }