context: // ***************************************************************
  // <copyright file="FormatOutput.cs" company="Microsoft">
  //      Copyright (C) Microsoft Corporation.  All rights reserved.
  // </copyright>
  // <summary>
  //      ...
  // </summary>
  // ***************************************************************
  
  namespace Microsoft.Exchange.Data.TextConverters.Internal.Format
  {
      using System;
      using System.Text;
      using System.IO;
      using Data.Internal;
      using Html;
      using Strings = CtsResources.TextConvertersStrings;
  
      internal enum SourceFormat
      {
          Text,
          Html,
          Rtf,
          Enriched,
      }
  
      
      internal abstract class FormatOutput : IDisposable
      {
          private SourceFormat sourceFormat;
          private string comment;
  
          private FormatStore formatStore;
          private FormatNode rootNode;
  
          private OutputStackEntry currentOutputLevel;
          private OutputStackEntry[] outputStack;
          private int outputStackTop;
  
          internal ScratchBuffer scratchBuffer;
          internal ScratchBuffer scratchValueBuffer;
  
          internal PropertyState propertyState = new PropertyState();
  #if DEBUG
          internal TextWriter formatOutputTraceWriter;
  #endif
          
  
          private enum OutputState : byte
          {
              NotStarted,
              Started,
              EndPending,
              Ended
          }
  
          
  
          private struct OutputStackEntry
          {
              public OutputState state;
              public FormatNode node;
              public int index;
              public int childIndex;
              public int propertyUndoLevel;
          }
  
          public virtual bool OutputCodePageSameAsInput => false;
  
          public virtual Encoding OutputEncoding
          {
              set
              {
                  InternalDebug.Assert(false, "this should never happen");
                  throw new InvalidOperationException();
              }
          }
  
          public virtual bool CanAcceptMoreOutput => true;
  
          protected FormatStore FormatStore => formatStore;
          protected SourceFormat SourceFormat => sourceFormat;
          protected string Comment => comment;
  
          protected FormatNode CurrentNode => currentOutputLevel.node;
          protected int CurrentNodeIndex => currentOutputLevel.index;
  
          protected FormatOutput(Stream formatOutputTraceStream)
          {
  #if DEBUG
              if (formatOutputTraceStream != null)
              {
                  formatOutputTraceWriter = new StreamWriter(formatOutputTraceStream);
              }
  #endif
          }
  
          public virtual void Initialize(FormatStore store, SourceFormat sourceFormat, string comment)
          {
              this.sourceFormat = sourceFormat;
              this.comment = comment;
  
              formatStore = store;
  
              Restart(formatStore.RootNode);
          }
  
          public void Restart(FormatNode rootNode)
          {
              outputStackTop = 0;
  
              currentOutputLevel.node = rootNode;
              currentOutputLevel.state = OutputState.NotStarted;
  
              this.rootNode = rootNode;
          }
  
          protected void Restart()
          {
              Restart(rootNode);
          }
  
          
  
          public bool HaveSomethingToFlush()
          {
              return currentOutputLevel.node.CanFlush;
          }
  
          public FlagProperties GetEffectiveFlags()
          {
              return propertyState.GetEffectiveFlags();
          }
  
          public FlagProperties GetDistinctFlags()
          {
              return propertyState.GetDistinctFlags();
          }
  
          public PropertyValue GetEffectiveProperty(PropertyId id)
          {
              return propertyState.GetEffectiveProperty(id);
          }
  
          public PropertyValue GetDistinctProperty(PropertyId id)
          {
              return propertyState.GetDistinctProperty(id);
          }
  
          public void SubtractDefaultContainerPropertiesFromDistinct(FlagProperties flags, Property[] properties)
          {
              propertyState.SubtractDefaultFromDistinct(flags, properties);
          }
  
          
  
          public virtual bool Flush()
          {
              while (CanAcceptMoreOutput && currentOutputLevel.state != OutputState.Ended)
              {
                  if (currentOutputLevel.state == OutputState.NotStarted)
                  {
                      if (StartCurrentLevel())
                      {
                          PushFirstChild();
                      }
                      else
                      {
                          
                          PopPushNextSibling();
                      }
                  }
                  else if (currentOutputLevel.state == OutputState.Started)
                  {
                      if (ContinueCurrentLevel())
                      {
                          currentOutputLevel.state = OutputState.EndPending;
                      }
                  }
                  else 
                  {
                      InternalDebug.Assert(currentOutputLevel.state == OutputState.EndPending);
  
                      EndCurrentLevel();
  
                      currentOutputLevel.state = OutputState.Ended;
  
                      if (outputStackTop != 0)
                      {
                          
                          PopPushNextSibling();
                      }
                  }
              }
  
              InternalDebug.Assert(currentOutputLevel.state != OutputState.Ended || outputStackTop == 0);
              return currentOutputLevel.state == OutputState.Ended;
          }
  
          
  
          public void OutputFragment(FormatNode fragmentNode)
          {
              Restart(fragmentNode);
              FlushFragment();
          }
  
          
  
          public void OutputFragment(FormatNode beginNode, uint beginTextPosition, FormatNode endNode, uint endTextPosition)
          {
              Restart(rootNode);
  
              var node = beginNode;
              var countStartLevels = 0;
  
              
  
              while (node != rootNode)
              {
                  countStartLevels ++;
                  node = node.Parent;
              }
  
              if (outputStack == null)
              {
                  InternalDebug.Assert(outputStackTop == 0 && formatStore != null);
  
                  outputStack = new OutputStackEntry[Math.Max(32, countStartLevels)];
              }
              else if (outputStack.Length < countStartLevels)
              {
                  if (outputStackTop >= HtmlSupport.HtmlNestingLimit)
                  {
                      throw new TextConvertersException(Strings.InputDocumentTooComplex);
                  }
  
                  outputStack = new OutputStackEntry[Math.Max(outputStack.Length * 2, countStartLevels)];
              }
  
              
  
              node = beginNode;
              var level = countStartLevels - 1;
              while (node != rootNode)
              {
                  outputStack[level--].node = node;
                  node = node.Parent;
              }
              InternalDebug.Assert(level == -1);
  
              
  
              for (level = 0; level < countStartLevels; level++)
              {
                  if (!StartCurrentLevel())
                  {
                      PopPushNextSibling();
                      break;
                  }
  
                  currentOutputLevel.state = OutputState.Started;
  
                  Push(outputStack[level].node);
              }
  
              var endReached = false;
  
              while (currentOutputLevel.state != OutputState.Ended)
              {
                  if (currentOutputLevel.state == OutputState.NotStarted)
                  {
                      if (StartCurrentLevel())
                      {
                          PushFirstChild();
                      }
                      else
                      {
                          
                          PopPushNextSibling();
                      }
                  }
                  else if (currentOutputLevel.state == OutputState.Started)
                  {
                      InternalDebug.Assert(currentOutputLevel.node.NodeType == FormatContainerType.Text);
  
                      var nodeBeginTextPosition = currentOutputLevel.node == beginNode ? beginTextPosition : currentOutputLevel.node.BeginTextPosition;
                      var nodeEndTextPosition = currentOutputLevel.node == endNode ? endTextPosition : currentOutputLevel.node.EndTextPosition;
  
                      if (nodeBeginTextPosition <= nodeEndTextPosition)
                      {
                          ContinueText(nodeBeginTextPosition, nodeEndTextPosition);
                      }
  
                      currentOutputLevel.state = OutputState.EndPending;
                  }
                  else 
                  {
                      InternalDebug.Assert(currentOutputLevel.state == OutputState.EndPending);
  
                      EndCurrentLevel();
  
                      currentOutputLevel.state = OutputState.Ended;
  
                      if (outputStackTop != 0)
                      {
                          if (!endReached &&
                              currentOutputLevel.node != endNode &&
                              (currentOutputLevel.node.NextSibling.IsNull ||
                              currentOutputLevel.node.NextSibling != endNode ||
                              (currentOutputLevel.node.NextSibling.NodeType == FormatContainerType.Text &&
                                  currentOutputLevel.node.NextSibling.BeginTextPosition < endTextPosition)))
                          {
                              
                              PopPushNextSibling();
                          }
                          else
                          {
                              Pop();
  
                              
  
                              currentOutputLevel.state = OutputState.EndPending;
  
                              endReached = true;
                          }
                      }
                  }
              }
  
              InternalDebug.Assert(outputStackTop == 0);
              InternalDebug.Assert(propertyState.UndoStackTop == 0);
          }
  
          
  
          private void FlushFragment()
          {
              
              
              
  
              while (currentOutputLevel.state != OutputState.Ended)
              {
                  if (currentOutputLevel.state == OutputState.NotStarted)
                  {
                      if (StartCurrentLevel())
                      {
                          PushFirstChild();
                      }
                      else
                      {
                          
                          PopPushNextSibling();
                      }
                  }
                  else if (currentOutputLevel.state == OutputState.Started)
                  {
                      if (ContinueCurrentLevel())
                      {
                          currentOutputLevel.state = OutputState.EndPending;
                      }
                  }
                  else 
                  {
                      InternalDebug.Assert(currentOutputLevel.state == OutputState.EndPending);
  
                      EndCurrentLevel();
  
                      currentOutputLevel.state = OutputState.Ended;
  
                      if (outputStackTop != 0)
                      {
                          
                          PopPushNextSibling();
                      }
                  }
              }
  
              InternalDebug.Assert(outputStackTop == 0);
              InternalDebug.Assert(propertyState.UndoStackTop == 0);
          }
  
          
  
          private bool StartCurrentLevel()
          {
              switch (currentOutputLevel.node.NodeType)
              {
                  case FormatContainerType.Root:
  
                          return StartRoot();
  
                  case FormatContainerType.Document:
  
                          return StartDocument();
  
                  case FormatContainerType.Fragment:
  
                          return StartFragment();
  
                  case FormatContainerType.BaseFont:
  
                          StartEndBaseFont();
                          
                          return false;
  
                  case FormatContainerType.Block:
  
                          return StartBlock();
  
                  case FormatContainerType.BlockQuote:
  
                          return StartBlockQuote();
  
                  case FormatContainerType.TableContainer:
  
                          return StartTableContainer();
  
                  case FormatContainerType.TableDefinition:
  
                          return StartTableDefinition();
  
                  case FormatContainerType.TableColumnGroup:
  
                          return StartTableColumnGroup();
  
                  case FormatContainerType.TableColumn:
  
                          StartEndTableColumn();
                          
                          return false;
  
                  case FormatContainerType.TableCaption:
  
                          return StartTableCaption();
  
                  case FormatContainerType.TableExtraContent:
  
                          return StartTableExtraContent();
  
                  case FormatContainerType.Table:
  
                          return StartTable();
  
                  case FormatContainerType.TableRow:
  
                          return StartTableRow();
  
                  case FormatContainerType.TableCell:
  
                          return StartTableCell();
  
                  case FormatContainerType.List:
  
                          return StartList();
  
                  case FormatContainerType.ListItem:
  
                          return StartListItem();
  
                  case FormatContainerType.HyperLink:
  
                          return StartHyperLink();
  
                  case FormatContainerType.Bookmark:
  
                          return StartBookmark();
  
                  case FormatContainerType.Image:
  
                          StartEndImage();
                          
                          return false;
  
                  case FormatContainerType.HorizontalLine:
  
                          StartEndHorizontalLine();
                          
                          return false;
  
                  case FormatContainerType.Inline:
  
                          return StartInline();
  
                  case FormatContainerType.Map:
  
                          return StartMap();
  
                  case FormatContainerType.Area:
  
                          StartEndArea();
                          
                          return false;
  
                  case FormatContainerType.Form:
  
                          return StartForm();
  
                  case FormatContainerType.FieldSet:
  
                          return StartFieldSet();
  
                  case FormatContainerType.Label:
  
                          return StartLabel();
  
                  case FormatContainerType.Input:
  
                          return StartInput();
  
                  case FormatContainerType.Button:
  
                          return StartButton();
  
                  case FormatContainerType.Legend:
  
                          return StartLegend();
  
                  case FormatContainerType.TextArea:
  
                          return StartTextArea();
  
                  case FormatContainerType.Select:
  
                          return StartSelect();
  
                  case FormatContainerType.OptionGroup:
  
                          return StartOptionGroup();
  
                  case FormatContainerType.Option:
  
                          return StartOption();
  
                  case FormatContainerType.Text:
  
                          
                          
                          return StartText();
              }
  
              
              InternalDebug.Assert(false);
              return true;
          }
  
          
  
          private bool ContinueCurrentLevel()
          {
              InternalDebug.Assert(currentOutputLevel.node.NodeType == FormatContainerType.Text);
  
              
  
              return ContinueText(currentOutputLevel.node.BeginTextPosition, currentOutputLevel.node.EndTextPosition);
          }
  
          
  
          private void EndCurrentLevel()
          {
              switch (currentOutputLevel.node.NodeType)
              {
  #if DEBUG
                  default:
                  case FormatContainerType.BaseFont:
                  case FormatContainerType.Image:
                  case FormatContainerType.HorizontalLine:
                  case FormatContainerType.Area:
                  case FormatContainerType.TableColumn:
  
                          InternalDebug.Assert(false);
                          break;
  #endif
                  case FormatContainerType.Root:
  
                          EndRoot();
                          break;
  
                  case FormatContainerType.Document:
  
                          EndDocument();
                          break;
  
                  case FormatContainerType.Fragment:
  
                          EndFragment();
                          break;
  
                  case FormatContainerType.Block:
  
                          EndBlock();
                          break;
  
                  case FormatContainerType.BlockQuote:
  
                          EndBlockQuote();
                          break;
  
                  case FormatContainerType.TableContainer:
  
                          EndTableContainer();
                          break;
  
                  case FormatContainerType.TableDefinition:
  
                          EndTableDefinition();
                          break;
  
                  case FormatContainerType.TableColumnGroup:
  
                          EndTableColumnGroup();
                          break;
  
                  case FormatContainerType.TableCaption:
  
                          EndTableCaption();
                          break;
  
                  case FormatContainerType.TableExtraContent:
  
                          EndTableExtraContent();
                          break;
  
                  case FormatContainerType.Table:
  
                          EndTable();
                          break;
  
                  case FormatContainerType.TableRow:
  
                          EndTableRow();
                          break;
  
                  case FormatContainerType.TableCell:
  
                          EndTableCell();
                          break;
  
                  case FormatContainerType.List:
  
                          EndList();
                          break;
  
                  case FormatContainerType.ListItem:
  
                          EndListItem();
                          break;
  
                  case FormatContainerType.HyperLink:
  
                          EndHyperLink();
                          break;
  
                  case FormatContainerType.Bookmark:
  
                          EndBookmark();
                          break;
  
                  case FormatContainerType.Inline:
  
                          EndInline();
                          break;
  
                  case FormatContainerType.Map:
  
                          EndMap();
                          break;
  
                  case FormatContainerType.Form:
  
                          EndForm();
                          break;
  
                  case FormatContainerType.FieldSet:
  
                          EndFieldSet();
                          break;
  
                  case FormatContainerType.Label:
  
                          EndLabel();
                          break;
  
                  case FormatContainerType.Input:
  
                          EndInput();
                          break;
  
                  case FormatContainerType.Button:
  
                          EndButton();
                          break;
  
                  case FormatContainerType.Legend:
  
                          EndLegend();
                          break;
  
                  case FormatContainerType.TextArea:
  
                          EndTextArea();
                          break;
  
                  case FormatContainerType.Select:
  
                          EndSelect();
                          break;
  
                  case FormatContainerType.OptionGroup:
  
                          EndOptionGroup();
                          break;
  
                  case FormatContainerType.Option:
  
                          EndOption();
                          break;
  
                  case FormatContainerType.Text:
  
                          EndText();
                          break;
              }
          }
  
          
  
          private void PushFirstChild()
          {
              var firstChild = currentOutputLevel.node.FirstChild;
  
              if (!firstChild.IsNull)
              {
                  currentOutputLevel.state = OutputState.Started;
  
                  Push(firstChild);
              }
              else if (currentOutputLevel.node.IsText)
              {
                  
                  
                  currentOutputLevel.state = OutputState.Started;
              }
              else
              {
                  currentOutputLevel.state = OutputState.EndPending;
              }
          }
  
          
  
          private void PopPushNextSibling()
          {
              var nextSibling = currentOutputLevel.node.NextSibling;
  
              Pop();
  
              currentOutputLevel.childIndex++;
  
              if (!nextSibling.IsNull)
              {
                  InternalDebug.Assert(currentOutputLevel.state == OutputState.Started);
  
                  Push(nextSibling);
              }
              else
              {
                  currentOutputLevel.state = OutputState.EndPending;
              }
          }
  
          
  
          private void Push(FormatNode node)
          {
              if (outputStack == null)
              {
                  InternalDebug.Assert(outputStackTop == 0 && formatStore != null);
  
                  outputStack = new OutputStackEntry[32];
              }
              else if (outputStackTop == outputStack.Length)
              {
                  if (outputStackTop >= HtmlSupport.HtmlNestingLimit)
                  {
                      throw new TextConvertersException(Strings.InputDocumentTooComplex);
                  }
  
                  var newOutputStack = new OutputStackEntry[outputStack.Length * 2];
                  Array.Copy(outputStack, 0, newOutputStack, 0, outputStackTop);
                  outputStack = newOutputStack;
              }
  
  #if DEBUG
              if (formatOutputTraceWriter != null)
              {
                  formatOutputTraceWriter.WriteLine("{0}{1} Push {2}", Indent(outputStackTop), outputStackTop, node.NodeType.ToString());
                  
                  formatOutputTraceWriter.Flush();
              }
  #endif
              outputStack[outputStackTop++] = currentOutputLevel;
  
              currentOutputLevel.node = node;
              currentOutputLevel.state = OutputState.NotStarted;
              currentOutputLevel.index = currentOutputLevel.childIndex;
              currentOutputLevel.childIndex = 0;
              currentOutputLevel.propertyUndoLevel = propertyState.ApplyProperties(node.FlagProperties, node.Properties, FormatStoreData.GlobalInheritanceMasks[node.InheritanceMaskIndex].flagProperties, FormatStoreData.GlobalInheritanceMasks[node.InheritanceMaskIndex].propertyMask);
  #if DEBUG
  
gt:             if (formatOutputTraceWriter != null)