context: using System;
  using System.Collections.Generic;
  using System.Text;
  using System.Web;
  using Algorithm.Diff;
  using System.Collections;
  
  namespace WikiFunctions
  {
      /// <summary>
      /// This class renders MediaWiki-like HTML diffs
      /// </summary>
      public class WikiDiff
      {
          string[] LeftLines;
          string[] RightLines;
          Diff diff;
          StringBuilder Result;
          int ContextLines;
  
          /// <summary>
          /// Renders diff
          /// </summary>
          /// <param name="leftText">Earlier version of the text</param>
          /// <param name="rightText">Later version of the text</param>
          /// <param name="contextLines">Number of unchanged lines to show around changed ones</param>
          /// <returns>HTML diff</returns>
          public string GetDiff(string leftText, string rightText, int contextLines)
          {
              Result = new StringBuilder(500000);
              LeftLines = leftText.Split(new [] { "\r\n" }, StringSplitOptions.None);
              RightLines = rightText.Split(new [] { "\r\n" }, StringSplitOptions.None);
              ContextLines = contextLines;
  
              diff = new Diff(LeftLines, RightLines, true, true);
              foreach (Diff.Hunk h in diff)
              {
                  if (h.Same) RenderContext(h);
                  else RenderDifference(h);
  
              }
              return Result.ToString();
          }
  
          #region High-level diff stuff
          private void RenderDifference(Diff.Hunk hunk)
          {
              Range left = hunk.Left;
              Range right = hunk.Right;
  
              if (right.Start == 0) ContextHeader(0, 0);
              int changes = Math.Min(left.Count, right.Count);
              for (int i = 0; i < changes; i++)
              {
                  LineChanged(left.Start + i, right.Start + i);
              }
              if (left.Count > right.Count)
                  for (int i = changes; i < left.Count; i++)
                      LineDeleted(left.Start + i, right.Start + changes);
              else if (left.Count < right.Count)
                  for (int i = changes; i < right.Count; i++)
                      LineAdded(right.Start + i);
          }
  
          private void RenderContext(Diff.Hunk hunk)
          {
              Range left = hunk.Left;
              Range right = hunk.Right;
              int displayed = 0;
  
              if (Result.Length > 0) // not the first hunk, adding context for previous change
              {
                  displayed = Math.Min(ContextLines, right.Count);
                  for (int i = 0; i < displayed; i++) ContextLine(right.Start + i);
              }
  
              int toDisplay = Math.Min(right.Count - displayed, ContextLines);
              if ((left.End < LeftLines.Length - 1 || right.End < RightLines.Length - 1) && toDisplay > 0)
              // not the last hunk, adding context for next change
              {
                  if (right.Count > displayed + toDisplay) ContextHeader(left.End - toDisplay + 1, right.End - toDisplay + 1);
                  for (int i = 0; i < toDisplay; i++) ContextLine(right.End - toDisplay + i + 1);
              }
          }
  
          private void LineChanged(int leftLine, int rightLine)
          {
              // some kind of glitch with the diff engine
              if (LeftLines[leftLine] == RightLines[rightLine])
              {
                  ContextLine(rightLine);
                  return;
              }
  
              StringBuilder left = new StringBuilder();
              StringBuilder right = new StringBuilder();
  
              List<Word> leftList = Word.SplitString(LeftLines[leftLine]);
              List<Word> rightList = Word.SplitString(RightLines[rightLine]);
  
              diff = new Diff(leftList, rightList, Word.Comparer, Word.Comparer);
  
              foreach (Diff.Hunk h in diff)
              {
                  if (h.Same)
                  {
                      for (int i = 0; i < h.Left.Count; i++)
                      {
                          WhitespaceDiff(left, rightList[h.Right.Start + i], leftList[h.Left.Start + i]);
                          WhitespaceDiff(right, leftList[h.Left.Start + i], rightList[h.Right.Start + i]);
                      }
                  }
                  else
                  {
                      WordDiff(left, h.Left, h.Right, leftList, rightList);
  
                      WordDiff(right, h.Right, h.Left, rightList, leftList);
                  }
              }
  
              Result.AppendFormat(@"<tr onclick='window.external.GoTo({1})' ondblclick='window.external.UndoChange({0},{1})'>
    <td>-</td>
    <td class='diff-deletedline'>", leftLine, rightLine);
              Result.Append(left);
              Result.Append(@"  </td>
    <td>+</td>
    <td class='diff-addedline'>");
              Result.Append(right);
              Result.Append(@"  </td>
  		</tr>");
          }
  
          private static void WordDiff(StringBuilder res, Range range, Range otherRange, IList<Word> words, IList<Word> otherWords)
          {
              bool open = false;
  
              for (int i = 0; i < range.Count; i++)
              {
                  if (i >= otherRange.Count || words[range.Start + i].ToString() != otherWords[otherRange.Start + i].ToString())
                  {
                      if (!open) res.Append("<span class='diffchange'>");
                      open = true;
                  }
                  else
                  {
                      if (open) res.Append("</span>");
                      open = false;
                  }
                  res.Append(HttpUtility.HtmlEncode(words[range.Start + i].ToString()));
              }
  
              if (open) res.Append("</span>");
          }
  
          private static void WhitespaceDiff(StringBuilder res, Word left, Word right)
          {
              if (left.Whitespace == right.Whitespace) res.Append(HttpUtility.HtmlEncode(right.ToString()));
              else
              {
                  res.Append(HttpUtility.HtmlEncode(right.TheWord));
                  char[] leftChars = left.Whitespace.ToCharArray();
                  char[] rightChars = right.Whitespace.ToCharArray();
  
                  Diff diff = new Diff(leftChars, rightChars, Word.Comparer, Word.Comparer);
                  foreach (Diff.Hunk h in diff)
                  {
                      if (h.Same)
                          res.Append(rightChars, h.Right.Start, h.Right.Count);
                      else
                      {
                          res.Append("<span class='diffchange'>");
                          res.Append('\x00A0', h.Right.Count); // replace spaces with NBSPs to make 'em visible
                          res.Append("</span>");
                      }
                  }
              }
          }
          #endregion
  
          #region Visualisation primitives
          /// <summary>
          /// Renders a context row
          /// </summary>
          /// <param name="line">Number of line in the RIGHT text</param>
          void ContextLine(int line)
          {
              string html = HttpUtility.HtmlEncode(RightLines[line]);
              Result.AppendFormat(@"<tr onclick='window.external.GoTo({0});'>
    <td class='diff-marker'> </td>
    <td class='diff-context'>", line);
              Result.Append(html);
              Result.Append(@"</td>
    <td class='diff-marker'> </td>
    <td class='diff-context'>");
              Result.Append(html);
              Result.Append(@"</td>
  </tr>");
          }
  
          void LineDeleted(int left, int right)
          {
              Result.AppendFormat(@"<tr>
    <td>-</td>
    <td class='diff-deletedline' onclick='window.external.GoTo({1})' ondblclick='window.external.UndoDeletion({0}, {1})'>",
                  left, right);
              Result.Append(HttpUtility.HtmlEncode(LeftLines[left]));
              Result.Append(@"  </td>
    <td> </td>
    <td> </td>
  </tr>");
          }
  
          void LineAdded(int line)
          {
              Result.AppendFormat(@"<tr>
    <td> </td>
    <td> </td>
    <td>+</td>
    <td class='diff-addedline' onclick='window.external.GoTo({0})' ondblclick='window.external.UndoAddition({0})'>", line);
              Result.Append(HttpUtility.HtmlEncode(RightLines[line]));
              Result.Append(@"  </td>
  </tr>");
          }
  
          void ContextHeader(int left, int right)
          {
              Result.AppendFormat(@"<tr onclick='window.external.GoTo({2})'>
    <td colspan='2' class='diff-lineno'>Line {0}:</td>
    <td colspan='2' class='diff-lineno'>Line {1}:</td>
  </tr>", left + 1, right + 1, right);
          }
          #endregion
  
          #region Undo
          public string UndoChange(int left, int right)
          {
              RightLines[right] = LeftLines[left];
  
              return string.Join("\r\n", RightLines);
          }
  
          public string UndoAddition(int right)
          {
              StringBuilder s = new StringBuilder();
  
  
gt:             for (int i = 0; i < RightLines.Length; i++)