/// <summary>
        ///     Generates the diff view for two file revisions.
        /// </summary>
        private string GenerateFileDiffView(
            int baseRevision, int diffRevision, UserSettingsDto settings,
            StreamCombiner baseFile, int baseId, string baseHeader,
            StreamCombiner diffFile, int diffId, string diffHeader,
            StreamCombiner rawDiff, string fileName, string outputFile)
        {
            var baseEncoder = GetEncoderForFile(fileName);
            var diffEncoder = GetEncoderForFile(fileName);

            var baseFileInfo = new DiffFileInfo(baseFile, baseEncoder, baseId, BaseOrDiff.Base);
            var diffFileInfo = new DiffFileInfo(diffFile, diffEncoder, diffId, BaseOrDiff.Diff);

            // Line Stamp format
            var baseScriptIdPrefix = "Base-" + EncodeLinePrefix(baseRevision, diffRevision, baseId, diffId, baseId);
            var diffScriptIdPrefix = "Diff-" + EncodeLinePrefix(baseRevision, diffRevision, baseId, diffId, diffId);
            var edgePrefix = "Edge-" + EncodeLinePrefix(baseRevision, diffRevision, baseId, diffId, diffId);
            var rowGroups = new List<HtmlTableRowGroup>();
            var lastBaseLine = "1";
            var lastDiffLine = "1";

            foreach (var diffItem in DiffItem.EnumerateDifferences(rawDiff))
            {
                var atEnd = diffItem.BaseLineCount == int.MaxValue;

                var baseLines = new List<Line>();
                for (var i = 0; i < diffItem.BaseLineCount && baseFileInfo.MoveNextLine(); ++i)
                    baseLines.Add(new Line
                        {
                            LineNum = baseFileInfo.CurLineNum.ToString(CultureInfo.InvariantCulture),
                            LineText = baseFileInfo.CurLine
                        });

                var diffLines = new List<Line>();
                for (var i = 0; i < diffItem.DiffLineCount && diffFileInfo.MoveNextLine(); ++i)
                    diffLines.Add(new Line
                        {
                            LineNum = diffFileInfo.CurLineNum.ToString(CultureInfo.InvariantCulture),
                            LineText = diffFileInfo.CurLine
                        });

                var baseLinesLength = baseLines.Count();
                var diffLinesLength = diffLines.Count();

                // The end is the only case where the DiffInfo line counts may be incorrect. If there are in fact
                // zero lines then just continue, which should cause the foreach block to end and we'll continue
                // like the DiffItem never existed.
                if (atEnd && diffItem.DiffType == DiffType.Unchanged && baseLinesLength == 0 && diffLinesLength == 0)
                    continue;

                var rowGroup = new HtmlTableRowGroup(diffItem.DiffType);

                for (var i = 0; i < Math.Max(baseLinesLength, diffLinesLength); ++i)
                {
                    var line = new HtmlTableLine();
                    if (i < baseLinesLength)
                    {
                        line.Left = baseLines[i];
                        line.Left.LineText = baseFileInfo.Encoder.EncodeLine(baseLines[i].LineText, int.MaxValue,
                                                                             TabValue);
                        lastBaseLine = line.Left.LineNum;
                    }
                    else
                    {
                        // new diff code - add entry for empty line with old line #
                        var idx = i - baseLinesLength;
                        line.Left = new Line { Id = lastBaseLine + '_' + idx };
                    }

                    if (i < diffLinesLength)
                    {
                        line.Right = diffLines[i];
                        line.Right.LineText = diffFileInfo.Encoder.EncodeLine(diffLines[i].LineText, int.MaxValue,
                                                                             TabValue);
                        lastDiffLine = line.Right.LineNum;
                    }
                    else
                    {
                        // new diff code - add entry for empty line with old line #
                        var idx = i - diffLinesLength;
                        line.Right = new Line { Id = lastDiffLine + '_' + idx };
                    }

                    rowGroup.Lines.Add(line);
                }

                if (diffItem.DiffType == DiffType.Changed)
                {
                    if (settings.diff.intraLineDiff)
                        InterlineDiff(rowGroup, settings.diff.ignoreWhiteSpace);
                }

                rowGroups.Add(rowGroup);
            }

            baseEncoder.Dispose();
            diffEncoder.Dispose();

            var htmlLines = EncodeRowGroups(rowGroups, baseHeader, diffHeader, baseScriptIdPrefix, diffScriptIdPrefix,
                                       edgePrefix);
            if (!string.IsNullOrWhiteSpace(outputFile))
            {
                var folder = Path.GetDirectoryName(outputFile);
                Directory.CreateDirectory(folder);
                File.WriteAllLines(outputFile, htmlLines);
                Log.Info("Generated {0}", outputFile);
                return outputFile;
            }
            else
            {
                return string.Join(Environment.NewLine, htmlLines);
            }
        }
        /// <summary>
        ///     Generates a sequence of DiffItems representing differences in rawDiffStream.
        /// </summary>
        /// <param name="includeUnchangedBlocks">
        ///     Indicates whether to generate DiffItems for unchanged blocks.
        /// </param>
        /// <returns>A DiffItem generator.</returns>
        public static IEnumerable<DiffItem> EnumerateDifferences(StreamCombiner rawDiffStream, bool includeUnchangedBlocks)
        {
            DiffItem prevItem = null;
            DiffItem item = null;
            string line = null;
            do
            {
                line = rawDiffStream == null ? null : rawDiffStream.ReadLine();

                if (line != null && line.StartsWith("<"))
                {
                    ++item.BaseLineCount;
                }
                else if (line != null && line.StartsWith("-"))
                {
                    continue;
                }
                else if (line != null && line.StartsWith(">"))
                {
                    ++item.DiffLineCount;
                }
                else if (line != null && line.Equals("\\ No newline at end of file"))
                {
                    // This is a very annoying perforce thing. But we have to account for it.
                    continue;
                }
                else
                {
                    if (item != null)
                    {
                        if (item.DiffLineCount == 0)
                            item.DiffType = DiffType.Deleted;
                        else if (item.BaseLineCount == 0)
                            item.DiffType = DiffType.Added;
                        else
                            item.DiffType = DiffType.Changed;

                        yield return item;
                        prevItem = item;
                        item = null;
                    }

                    if (line != null)
                    {
                        item = new DiffItem();

                        var m = DiffDecoder.Match(line);
                        if (!m.Success)
                            yield break;

                        item.BaseStartLineNumber = Int32.Parse(m.Groups[1].Value);

                        // 'a' adds AFTER the line, but we do processing once we get to the line.
                        // So we need to really get to the next line.
                        if (m.Groups[3].Value.Equals("a"))
                            item.BaseStartLineNumber += 1;
                    }

                    if (includeUnchangedBlocks)
                    {
                        var unchangedItem = new DiffItem();
                        unchangedItem.DiffType = DiffType.Unchanged;
                        unchangedItem.BaseStartLineNumber =
                            prevItem == null ? 1 : prevItem.BaseStartLineNumber + prevItem.BaseLineCount;
                        unchangedItem.BaseLineCount = item == null
                                                          ? int.MaxValue
                                                          : item.BaseStartLineNumber - unchangedItem.BaseStartLineNumber;
                        unchangedItem.DiffLineCount = unchangedItem.BaseLineCount;

                        if (unchangedItem.BaseLineCount != 0)
                            yield return unchangedItem;
                    }
                }
            } while (line != null);
        }
        /// <summary>
        ///     Computes and displays the diff between two distinct versions. Uses unix "diff" command to actually
        ///     produce the diff. This is relatively slow operation and involves spooling the data into two temp
        ///     files and running an external process.
        /// </summary>
        public string GenerateDiffFile(FileVersion left, FileVersion right, string clName, int baseReviewId, UserSettingsDto settings, bool force = false)
        {
            string outputFile = null;
            if (settings.diff.DefaultSettings)
            {
                outputFile = string.Format(@"{0}\{1}\{2}\{3}.{4}.htm", _diffFolderRoot, clName, left.FileId, left.Id, right.Id);
                if (File.Exists(outputFile))
                {
                    if (!force)
                        return outputFile;
                    File.Delete(outputFile);
                }
            }

            var useLeft = true;
            var useRight = true;
            var ignoreBase = false;

            if (left.Id == right.Id)
            {
                ignoreBase = true;
                var actionType = (SourceControlAction)Enum.ToObject(typeof(SourceControlAction), left.Action);
                switch (actionType)
                {
                    case SourceControlAction.Add:
                    case SourceControlAction.Branch:
                    case SourceControlAction.Edit:
                    case SourceControlAction.Integrate:
                    case SourceControlAction.Rename:
                    default:
                        useLeft = false;
                        break;

                    case SourceControlAction.Delete:
                        useRight = false;
                        break;
                }
            }

            using (var leftFile = useLeft ? SaveToTempFile(left, ignoreBase) : EmptyTempFile())
            using (var rightFile = useRight ? SaveToTempFile(right, ignoreBase) : EmptyTempFile())
            {
                if (leftFile == null || rightFile == null)
                    throw new ApplicationException(string.Format("Could not save temporary file for diffs. change list: {0} left file id: {1} right file id: {2}", clName, left.FileId, right.FileId));

                string stderr = null;
                string result = null;

                if (ConfigurationManager.AppSettings.LookupValue("preDiff", false) && settings.diff.preDiff)
                {
                    PreDiffFile(leftFile.FullName, rightFile.FullName);
                }

                result = DiffFiles(leftFile.FullName, rightFile.FullName, settings.diff.ignoreWhiteSpace, ref stderr);
                if (!string.IsNullOrEmpty(stderr))
                {
                    ErrorOut("Diff failed.");
                    ErrorOut(stderr);

                    return "Error Failed to generate diff. " + stderr;
                }

                using (var leftStream = new StreamCombiner(new StreamReader(leftFile.FullName)))
                using (var rightStream = new StreamCombiner(new StreamReader(rightFile.FullName)))
                using (var rawDiffStream = new StreamCombiner(result))
                {
                    var leftName = string.Format("{0}_{1}", Path.GetFileName(left.ChangeFile.ServerFileName), left.Id);
                    var rightName = string.Format("{0}_{1}", Path.GetFileName(right.ChangeFile.ServerFileName),
                                                  right.Id);

                    if (string.IsNullOrEmpty(outputFile))
                        outputFile = string.Format(@"{0}\{1}\{2}\{3}.{4}.delete.{5}.htm", _diffFolderRoot, clName, left.FileId, left.Id, right.Id, this._random.Next());

                    GenerateFileDiffView(left.ReviewRevision, right.ReviewRevision, settings,
                                                leftStream, left.Id, leftName,
                                                rightStream, right.Id, rightName,
                                                rawDiffStream, clName, outputFile);

                    GenerateDiffInfoFile(clName, left, right, baseReviewId);

                    return outputFile;
                }
            }
        }
 /// <summary>
 ///     Generates a sequence of DiffItems representing differences in rawDiffStream. Includes unchanged blocks.
 /// </summary>
 /// <returns>A DiffItem generator.</returns>
 public static IEnumerable<DiffItem> EnumerateDifferences(StreamCombiner rawDiffStream)
 {
     return EnumerateDifferences(rawDiffStream, true);
 }
 /// <summary>
 ///     Creates a DiffFileInfo for a file being compared.
 /// </summary>
 /// <param name="file">The file stream.</param>
 /// <param name="encoder">The line encoder.</param>
 /// <param name="id">The file ID within the database.</param>
 /// <param name="baseOrDiff">What role the file plays within the comparison.</param>
 public DiffFileInfo(
     StreamCombiner file,
     ILineEncoder encoder,
     int id,
     BaseOrDiff baseOrDiff)
 {
     File = file;
     Encoder = encoder;
     Id = id;
     ScriptId = baseOrDiff.ToString().ToLowerCultureInvariant() + "_" + Id.ToString(CultureInfo.InvariantCulture) + "_";
     //CurLineNum = 0;
     //NextCommentIndex = 0;
     BaseOrDiff = baseOrDiff;
 }