context: // Copyright 2009 Frank van Dijk
  // This file is part of Taps.
  //
  // Taps is free software: you can redistribute it and/or modify it
  // under the terms of the GNU General Public License as published by
  // the Free Software Foundation, either version 3 of the License, or
  // (at your option) any later version.
  // 
  // Taps is distributed in the hope that it will be useful, but WITHOUT
  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  // or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
  // License for more details.
  // 
  // You should have received a copy of the GNU General Public License
  // along with Taps.  If not, see <http://www.gnu.org/licenses/>.
  //
  // You are granted an "additional permission" (as defined by section 7
  // of the GPL) regarding the use of this software in automated test
  // scripts; see the COPYING.EXCEPTION file for details.
  
  using System;
  using System.IO;
  using System.Collections;
  using System.Collections.Specialized;
  using System.Collections.Generic;
  using System.Globalization;
  using System.Text.RegularExpressions;
  using System.Reflection;
  using System.Threading;
  using System.Diagnostics;
  using System.Linq;
  
  namespace Taps {
  
      class CommentDictionaries {
          public OrderedDictionary Dic=new OrderedDictionary();
          public OrderedDictionary Ext=null;
  
          public void Add(string key,object value) {
              Dic.Add(key,value);
          }
  
          public object this[string key] {
              get {return Dic[key];}
              set {Dic[key]=value;}
          }
          
          public void AddExtension(string key,object value) {
              if(Ext==null) Ext=new OrderedDictionary();
              Ext.Add(key,value);
          }
          
      }
  
      public class WithInvariantCulture: IDisposable {
  
          CultureInfo Saved;
          
          public WithInvariantCulture() {
              Saved=Thread.CurrentThread.CurrentCulture;
              Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
          }
  
          public void Dispose() {
              Thread.CurrentThread.CurrentCulture=Saved;
          }
      }
  
      public abstract class CommentWriter {
  
          protected IDictionary<Type,bool> BBD;
          protected TextWriter Tw;
  
          public CommentWriter(TextWriter tw,IDictionary<Type,bool> bbd) {
              BBD=bbd;
              Tw=tw;
          }
  
          public abstract void WriteComment(int nr,OrderedDictionary dic,List<PathEntry> path,string name);
  
          protected string GetAnnotation(int idx,YNode node,List<PathEntry> path,int side,int rootdepth) {
              int depth=node.Depth;
              if(path!=null && depth<path.Count) {
                  PathEntry p=path[depth];
                  if(p.Idx==idx) {
                      if(p.Counts[0]!=p.Counts[1]) {
                          string notcoll=node.IsLeaf?" (not even a countable)":"";
                          return string.Format("COUNT MISMATCH {0} vs {1}{2}",p.Counts[0^side],p.Counts[1^side],notcoll);
                      } else {
                          // saying 'here' at the root is stating the obvious 
                          if(depth!=rootdepth) {
                              return "HERE";
                          }
                      }
                  }
              }
              return null;
          }
  
          protected static string MkCoords(OrderedDictionary dic) {
              string s=dic["line"].ToString();
              object t=dic["column"];
              if(t!=null) {
                  return string.Concat(s,",",t.ToString());
              }
              return s;
          }
  
      }
  
      public class YAMLCommentWriter: CommentWriter {
  
          public YAMLCommentWriter(TextWriter tw,IDictionary<Type,bool> bbd): base(tw,bbd) {
          }
  
          public override void WriteComment(int nr,OrderedDictionary dic,List<PathEntry> path,string name) {
              if(path!=null) path.Insert(0,new PathEntry(-1));
              var yw=new YAMLWriter(Tw,2,TAP.HorizontalThreshold,BBD);
              int rootidx=-1;
              int side=0;
              yw.Annotate=(i,n)=>{
                  int depth=n.Depth;
                  if(depth==1)  {
                      var k=n.Key as string;
                      if(k!=null) {
                          if(k=="actual") {
                              side=0;
                              rootidx=i;
                          } else if(k=="expected") {
                              side=1;
                              rootidx=i;
                          }
                      } else {
                          rootidx=-1;
                      }
                  }
                  if(rootidx!=-1) {
                      return GetAnnotation(i-rootidx,n,path,side,1);
                  }
                  return null;
              };
              yw.Write(dic);
          }
  
      }
  
      class TerseCommentWriter: CommentWriter {
  
          public TerseCommentWriter(TextWriter tw,IDictionary<Type,bool> bbd): base(tw,bbd) {
          }
  
          void WriteBaseComment(int nr,OrderedDictionary dic) {
              Tw.Write("#   failed test {0} (",nr);
              var filename=(string)dic["file"];
              if(filename!=null) {
                  Tw.Write("{0} at pos {1} ",filename,MkCoords(dic));
              }
              Tw.WriteLine("in {0})",dic["method"]);
          }
  
          static string SafeToString(object s) {
              if(s==null) return "(null)";
              return s.ToString();
          }
  
          static string ValOrNull(OrderedDictionary dic,string key) {
              return SafeToString(dic[key]);
          }
  
          void WriteCmpComment(OrderedDictionary dic,string cmp) {
              Tw.WriteLine("#   '{0}'",ValOrNull(dic,"actual"));
              Tw.WriteLine("#   {0}",cmp);
              Tw.WriteLine("#   '{0}'",ValOrNull(dic,"expected"));
          }
  
          void WriteIsVal(OrderedDictionary dic,string label,string key,List<PathEntry> path,int side) {
              var val=dic[key];
              if(path==null || YAMLWriter.IsLeafType(val)) {
                  Tw.WriteLine("#{0,11}: '{1}'",label,SafeToString(val));
              } else {
                  Tw.WriteLine("#   {0}:",label);
                  var yw=new YAMLWriter(Tw,4,TAP.HorizontalThreshold,BBD);
                  yw.Annotate=(i,n)=> {
                      return GetAnnotation(i,n,path,side,0);
                  };
                  yw.Write(val);
              }
          }
  
          void WriteIsComment(OrderedDictionary dic,List<PathEntry> path) {
              WriteIsVal(dic,"got","actual",path,0);
              WriteIsVal(dic,"expected","expected",path,1);
          }
  
          public override void WriteComment(int nr,OrderedDictionary dic,List<PathEntry> path,string name) {
              WriteBaseComment(nr,dic);
              var ext=(OrderedDictionary)dic["extensions"];
              if(ext!=null) {
                  if(ext.Contains("cmp")) {
                      WriteCmpComment(dic,(string)ext["cmp"]);
                  }
              } else if(dic.Contains("actual")) {
                  WriteIsComment(dic,path);
              }
              var msg=(string)dic["message"];
              if(msg!=null) {
                  Tw.WriteLine("#   "+msg);
              }
              var backtrace=(string)dic["backtrace"];
              if(backtrace!=null) {
                  string estring=Regex.Replace(backtrace,@"^","# ",RegexOptions.Multiline);
                  Tw.WriteLine(estring);
  
              }
          }
  
      }
  
      class VSCommentWriter: CommentWriter {
  
          public VSCommentWriter(TextWriter tw,IDictionary<Type,bool> bbd): base(tw,bbd) {
          }
  
          void WriteBaseComment(int nr,OrderedDictionary dic,string name) {
              var filename=(string)dic["file"];
  
gt:             if(name==null) name=""; else name+=". ";