// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml;

namespace RazorSyntaxGenerator
{
    internal class SourceWriter : AbstractFileWriter
    {
        private SourceWriter(TextWriter writer, Tree tree)
            : base(writer, tree)
        {
        }

        public static void WriteMain(TextWriter writer, Tree tree) => new SourceWriter(writer, tree).WriteMain();

        public static void WriteInternal(TextWriter writer, Tree tree) => new SourceWriter(writer, tree).WriteInternal();

        public static void WriteSyntax(TextWriter writer, Tree tree) => new SourceWriter(writer, tree).WriteSyntax();

        private void WriteFileHeader()
        {
            WriteLine("// <auto-generated />");
            WriteLine();
            WriteLine("using System;");
            WriteLine("using System.Collections;");
            WriteLine("using System.Collections.Generic;");
            WriteLine("using System.Linq;");
            WriteLine("using System.Threading;");
            WriteLine();
        }

        private void WriteInternal()
        {
            WriteFileHeader();

            WriteLine("namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax");
            WriteLine("{");
            WriteLine();
            WriteGreenTypes();
            WriteGreenVisitors();
            WriteGreenRewriter();
            WriteStaticGreenFactories();
            WriteLine("}");
        }

        private void WriteSyntax()
        {
            WriteFileHeader();

            WriteLine();
            WriteLine("namespace Microsoft.AspNetCore.Razor.Language.Syntax");
            WriteLine("{");
            WriteLine();
            WriteRedTypes();
            WriteLine("}");
        }

        private void WriteMain()
        {
            WriteFileHeader();

            WriteLine();
            WriteLine("namespace Microsoft.AspNetCore.Razor.Language.Syntax");
            WriteLine("{");
            //WriteLine("    using Microsoft.AspNetCore.Razor.Language.Syntax;");
            //WriteLine();
            WriteRedVisitors();
            WriteRedRewriter();
            WriteRedFactories();
            WriteLine("}");
        }

        private void WriteGreenTypes()
        {
            var nodes = Tree.Types.Where(n => !(n is PredefinedNode)).ToList();
            for (int i = 0, n = nodes.Count; i < n; i++)
            {
                var node = nodes[i];
                WriteLine();
                WriteGreenType(node);
            }
        }

        private void WriteGreenType(TreeType node)
        {
            WriteComment(node.TypeComment, "  ");

            if (node is AbstractNode)
            {
                AbstractNode nd = (AbstractNode)node;
                WriteLine("  internal abstract partial class {0} : {1}", node.Name, node.Base == "SyntaxNode" ? "GreenNode" : node.Base);
                WriteLine("  {");

                // ctor with diagnostics and annotations
                WriteLine("    internal {0}(SyntaxKind kind, RazorDiagnostic[] diagnostics, SyntaxAnnotation[] annotations)", node.Name);
                WriteLine("      : base(kind, diagnostics, annotations)");
                WriteLine("    {");
                if (node.Name == "DirectiveTriviaSyntax")
                {
                    WriteLine("      _flags |= NodeFlags.ContainsDirectives;");
                }
                WriteLine("    }");

                // ctor without diagnostics and annotations
                WriteLine("    internal {0}(SyntaxKind kind)", node.Name);
                WriteLine("      : base(kind)");
                WriteLine("    {");
                if (node.Name == "DirectiveTriviaSyntax")
                {
                    WriteLine("      _flags |= NodeFlags.ContainsDirectives;");
                }
                WriteLine("    }");

                /* Remove
                // object reader constructor
                WriteLine();
                WriteLine("    protected {0}(ObjectReader reader)", node.Name);
                WriteLine("       : base(reader)");
                WriteLine("    {");
                if (node.Name == "DirectiveTriviaSyntax")
                {
                    WriteLine("      _flags |= NodeFlags.ContainsDirectives;");
                }
                WriteLine("    }"); */

                var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
                var nodeFields = nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList();

                for (int i = 0, n = nodeFields.Count; i < n; i++)
                {
                    var field = nodeFields[i];
                    if (IsNodeOrNodeList(field.Type))
                    {
                        WriteLine();
                        WriteComment(field.PropertyComment, "    ");

                        if (IsSeparatedNodeList(field.Type) ||
                            IsNodeList(field.Type))
                        {
                            WriteLine("    public abstract {0}{1} {2} {{ get; }}",
                                (IsNew(field) ? "new " : ""), field.Type, field.Name);
                        }
                        else
                        {
                            WriteLine("    public abstract {0}{1} {2} {{ get; }}",
                                (IsNew(field) ? "new " : ""), field.Type, field.Name);
                        }
                    }
                }

                for (int i = 0, n = valueFields.Count; i < n; i++)
                {
                    var field = valueFields[i];
                    WriteLine();
                    WriteComment(field.PropertyComment, "    ");

                    WriteLine("   public abstract {0}{1} {2} {{ get; }}",
                        (IsNew(field) ? "new " : ""), field.Type, field.Name);
                }

                WriteLine("  }");
            }
            else if (node is Node)
            {
                Node nd = (Node)node;

                WriteLine("  internal sealed partial class {0} : {1}", node.Name, node.Base);
                WriteLine("  {");

                var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
                var nodeFields = nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList();

                for (int i = 0, n = nodeFields.Count; i < n; i++)
                {
                    var field = nodeFields[i];
                    var type = GetFieldType(field, green: true);
                    WriteLine("    private readonly {0} {1};", type, UnderscoreCamelCase(field.Name));
                }

                for (int i = 0, n = valueFields.Count; i < n; i++)
                {
                    var field = valueFields[i];
                    WriteLine("    private readonly {0} {1};", field.Type, UnderscoreCamelCase(field.Name));
                }

                // write constructor with diagnostics and annotations
                WriteLine();
                Write("    internal {0}(SyntaxKind kind", node.Name);

                WriteGreenNodeConstructorArgs(nodeFields, valueFields);

                WriteLine(", RazorDiagnostic[] diagnostics, SyntaxAnnotation[] annotations)");
                WriteLine("        : base(kind, diagnostics, annotations)");
                WriteLine("    {");
                WriteCtorBody(valueFields, nodeFields);
                WriteLine("    }");
                WriteLine();

                /* Remove
                // write constructor with async
                WriteLine();
                Write("    internal {0}(SyntaxKind kind", node.Name);

                WriteGreenNodeConstructorArgs(nodeFields, valueFields);

                WriteLine(", SyntaxFactoryContext context)");
                WriteLine("        : base(kind)");
                WriteLine("    {");
                WriteLine("        this.SetFactoryContext(context);");
                WriteCtorBody(valueFields, nodeFields);
                WriteLine("    }");
                WriteLine(); */

                // write constructor without diagnostics and annotations
                WriteLine();
                Write("    internal {0}(SyntaxKind kind", node.Name);

                WriteGreenNodeConstructorArgs(nodeFields, valueFields);

                WriteLine(")");
                WriteLine("        : base(kind)");
                WriteLine("    {");
                WriteCtorBody(valueFields, nodeFields);
                WriteLine("    }");
                WriteLine();

                // property accessors
                for (int i = 0, n = nodeFields.Count; i < n; i++)
                {
                    var field = nodeFields[i];
                    WriteComment(field.PropertyComment, "    ");
                    if (IsNodeList(field.Type))
                    {
                        WriteLine("    public {0}{1} {2} {{ get {{ return new {1}({3}); }} }}",
                            OverrideOrNewModifier(field), field.Type, field.Name, UnderscoreCamelCase(field.Name)
                            );
                    }
                    else if (IsSeparatedNodeList(field.Type))
                    {
                        WriteLine("    public {0}{1} {2} {{ get {{ return new {1}(new SyntaxList<GreenNode>({3})); }} }}",
                            OverrideOrNewModifier(field), field.Type, field.Name, UnderscoreCamelCase(field.Name), i
                            );
                    }
                    else if (field.Type == "SyntaxNodeOrTokenList")
                    {
                        WriteLine("    public {0}SyntaxList<GreenNode> {1} {{ get {{ return new SyntaxList<GreenNode>({2}); }} }}",
                            OverrideOrNewModifier(field), field.Name, UnderscoreCamelCase(field.Name)
                            );
                    }
                    else
                    {
                        WriteLine("    public {0}{1} {2} {{ get {{ return {3}; }} }}",
                            OverrideOrNewModifier(field), field.Type, field.Name, UnderscoreCamelCase(field.Name)
                            );
                    }
                }

                for (int i = 0, n = valueFields.Count; i < n; i++)
                {
                    var field = valueFields[i];
                    WriteComment(field.PropertyComment, "    ");
                    WriteLine("    public {0}{1} {2} {{ get {{ return {3}; }} }}",
                        OverrideOrNewModifier(field), field.Type, field.Name, UnderscoreCamelCase(field.Name)
                        );
                }

                // GetSlot
                WriteLine();
                WriteLine("    internal override GreenNode GetSlot(int index)");
                WriteLine("    {");
                WriteLine("        switch (index)");
                WriteLine("        {");
                for (int i = 0, n = nodeFields.Count; i < n; i++)
                {
                    var field = nodeFields[i];
                    WriteLine("            case {0}: return {1};", i, UnderscoreCamelCase(field.Name));
                }
                WriteLine("            default: return null;");
                WriteLine("        }");
                WriteLine("    }");

                WriteLine();
                WriteLine("    internal override SyntaxNode CreateRed(SyntaxNode parent, int position)");
                WriteLine("    {");
                WriteLine("      return new Syntax.{0}(this, parent, position);", node.Name);
                WriteLine("    }");

                WriteGreenAcceptMethods(nd);
                WriteGreenUpdateMethod(nd);
                WriteSetDiagnostics(nd);
                WriteSetAnnotations(nd);

                WriteLine("  }");
            }
        }

        private void WriteGreenNodeConstructorArgs(List<Field> nodeFields, List<Field> valueFields)
        {
            for (int i = 0, n = nodeFields.Count; i < n; i++)
            {
                var field = nodeFields[i];
                string type = GetFieldType(field, green: true);

                Write(", {0} {1}", type, CamelCase(field.Name));
            }

            for (int i = 0, n = valueFields.Count; i < n; i++)
            {
                var field = valueFields[i];
                Write(", {0} {1}", field.Type, CamelCase(field.Name));
            }
        }

        private void WriteGreenSerialization(Node node)
        {
            var valueFields = node.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
            var nodeFields = node.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList();

            // object reader constructor
            WriteLine();
            WriteLine("    internal {0}(ObjectReader reader)", node.Name);
            WriteLine("        : base(reader)");
            WriteLine("    {");

            WriteLine("      this.SlotCount = {0};", nodeFields.Count);

            for (int i = 0, n = nodeFields.Count; i < n; i++)
            {
                var field = nodeFields[i];
                string type = GetFieldType(field, green: true);
                WriteLine("      var {0} = ({1})reader.ReadValue();", CamelCase(field.Name), type);
                WriteLine("      if ({0} != null)", CamelCase(field.Name));
                WriteLine("      {");
                WriteLine("         AdjustFlagsAndWidth({0});", CamelCase(field.Name));
                WriteLine("         this.{0} = {0};", CamelCase(field.Name), type);
                WriteLine("      }");
            }

            for (int i = 0, n = valueFields.Count; i < n; i++)
            {
                var field = valueFields[i];
                string type = GetFieldType(field, green: true);
                WriteLine("      this.{0} = ({1})reader.{2}();", CamelCase(field.Name), type, GetReaderMethod(type));
            }

            WriteLine("    }");

            // IWritable
            WriteLine();
            WriteLine("    internal override void WriteTo(ObjectWriter writer)");
            WriteLine("    {");
            WriteLine("      base.WriteTo(writer);");

            for (int i = 0, n = nodeFields.Count; i < n; i++)
            {
                var field = nodeFields[i];
                string type = GetFieldType(field, green: true);
                WriteLine("      writer.WriteValue(this.{0});", CamelCase(field.Name));
            }

            for (int i = 0, n = valueFields.Count; i < n; i++)
            {
                var field = valueFields[i];
                var type = GetFieldType(field, green: true);
                WriteLine("      writer.{0}(this.{1});", GetWriterMethod(type), CamelCase(field.Name));
            }

            WriteLine("    }");

            // IReadable
            WriteLine();
            WriteLine("    static {0}()", node.Name);
            WriteLine("    {");
            WriteLine("       ObjectBinder.RegisterTypeReader(typeof({0}), r => new {0}(r));", node.Name);
            WriteLine("    }");
        }

        private string GetWriterMethod(string type)
        {
            switch (type)
            {
                case "bool":
                    return "WriteBoolean";
                default:
                    throw new InvalidOperationException("Type 'type' not supported for object reader serialization.");
            }
        }

        private string GetReaderMethod(string type)
        {
            switch (type)
            {
                case "bool":
                    return "ReadBoolean";
                default:
                    throw new InvalidOperationException("Type 'type' not supported for object reader serialization.");
            }
        }

        private void WriteCtorBody(List<Field> valueFields, List<Field> nodeFields)
        {
            // constructor body
            WriteLine("        SlotCount = {0};", nodeFields.Count);

            for (int i = 0, n = nodeFields.Count; i < n; i++)
            {
                var field = nodeFields[i];
                if (IsAnyList(field.Type) || IsOptional(field))
                {
                    WriteLine("        if ({0} != null)", CamelCase(field.Name));
                    WriteLine("        {");
                    WriteLine("            AdjustFlagsAndWidth({0});", CamelCase(field.Name));
                    WriteLine("            {0} = {1};", UnderscoreCamelCase(field.Name), CamelCase(field.Name));
                    WriteLine("        }");
                }
                else
                {
                    WriteLine("        AdjustFlagsAndWidth({0});", CamelCase(field.Name));
                    WriteLine("        {0} = {1};", UnderscoreCamelCase(field.Name), CamelCase(field.Name));
                }
            }

            for (int i = 0, n = valueFields.Count; i < n; i++)
            {
                var field = valueFields[i];
                WriteLine("        {0} = {1};", UnderscoreCamelCase(field.Name), CamelCase(field.Name));
            }
        }

        private void WriteSetAnnotations(Node node)
        {
            WriteLine();
            WriteLine("    internal override GreenNode SetAnnotations(SyntaxAnnotation[] annotations)");
            WriteLine("    {");

            Write("         return new {0}(", node.Name);
            Write("Kind, ");
            for (int f = 0; f < node.Fields.Count; f++)
            {
                var field = node.Fields[f];
                if (f > 0)
                    Write(", ");
                Write("{0}", UnderscoreCamelCase(field.Name));
            }
            WriteLine(", GetDiagnostics(), annotations);");
            WriteLine("    }");
        }

        private void WriteSetDiagnostics(Node node)
        {
            WriteLine();
            WriteLine("    internal override GreenNode SetDiagnostics(RazorDiagnostic[] diagnostics)");
            WriteLine("    {");

            Write("         return new {0}(", node.Name);
            Write("Kind, ");
            for (int f = 0; f < node.Fields.Count; f++)
            {
                var field = node.Fields[f];
                if (f > 0)
                    Write(", ");
                Write("{0}", UnderscoreCamelCase(field.Name));
            }
            WriteLine(", diagnostics, GetAnnotations());");
            WriteLine("    }");
        }

        private void WriteGreenAcceptMethods(Node node)
        {
            //WriteLine();
            //WriteLine("    public override TResult Accept<TArgument, TResult>(SyntaxVisitor<TArgument, TResult> visitor, TArgument argument)");
            //WriteLine("    {");
            //WriteLine("        return visitor.Visit{0}(this, argument);", StripPost(node.Name, "Syntax"));
            //WriteLine("    }");
            WriteLine();
            WriteLine("    public override TResult Accept<TResult>(SyntaxVisitor<TResult> visitor)");
            WriteLine("    {");
            WriteLine("        return visitor.Visit{0}(this);", StripPost(node.Name, "Syntax"));
            WriteLine("    }");
            WriteLine();
            WriteLine("    public override void Accept(SyntaxVisitor visitor)");
            WriteLine("    {");
            WriteLine("        visitor.Visit{0}(this);", StripPost(node.Name, "Syntax"));
            WriteLine("    }");
        }

        private void WriteGreenVisitors()
        {
            //WriteGreenVisitor(true, true);
            //WriteLine();
            WriteGreenVisitor(false, true);
            WriteLine();
            WriteGreenVisitor(false, false);
        }

        private void WriteGreenVisitor(bool withArgument, bool withResult)
        {
            var nodes = Tree.Types.Where(n => !(n is PredefinedNode)).ToList();

            WriteLine();
            WriteLine("  internal partial class SyntaxVisitor" + (withResult ? "<" + (withArgument ? "TArgument, " : "") + "TResult>" : ""));
            WriteLine("  {");
            int nWritten = 0;
            for (int i = 0, n = nodes.Count; i < n; i++)
            {
                if (nodes[i] is Node node)
                {
                    if (nWritten > 0)
                        WriteLine();
                    nWritten++;
                    WriteLine("    public virtual " + (withResult ? "TResult" : "void") + " Visit{0}({1} node{2})", StripPost(node.Name, "Syntax"), node.Name, withArgument ? ", TArgument argument" : "");
                    WriteLine("    {");
                    WriteLine("      " + (withResult ? "return " : "") + "DefaultVisit(node{0});", withArgument ? ", argument" : "");
                    WriteLine("    }");
                }
            }
            WriteLine("  }");
        }

        private void WriteGreenUpdateMethod(Node node)
        {
            WriteLine();
            Write("    public {0} Update(", node.Name);

            // parameters
            for (int f = 0; f < node.Fields.Count; f++)
            {
                var field = node.Fields[f];
                if (f > 0)
                    Write(", ");

                var type =
                    field.Type == "SyntaxNodeOrTokenList" ? "Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax.SyntaxList<GreenNode>" :
                    field.Type == "SyntaxTokenList" ? "Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax.SyntaxList<SyntaxToken>" :
                    IsNodeList(field.Type) ? "Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax." + field.Type :
                    IsSeparatedNodeList(field.Type) ? "Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax." + field.Type :
                    field.Type;

                Write("{0} {1}", type, CamelCase(field.Name));
            }
            WriteLine(")");
            WriteLine("    {");

            Write("        if (");
            int nCompared = 0;
            for (int f = 0; f < node.Fields.Count; f++)
            {
                var field = node.Fields[f];
                if (IsDerivedOrListOfDerived("SyntaxNode", field.Type) || IsDerivedOrListOfDerived("SyntaxToken", field.Type) || field.Type == "SyntaxNodeOrTokenList")
                {
                    if (nCompared > 0)
                        Write(" || ");
                    Write("{0} != {1}", CamelCase(field.Name), field.Name);
                    nCompared++;
                }
            }
            if (nCompared > 0)
            {
                WriteLine(")");
                WriteLine("        {");
                Write("            var newNode = SyntaxFactory.{0}(", StripPost(node.Name, "Syntax"));
                if (node.Kinds.Count > 1)
                {
                    Write("Kind, ");
                }
                for (int f = 0; f < node.Fields.Count; f++)
                {
                    var field = node.Fields[f];
                    if (f > 0)
                        Write(", ");
                    Write(CamelCase(field.Name));
                }
                WriteLine(");");
                WriteLine("            var diags = GetDiagnostics();");
                WriteLine("            if (diags != null && diags.Length > 0)");
                WriteLine("               newNode = newNode.WithDiagnosticsGreen(diags);");
                WriteLine("            var annotations = GetAnnotations();");
                WriteLine("            if (annotations != null && annotations.Length > 0)");
                WriteLine("               newNode = newNode.WithAnnotationsGreen(annotations);");
                WriteLine("            return newNode;");
                WriteLine("        }");
            }

            WriteLine();
            WriteLine("        return this;");
            WriteLine("    }");
        }

        private void WriteGreenRewriter()
        {
            var nodes = Tree.Types.Where(n => !(n is PredefinedNode)).ToList();

            WriteLine();
            WriteLine("  internal partial class SyntaxRewriter : SyntaxVisitor<GreenNode>");
            WriteLine("  {");
            int nWritten = 0;
            for (int i = 0, n = nodes.Count; i < n; i++)
            {
                if (nodes[i] is Node node)
                {
                    var nodeFields = node.Fields.Where(nd => IsNodeOrNodeList(nd.Type)).ToList();

                    if (nWritten > 0)
                        WriteLine();
                    nWritten++;
                    WriteLine("    public override GreenNode Visit{0}({1} node)", StripPost(node.Name, "Syntax"), node.Name);
                    WriteLine("    {");
                    for (int f = 0; f < nodeFields.Count; f++)
                    {
                        var field = nodeFields[f];
                        if (IsAnyList(field.Type))
                        {
                            WriteLine("      var {0} = VisitList(node.{1});", CamelCase(field.Name), field.Name);
                        }
                        else
                        {
                            WriteLine("      var {0} = ({1})Visit(node.{2});", CamelCase(field.Name), field.Type, field.Name);
                        }
                    }
                    if (nodeFields.Count > 0)
                    {
                        Write("      return node.Update(");
                        for (int f = 0; f < node.Fields.Count; f++)
                        {
                            var field = node.Fields[f];
                            if (f > 0)
                                Write(", ");
                            if (IsNodeOrNodeList(field.Type))
                            {
                                Write(CamelCase(field.Name));
                            }
                            else
                            {
                                Write("node.{0}", field.Name);
                            }
                        }
                        WriteLine(");");
                    }
                    else
                    {
                        WriteLine("      return node;");
                    }
                    WriteLine("    }");
                }
            }
            WriteLine("  }");
        }

        private void WriteContextualGreenFactories()
        {
            var nodes = Tree.Types.Where(n => !(n is PredefinedNode) && !(n is AbstractNode)).ToList();
            WriteLine();
            WriteLine("  internal partial class ContextAwareSyntax");
            WriteLine("  {");

            WriteLine();
            WriteLine("    private SyntaxFactoryContext context;");
            WriteLine();

            WriteLine();
            WriteLine("    public ContextAwareSyntax(SyntaxFactoryContext context)");
            WriteLine("    {");
            WriteLine("        this.context = context;");
            WriteLine("    }");

            WriteGreenFactories(nodes, withSyntaxFactoryContext: true);

            WriteLine("  }");
        }

        private void WriteStaticGreenFactories()
        {
            var nodes = Tree.Types.Where(n => !(n is PredefinedNode) && !(n is AbstractNode)).ToList();
            WriteLine();
            WriteLine("  internal static partial class SyntaxFactory");
            WriteLine("  {");

            WriteGreenFactories(nodes);

            WriteGreenTypeList();

            WriteLine("  }");
        }

        private void WriteGreenFactories(List<TreeType> nodes, bool withSyntaxFactoryContext = false)
        {
            for (int i = 0, n = nodes.Count; i < n; i++)
            {
                var node = nodes[i];
                WriteGreenFactory((Node)node, withSyntaxFactoryContext);
                if (i < n - 1)
                    WriteLine();
            }
        }

        private void WriteGreenTypeList()
        {
            WriteLine();
            WriteLine("    internal static IEnumerable<Type> GetNodeTypes()");
            WriteLine("    {");
            WriteLine("        return new Type[] {");

            var nodes = Tree.Types.Where(n => !(n is PredefinedNode) && !(n is AbstractNode)).ToList();
            for (int i = 0, n = nodes.Count; i < n; i++)
            {
                var node = nodes[i];
                Write("           typeof({0})", node.Name);
                if (i < n - 1)
                    Write(",");
                WriteLine();
            }

            WriteLine("        };");
            WriteLine("    }");
        }


        private void WriteGreenFactory(Node nd, bool withSyntaxFactoryContext = false)
        {
            var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
            var nodeFields = nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList();

            Write("    public {0}{1} {2}(", withSyntaxFactoryContext ? "" : "static ", nd.Name, StripPost(nd.Name, "Syntax"));
            WriteGreenFactoryParameters(nd);
            WriteLine(")");
            WriteLine("    {");

            // validate kind
            if (nd.Kinds.Count > 1)
            {
                WriteLine("      switch (kind)");
                WriteLine("      {");
                foreach (var k in nd.Kinds)
                {
                    WriteLine("        case SyntaxKind.{0}:", k.Name);
                }
                WriteLine("          break;");
                WriteLine("        default:");
                WriteLine("          throw new ArgumentException(\"kind\");");
                WriteLine("      }");
            }

            // validate parameters
            //WriteLine("#if DEBUG");
            for (int i = 0, n = nodeFields.Count; i < n; i++)
            {
                var field = nodeFields[i];
                var pname = CamelCase(field.Name);

                if (!IsAnyList(field.Type) && !IsOptional(field))
                {
                    WriteLine("      if ({0} == null)", CamelCase(field.Name));
                    WriteLine("        throw new ArgumentNullException(nameof({0}));", CamelCase(field.Name));
                }
                if (field.Type == "SyntaxToken" && field.Kinds != null && field.Kinds.Count > 0)
                {
                    if (IsOptional(field))
                    {
                        WriteLine("      if ({0} != null)", CamelCase(field.Name));
                        WriteLine("      {");
                    }
                    WriteLine("      switch ({0}.Kind)", pname);
                    WriteLine("      {");
                    foreach (var kind in field.Kinds)
                    {
                        WriteLine("        case SyntaxKind.{0}:", kind.Name);
                    }
                    //we need to check for Kind=None as well as node == null because that's what the red factory will pass
                    if (IsOptional(field))
                    {
                        WriteLine("        case SyntaxKind.None:");
                    }
                    WriteLine("          break;");
                    WriteLine("        default:");
                    WriteLine("          throw new ArgumentException(\"{0}\");", pname);
                    WriteLine("      }");
                    if (IsOptional(field))
                    {
                        WriteLine("      }");
                    }
                }
            }

            //WriteLine("#endif");

            if (nd.Name != "SkippedTokensTriviaSyntax" &&
                nd.Name != "DocumentationCommentTriviaSyntax" &&
                nd.Name != "IncompleteMemberSyntax" &&
                valueFields.Count + nodeFields.Count <= 3)
            {
                //int hash;
                //var cached = SyntaxNodeCache.TryGetNode((int)SyntaxKind.IdentifierName, identifier, this.context, out hash);
                //if (cached != null) return (IdentifierNameSyntax)cached;

                //var result = new IdentifierNameSyntax(SyntaxKind.IdentifierName, identifier, this.context);
                //if (hash >= 0)
                //{
                //    SyntaxNodeCache.AddNode(result, hash);
                //}

                //return result;

                WriteLine();

                /* Remove
                //int hash;
                WriteLine("      int hash;");
                //SyntaxNode cached = SyntaxNodeCache.TryGetNode(SyntaxKind.IdentifierName, identifier, this.context, out hash);
                if (withSyntaxFactoryContext)
                {
                    Write("      var cached = CSharpSyntaxNodeCache.TryGetNode((int)");
                }
                else
                {
                    Write("      var cached = SyntaxNodeCache.TryGetNode((int)");
                }

                WriteCtorArgList(nd, withSyntaxFactoryContext, valueFields, nodeFields);
                WriteLine(", out hash);");
                //    if (cached != null) return (IdentifierNameSyntax)cached;
                WriteLine("      if (cached != null) return ({0})cached;", nd.Name);
                WriteLine(); */

                //var result = new IdentifierNameSyntax(SyntaxKind.IdentifierName, identifier);
                Write("      var result = new {0}(", nd.Name);
                WriteCtorArgList(nd, withSyntaxFactoryContext, valueFields, nodeFields);
                WriteLine(");");

                /* Remove
                //if (hash >= 0)
                WriteLine("      if (hash >= 0)");
                //{
                WriteLine("      {");
                //    SyntaxNodeCache.AddNode(result, hash);
                WriteLine("          SyntaxNodeCache.AddNode(result, hash);");
                //}
                WriteLine("      }"); */
                WriteLine();

                //return result;
                WriteLine("      return result;");
            }
            else
            {
                WriteLine();
                Write("      return new {0}(", nd.Name);
                WriteCtorArgList(nd, withSyntaxFactoryContext, valueFields, nodeFields);
                WriteLine(");");
            }

            WriteLine("    }");
        }

        private void WriteGreenFactoryParameters(Node nd)
        {
            if (nd.Kinds.Count > 1)
            {
                Write("SyntaxKind kind, ");
            }
            for (int i = 0, n = nd.Fields.Count; i < n; i++)
            {
                var field = nd.Fields[i];
                if (i > 0)
                    Write(", ");
                var type = field.Type;
                if (type == "SyntaxNodeOrTokenList")
                {
                    type = "SyntaxList<GreenNode>";
                }
                else if (IsSeparatedNodeList(field.Type) ||
                         IsNodeList(field.Type))
                {
                    type = "Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax." + type;
                }
                Write("{0} {1}", type, CamelCase(field.Name));
            }
        }

        private void WriteCtorArgList(Node nd, bool withSyntaxFactoryContext, List<Field> valueFields, List<Field> nodeFields)
        {
            if (nd.Kinds.Count == 1)
            {
                Write("SyntaxKind.");
                Write(nd.Kinds[0].Name);
            }
            else
            {
                Write("kind");
            }
            for (int i = 0, n = nodeFields.Count; i < n; i++)
            {
                var field = nodeFields[i];
                Write(", ");
                if (field.Type == "SyntaxList<SyntaxToken>" || IsAnyList(field.Type))
                {
                    Write("{0}.Node", CamelCase(field.Name));
                }
                else
                {
                    Write(CamelCase(field.Name));
                }
            }
            // values are at end
            for (int i = 0, n = valueFields.Count; i < n; i++)
            {
                var field = valueFields[i];
                Write(", ");
                Write(UnderscoreCamelCase(field.Name));
            }
            if (withSyntaxFactoryContext)
            {
                Write(", this.context");
            }
        }

        private void WriteRedTypes()
        {
            var nodes = Tree.Types.Where(n => !(n is PredefinedNode)).ToList();
            for (int i = 0, n = nodes.Count; i < n; i++)
            {
                var node = nodes[i];
                WriteLine();
                WriteRedType(node);
            }
        }

        private List<Field> GetNodeOrNodeListFields(TreeType node)
            => node is AbstractNode an
                ? an.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList()
                : node is Node nd
                    ? nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList()
                    : new List<Field>();

        private void WriteRedType(TreeType node)
        {
            WriteComment(node.TypeComment, "  ");

            if (node is AbstractNode)
            {
                AbstractNode nd = (AbstractNode)node;
                WriteLine("  internal abstract partial class {0} : {1}", node.Name, node.Base);
                WriteLine("  {");
                WriteLine("    internal {0}(GreenNode green, SyntaxNode parent, int position)", node.Name);
                WriteLine("      : base(green, parent, position)");
                WriteLine("    {");
                WriteLine("    }");

                var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
                var nodeFields = GetNodeOrNodeListFields(nd);

                for (int i = 0, n = nodeFields.Count; i < n; i++)
                {
                    var field = nodeFields[i];
                    if (IsNodeOrNodeList(field.Type))
                    {
                        //red SyntaxLists can't contain tokens, so we switch to SyntaxTokenList
                        var fieldType = GetRedFieldType(field);
                        WriteLine();
                        WriteComment(field.PropertyComment, "    ");
                        WriteLine("    {0} abstract {1}{2} {3} {{ get; }}", "public", (IsNew(field) ? "new " : ""), fieldType, field.Name);
                        WriteLine($"    public {node.Name} With{field.Name}({fieldType} {CamelCase(field.Name)}) => With{field.Name}Core({CamelCase(field.Name)});");
                        WriteLine($"    internal abstract {node.Name} With{field.Name}Core({fieldType} {CamelCase(field.Name)});");

                        if (IsAnyList(field.Type))
                        {
                            var argType = GetElementType(field.Type);
                            WriteLine();
                            WriteLine("    public {0} Add{1}(params {2}[] items) => Add{1}Core(items);", node.Name, field.Name, argType);
                            WriteLine("    internal abstract {0} Add{1}Core(params {2}[] items);", node.Name, field.Name, argType);
                        }
                        else
                        {
                            var referencedNode = TryGetNodeForNestedList(field);
                            if (referencedNode != null)
                            {
                                for (int rf = 0; rf < referencedNode.Fields.Count; rf++)
                                {
                                    var referencedNodeField = referencedNode.Fields[rf];
                                    if (IsAnyList(referencedNodeField.Type))
                                    {
                                        var argType = GetElementType(referencedNodeField.Type);

                                        WriteLine();
                                        WriteLine("    public {0} Add{1}{2}(params {3}[] items) => Add{1}{2}Core(items);", node.Name, StripPost(field.Name, "Opt"), referencedNodeField.Name, argType);
                                        WriteLine("    internal abstract {0} Add{1}{2}Core(params {3}[] items);", node.Name, StripPost(field.Name, "Opt"), referencedNodeField.Name, argType);
                                    }
                                }
                            }
                        }
                    }
                }

                for (int i = 0, n = valueFields.Count; i < n; i++)
                {
                    var field = valueFields[i];
                    WriteLine();
                    WriteComment(field.PropertyComment, "    ");
                    WriteLine("    {0} abstract {1}{2} {3} {{ get; }}", "public", (IsNew(field) ? "new " : ""), field.Type, field.Name);
                }

                var baseType = GetTreeType(node.Base);
                if (baseType != null)
                {
                    var baseNodeFields = GetNodeOrNodeListFields(baseType);
                    if (baseNodeFields.Count > 0)
                    {
                        WriteLine();
                    }

                    foreach (var baseField in baseNodeFields)
                    {
                        WriteLine($"    public new {node.Name} With{baseField.Name}({GetRedFieldType(baseField)} {CamelCase(baseField.Name)}) => ({node.Name})With{baseField.Name}Core({CamelCase(baseField.Name)});");
                    }

                    foreach (var baseField in baseNodeFields)
                    {
                        if (IsAnyList(baseField.Type))
                        {
                            var argType = GetElementType(baseField.Type);
                            WriteLine();
                            WriteLine("    public new {0} Add{1}(params {2}[] items) => ({0})Add{1}Core(items);", node.Name, baseField.Name, argType);
                        }
                        else
                        {
                            var referencedNode = TryGetNodeForNestedList(baseField);
                            if (referencedNode != null)
                            {
                                // look for list members...
                                for (int rf = 0; rf < referencedNode.Fields.Count; rf++)
                                {
                                    var referencedNodeField = referencedNode.Fields[rf];
                                    if (IsAnyList(referencedNodeField.Type))
                                    {
                                        var argType = GetElementType(referencedNodeField.Type);

                                        WriteLine();
                                        WriteLine("    public new {0} Add{1}{2}(params {3}[] items) => Add{1}{2}Core(items);", baseType.Name, StripPost(baseField.Name, "Opt"), referencedNodeField.Name, argType);
                                    }
                                }
                            }
                        }
                    }
                }

                WriteLine("  }");
            }
            else if (node is Node)
            {
                Node nd = (Node)node;
                WriteLine("  internal sealed partial class {0} : {1}", node.Name, node.Base);
                WriteLine("  {");

                var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList();
                var nodeFields = nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList();

                for (int i = 0, n = nodeFields.Count; i < n; i++)
                {
                    var field = nodeFields[i];
                    //if (field.Type != "SyntaxToken"
                    //    && field.Type != "SyntaxList<SyntaxToken>"
                    //    )
                    //{
                        if (IsSeparatedNodeList(field.Type) || field.Type == "SyntaxNodeOrTokenList")
                        {
                            WriteLine("    private SyntaxNode {0};", UnderscoreCamelCase(field.Name));
                        }
                        else
                        {
                            var type = GetFieldType(field, green: false);
                            WriteLine("    private {0} {1};", type, UnderscoreCamelCase(field.Name));
                        }
                    //}
                }

                // write constructor
                WriteLine();
                WriteLine("    internal {0}(GreenNode green, SyntaxNode parent, int position)", node.Name);
                WriteLine("        : base(green, parent, position)");
                WriteLine("    {");
                WriteLine("    }");
                WriteLine();

                // property accessors
                for (int i = 0, n = nodeFields.Count; i < n; i++)
                {
                    var field = nodeFields[i];
                    //if (field.Type == "SyntaxToken")
                    //{
                    //    WriteComment(field.PropertyComment, "    ");
                    //    WriteLine("    {0} {1}{2} {3} ", "public", OverrideOrNewModifier(field), field.Type, field.Name);
                    //    WriteLine("    {");
                    //    if (IsOptional(field))
                    //    {
                    //        WriteLine("        get");
                    //        WriteLine("        {");
                    //        WriteLine("            var slot = ((InternalSyntax.{0})Green).{1};", node.Name, field.Name);
                    //        WriteLine("            if (slot != null)");
                    //        WriteLine("                return new SyntaxToken(slot, this, {0});", GetChildPosition(i)/*, GetChildIndex(i)*/);
                    //        WriteLine();
                    //        WriteLine("            return default(SyntaxToken);");
                    //        WriteLine("        }");
                    //    }
                    //    else
                    //    {
                    //        WriteLine("      get {{ return new SyntaxToken(((InternalSyntax.{0})Green).{1}, this, {2}); }}", node.Name, field.Name, GetChildPosition(i)/*, GetChildIndex(i)*/);
                    //    }
                    //    WriteLine("    }");
                    //}
                    /* Remove
                    else if (field.Type == "SyntaxList<SyntaxToken>")
                    {
                        WriteComment(field.PropertyComment, "    ");
                        WriteLine("    {0} {1}SyntaxTokenList {2} ", "public", OverrideOrNewModifier(field), field.Name);
                        WriteLine("    {");
                        WriteLine("        get");
                        WriteLine("        {");
                        WriteLine("            var slot = Green.GetSlot({0});", i);
                        WriteLine("            if (slot != null)");
                        WriteLine("                return new SyntaxTokenList(this, slot, {0}, {1});", GetChildPosition(i), GetChildIndex(i));
                        WriteLine();
                        WriteLine("            return default(SyntaxTokenList);");
                        WriteLine("        }");
                        WriteLine("    }");
                    } */
                    //else
                    //{
                        WriteComment(field.PropertyComment, "    ");
                        WriteLine("    {0} {1}{2} {3} ", "public", OverrideOrNewModifier(field), field.Type, field.Name);
                        WriteLine("    {");
                        WriteLine("        get");
                        WriteLine("        {");

                        if (IsNodeList(field.Type))
                        {
                            WriteLine("            return new {0}(GetRed(ref {1}, {2}));", field.Type, UnderscoreCamelCase(field.Name), i);
                        }
                        else if (IsSeparatedNodeList(field.Type))
                        {
                            WriteLine("            var red = GetRed(ref {0}, {1});", UnderscoreCamelCase(field.Name), i);
                            WriteLine("            if (red != null)", i);
                            WriteLine("                return new {0}(red, {1});", field.Type, GetChildIndex(i));
                            WriteLine();
                            WriteLine("            return default({0});", field.Type);
                        }
                        else if (field.Type == "SyntaxNodeOrTokenList")
                        {
                            throw new InvalidOperationException("field cannot be a random SyntaxNodeOrTokenList");
                        }
                        else
                        {
                            if (i == 0)
                            {
                                WriteLine("            return GetRedAtZero(ref {0});", UnderscoreCamelCase(field.Name));
                            }
                            else
                            {
                                WriteLine("            return GetRed(ref {0}, {1});", UnderscoreCamelCase(field.Name), i);
                            }
                        }
                        WriteLine("        }");
                        WriteLine("    }");
                    //}
                    WriteLine();
                }

                for (int i = 0, n = valueFields.Count; i < n; i++)
                {
                    var field = valueFields[i];
                    WriteComment(field.PropertyComment, "    ");
                    WriteLine("    {0} {1}{2} {3} {{ get {{ return ((InternalSyntax.{4})Green).{3}; }} }}",
                        "public", OverrideOrNewModifier(field), field.Type, field.Name, node.Name
                        );
                    WriteLine();
                }

                //GetNodeSlot forces creation of a red node.
                WriteLine("    internal override SyntaxNode GetNodeSlot(int index)");
                WriteLine("    {");
                WriteLine("        switch (index)");
                WriteLine("        {");
                for (int i = 0, n = nodeFields.Count; i < n; i++)
                {
                    var field = nodeFields[i];

                    //if (field.Type != "SyntaxToken" && field.Type != "SyntaxList<SyntaxToken>")
                    if (true)
                    {
                        if (i == 0)
                        {
                            WriteLine("            case {0}: return GetRedAtZero(ref {1});", i, UnderscoreCamelCase(field.Name));
                        }
                        else
                        {
                            WriteLine("            case {0}: return GetRed(ref {1}, {0});", i, UnderscoreCamelCase(field.Name));
                        }
                    }
                }
                WriteLine("            default: return null;");
                WriteLine("        }");
                WriteLine("    }");

                //GetCachedSlot returns a red node if we have it.
                WriteLine("    internal override SyntaxNode GetCachedSlot(int index)");
                WriteLine("    {");
                WriteLine("        switch (index)");
                WriteLine("        {");
                for (int i = 0, n = nodeFields.Count; i < n; i++)
                {
                    var field = nodeFields[i];
                    //if (field.Type != "SyntaxToken" && field.Type != "SyntaxList<SyntaxToken>")
                    if (true)
                    {
                        WriteLine("            case {0}: return {1};", i, UnderscoreCamelCase(field.Name));
                    }
                }
                WriteLine("            default: return null;");
                WriteLine("        }");
                WriteLine("    }");


                WriteRedAcceptMethods(nd);
                WriteRedUpdateMethod(nd);
                WriteRedWithMethods(nd);
                WriteRedListHelperMethods(nd);

                WriteLine("  }");
            }
        }

        private static string GetRedFieldType(Field field)
        {
            //return field.Type == "SyntaxList<SyntaxToken>" ? "SyntaxTokenList" : field.Type;
            return field.Type;
        }

        private string GetChildPosition(int i)
        {
            if (i == 0)
            {
                return "Position";
            }
            else
            {
                return "GetChildPosition(" + i + ")";
            }
        }

        private string GetChildIndex(int i)
        {
            if (i == 0)
            {
                return "0";
            }
            else
            {
                return "GetChildIndex(" + i + ")";
            }
        }

        private void WriteRedAcceptMethods(Node node)
        {
            //WriteRedAcceptMethod(node, true, true);
            WriteRedAcceptMethod(node, false, true);
            WriteRedAcceptMethod(node, false, false);
        }

        private void WriteRedAcceptMethod(Node node, bool genericArgument, bool genericResult)
        {
            string genericArgs =
                (genericResult && genericArgument) ? "<TArgument, TResult>" :
                genericResult ? "<TResult>" : "";
            WriteLine();
            WriteLine("    public override " + (genericResult ? "TResult" : "void") + " Accept" + genericArgs + "(SyntaxVisitor" + genericArgs + " visitor{0})", genericArgument ? ", TArgument argument" : "");
            WriteLine("    {");
            WriteLine("        " + (genericResult ? "return " : "") + "visitor.Visit{0}(this{1});", StripPost(node.Name, "Syntax"), genericArgument ? ", argument" : "");
            WriteLine("    }");
        }

        private void WriteRedVisitors()
        {
            //WriteRedVisitor(true, true);
            WriteRedVisitor(false, true);
            WriteRedVisitor(false, false);
        }

        private void WriteRedVisitor(bool genericArgument, bool genericResult)
        {
            string genericArgs =
                (genericResult && genericArgument) ? "<TArgument, TResult>" :
                genericResult ? "<TResult>" : "";
            var nodes = Tree.Types.Where(n => !(n is PredefinedNode)).ToList();

            WriteLine();
            WriteLine("  internal partial class SyntaxVisitor" + genericArgs);
            WriteLine("  {");
            int nWritten = 0;
            for (int i = 0, n = nodes.Count; i < n; i++)
            {
                if (nodes[i] is Node node)
                {
                    if (nWritten > 0)
                        WriteLine();
                    nWritten++;
                    WriteComment(string.Format(CultureInfo.InvariantCulture, "<summary>Called when the visitor visits a {0} node.</summary>", node.Name), "    ");
                    WriteLine("    public virtual " + (genericResult ? "TResult" : "void") + " Visit{0}({1} node{2})", StripPost(node.Name, "Syntax"), node.Name, genericArgument ? ", TArgument argument" : "");
                    WriteLine("    {");
                    WriteLine("      " + (genericResult ? "return " : "") + "DefaultVisit(node{0});", genericArgument ? ", argument" : "");
                    WriteLine("    }");
                }
            }
            WriteLine("  }");
        }

        private void WriteRedUpdateMethod(Node node)
        {
            WriteLine();
            Write("    {0} {1} Update(", "public", node.Name);

            // parameters
            for (int f = 0; f < node.Fields.Count; f++)
            {
                var field = node.Fields[f];
                if (f > 0)
                    Write(", ");
                //var type = field.Type == "SyntaxList<SyntaxToken>" ? "SyntaxTokenList" : field.Type;
                var type = field.Type;
                Write("{0} {1}", type, CamelCase(field.Name));
            }
            WriteLine(")");
            WriteLine("    {");

            Write("        if (");
            int nCompared = 0;
            for (int f = 0; f < node.Fields.Count; f++)
            {
                var field = node.Fields[f];
                if (IsDerivedOrListOfDerived("SyntaxNode", field.Type) || IsDerivedOrListOfDerived("SyntaxToken", field.Type) || field.Type == "SyntaxNodeOrTokenList")
                {
                    if (nCompared > 0)
                        Write(" || ");
                    Write("{0} != {1}", CamelCase(field.Name), field.Name);
                    nCompared++;
                }
            }
            if (nCompared > 0)
            {
                WriteLine(")");
                WriteLine("        {");
                Write("            var newNode = SyntaxFactory.{0}(", StripPost(node.Name, "Syntax"));
                if (node.Kinds.Count > 1)
                {
                    Write("Kind(), ");
                }
                for (int f = 0; f < node.Fields.Count; f++)
                {
                    var field = node.Fields[f];
                    if (f > 0)
                        Write(", ");
                    Write(CamelCase(field.Name));
                }
                WriteLine(");");
                WriteLine("            var diagnostics = GetDiagnostics();");
                WriteLine("            if (diagnostics != null && diagnostics.Length > 0)");
                WriteLine("               newNode = newNode.WithDiagnostics(diagnostics);");
                WriteLine("            var annotations = GetAnnotations();");
                WriteLine("            if (annotations != null && annotations.Length > 0)");
                WriteLine("               return newNode.WithAnnotations(annotations);");
                WriteLine("            return newNode;");
                WriteLine("        }");
            }

            WriteLine();
            WriteLine("        return this;");
            WriteLine("    }");
        }

        private void WriteRedWithMethod(Node node)
        {
            WriteLine();
            Write("    public {0} With(", node.Name);

            // parameters
            for (int f = 0; f < node.Fields.Count; f++)
            {
                var field = node.Fields[f];
                var type = GetRedPropertyType(field);
                Write("Optional<{0}> {1} = default(Optional<{0}>)", type, UnderscoreCamelCase(field.Name));
                if (f < node.Fields.Count - 1)
                    Write(", ");
            }
            WriteLine(")");
            WriteLine("    {");

            Write("        return Update(");

            for (int f = 0; f < node.Fields.Count; f++)
            {
                var field = node.Fields[f];
                var parameterName = UnderscoreCamelCase(field.Name);
                WriteLine();
                Write("                    {0}.HasValue ? {0}.Value : {1}", parameterName, field.Name);
                if (f < node.Fields.Count - 1)
                    Write(",");
            }

            WriteLine();
            WriteLine("                    );");

            WriteLine("    }");
        }

        private void WriteRedWithMethods(Node node)
        {
            for (int f = 0; f < node.Fields.Count; f++)
            {
                var field = node.Fields[f];
                var type = GetRedPropertyType(field);

                WriteLine();

                var isNew = false;
                if (IsOverride(field))
                {
                    var baseType = GetHighestBaseTypeWithField(node, field.Name);
                    if (baseType != null)
                    {
                        WriteLine($"    internal override {baseType.Name} With{field.Name}Core({type} {CamelCase(field.Name)}) => With{field.Name}({CamelCase(field.Name)});");
                        isNew = true;
                    }
                }

                WriteLine($"    public{(isNew ? " new " : " ")}{node.Name} With{StripPost(field.Name, "Opt")}({type} {CamelCase(field.Name)})");
                WriteLine("    {");

                // call update inside each setter
                Write("        return Update(");
                for (int f2 = 0; f2 < node.Fields.Count; f2++)
                {
                    var field2 = node.Fields[f2];
                    if (f2 > 0)
                        Write(", ");

                    if (field2 == field)
                    {
                        Write("{0}", CamelCase(field2.Name));
                    }
                    else
                    {
                        Write("{0}", field2.Name);
                    }
                }
                WriteLine(");");

                WriteLine("    }");
            }
        }

        private TreeType GetHighestBaseTypeWithField(TreeType node, string name)
        {
            TreeType bestType = null;
            for (var current = node; current != null; current = TryGetBaseType(current))
            {
                var fields = GetNodeOrNodeListFields(current);
                var field = fields.FirstOrDefault(f => f.Name == name);
                if (field != null)
                {
                    bestType = current;
                }
            }

            return bestType;
        }

        private TreeType TryGetBaseType(TreeType node)
            => node is AbstractNode an
                ? GetTreeType(an.Base)
                : node is Node n
                    ? GetTreeType(n.Base)
                    : null;

        private void WriteRedListHelperMethods(Node node)
        {
            for (int f = 0; f < node.Fields.Count; f++)
            {
                var field = node.Fields[f];

                if (IsAnyList(field.Type))
                {
                    // write list helper methods for list properties
                    WriteRedListHelperMethods(node, field);
                }
                else
                {
                    var referencedNode = TryGetNodeForNestedList(field);
                    if (referencedNode != null)
                    {
                        // look for list members...
                        for (int rf = 0; rf < referencedNode.Fields.Count; rf++)
                        {
                            var referencedNodeField = referencedNode.Fields[rf];
                            if (IsAnyList(referencedNodeField.Type))
                            {
                                WriteRedNestedListHelperMethods(node, field, referencedNode, referencedNodeField);
                            }
                        }
                    }
                }
            }
        }

        private Node TryGetNodeForNestedList(Field field)
        {
            Node referencedNode = GetNode(field.Type);
            if (referencedNode != null && (!IsOptional(field) || RequiredFactoryArgumentCount(referencedNode) == 0))
            {
                return referencedNode;
            }

            return null;
        }

        private void WriteRedListHelperMethods(Node node, Field field)
        {
            var argType = GetElementType(field.Type);

            var isNew = false;
            if (IsOverride(field))
            {
                var baseType = GetHighestBaseTypeWithField(node, field.Name);
                if (baseType != null)
                {
                    WriteLine("    internal override {0} Add{1}Core(params {2}[] items) => Add{1}(items);", baseType.Name, field.Name, argType);
                    isNew = true;
                }
            }

            WriteLine();
            WriteLine($"    public{(isNew ? " new " : " ")}{node.Name} Add{field.Name}(params {argType}[] items)");
            WriteLine("    {");
            WriteLine("        return With{0}(this.{1}.AddRange(items));", StripPost(field.Name, "Opt"), field.Name);
            WriteLine("    }");
        }

        private void WriteRedNestedListHelperMethods(Node node, Field field, Node referencedNode, Field referencedNodeField)
        {
            var argType = GetElementType(referencedNodeField.Type);

            var isNew = false;
            if (IsOverride(field))
            {
                var baseType = GetHighestBaseTypeWithField(node, field.Name);
                if (baseType != null)
                {
                    WriteLine("    internal override {0} Add{1}{2}Core(params {3}[] items) => Add{1}{2}(items);", baseType.Name, StripPost(field.Name, "Opt"), referencedNodeField.Name, argType);
                    isNew = true;
                }
            }

            // AddBaseListTypes
            WriteLine();
            WriteLine($"    public{(isNew ? " new " : " ")}{node.Name} Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}(params {argType}[] items)");
            WriteLine("    {");

            if (IsOptional(field))
            {
                var factoryName = StripPost(referencedNode.Name, "Syntax");
                var varName = StripPost(UnderscoreCamelCase(field.Name), "Opt");
                WriteLine("        var {0} = this.{1} ?? SyntaxFactory.{2}();", varName, field.Name, factoryName);
                WriteLine("        return this.With{0}({1}.With{2}({1}.{3}.AddRange(items)));", StripPost(field.Name, "Opt"), varName, StripPost(referencedNodeField.Name, "Opt"), referencedNodeField.Name);
            }
            else
            {
                WriteLine("        return this.With{0}(this.{1}.With{2}(this.{1}.{3}.AddRange(items)));", StripPost(field.Name, "Opt"), field.Name, StripPost(referencedNodeField.Name, "Opt"), referencedNodeField.Name);
            }

            WriteLine("    }");
        }

        private void WriteRedRewriter()
        {
            var nodes = Tree.Types.Where(n => !(n is PredefinedNode)).ToList();

            WriteLine();
            WriteLine("  internal partial class SyntaxRewriter : SyntaxVisitor<SyntaxNode>");
            WriteLine("  {");
            int nWritten = 0;
            for (int i = 0, n = nodes.Count; i < n; i++)
            {
                if (nodes[i] is Node node)
                {
                    var nodeFields = node.Fields.Where(nd => IsNodeOrNodeList(nd.Type)).ToList();

                    if (nWritten > 0)
                        WriteLine();
                    nWritten++;
                    WriteLine("    public override SyntaxNode Visit{0}({1} node)", StripPost(node.Name, "Syntax"), node.Name);
                    WriteLine("    {");
                    for (int f = 0; f < nodeFields.Count; f++)
                    {
                        var field = nodeFields[f];
                        if (IsAnyList(field.Type))
                        {
                            WriteLine("      var {0} = VisitList(node.{1});", CamelCase(field.Name), field.Name);
                        }
                        else if (field.Type == "SyntaxToken")
                        {
                            WriteLine("      var {0} = ({1})VisitToken(node.{2});", CamelCase(field.Name), field.Type, field.Name);
                        }
                        else
                        {
                            WriteLine("      var {0} = ({1})Visit(node.{2});", CamelCase(field.Name), field.Type, field.Name);
                        }
                    }
                    if (nodeFields.Count > 0)
                    {
                        Write("      return node.Update(");
                        for (int f = 0; f < node.Fields.Count; f++)
                        {
                            var field = node.Fields[f];
                            if (f > 0)
                                Write(", ");
                            if (IsNodeOrNodeList(field.Type))
                            {
                                Write(CamelCase(field.Name));
                            }
                            else
                            {
                                Write("node.{0}", field.Name);
                            }
                        }
                        WriteLine(");");
                    }
                    else
                    {
                        WriteLine("      return node;");
                    }
                    WriteLine("    }");
                }
            }
            WriteLine("  }");
        }

        private void WriteRedFactories()
        {
            var nodes = Tree.Types.Where(n => !(n is PredefinedNode) && !(n is AbstractNode)).OfType<Node>().ToList();
            WriteLine();
            WriteLine("  internal static partial class SyntaxFactory");
            WriteLine("  {");

            for (int i = 0, n = nodes.Count; i < n; i++)
            {
                var node = nodes[i];
                WriteRedFactory(node);
                WriteRedFactoryWithNoAutoCreatableTokens(node);
                WriteRedMinimalFactory(node);
                WriteRedMinimalFactory(node, withStringNames: true);
                WriteKindConverters(node);
            }

            WriteLine("  }");
        }

        protected bool CanBeAutoCreated(Node node, Field field)
        {
            return IsAutoCreatableToken(node, field) || IsAutoCreatableNode(node, field);
        }

        private bool IsAutoCreatableToken(Node node, Field field)
        {
            return field.Type == "SyntaxToken"
                && field.Kinds != null
                && ((field.Kinds.Count == 1 && field.Kinds[0].Name != "IdentifierToken" && !field.Kinds[0].Name.EndsWith("LiteralToken", StringComparison.Ordinal)) || (field.Kinds.Count > 1 && field.Kinds.Count == node.Kinds.Count));
        }

        private bool IsAutoCreatableNode(Node node, Field field)
        {
            var referencedNode = GetNode(field.Type);
            return (referencedNode != null && RequiredFactoryArgumentCount(referencedNode) == 0);
        }

        private bool IsRequiredFactoryField(Node node, Field field)
        {
            return (!IsOptional(field) && !IsAnyList(field.Type) && !CanBeAutoCreated(node, field)) || IsValueField(field);
        }

        private bool IsValueField(Field field)
        {
            return !IsNodeOrNodeList(field.Type);
        }

        private int RequiredFactoryArgumentCount(Node nd, bool includeKind = true)
        {
            int count = 0;

            // kind must be specified in factory
            if (nd.Kinds.Count > 1 && includeKind)
            {
                count++;
            }

            for (int i = 0, n = nd.Fields.Count; i < n; i++)
            {
                var field = nd.Fields[i];
                if (IsRequiredFactoryField(nd, field))
                {
                    count++;
                }
            }

            return count;
        }

        private int OptionalFactoryArgumentCount(Node nd)
        {
            int count = 0;
            for (int i = 0, n = nd.Fields.Count; i < n; i++)
            {
                var field = nd.Fields[i];
                if (IsOptional(field) || CanBeAutoCreated(nd, field) || IsAnyList(field.Type))
                {
                    count++;
                }
            }

            return count;
        }

        // full factory signature with nothing optional
        private void WriteRedFactory(Node nd)
        {
            WriteLine();

            var valueFields = nd.Fields.Where(n => IsValueField(n)).ToList();
            var nodeFields = nd.Fields.Where(n => !IsValueField(n)).ToList();

            WriteComment(string.Format(CultureInfo.InvariantCulture, "<summary>Creates a new {0} instance.</summary>", nd.Name), "    ");

            Write("    {0} static {1} {2}(", "public", nd.Name, StripPost(nd.Name, "Syntax"));
            WriteRedFactoryParameters(nd);

            WriteLine(")");
            WriteLine("    {");

            // validate kinds
            if (nd.Kinds.Count > 1)
            {
                WriteLine("      switch (kind)");
                WriteLine("      {");
                foreach (var kind in nd.Kinds)
                {
                    WriteLine("        case SyntaxKind.{0}:", kind.Name);
                }
                WriteLine("          break;");
                WriteLine("        default:");
                WriteLine("          throw new ArgumentException(\"kind\");");
                WriteLine("      }");
            }

            // validate parameters
            for (int i = 0, n = nodeFields.Count; i < n; i++)
            {
                var field = nodeFields[i];
                var pname = CamelCase(field.Name);

                if (field.Type == "SyntaxToken")
                {
                    if (field.Kinds != null && field.Kinds.Count > 0)
                    {
                        if (IsOptional(field))
                        {
                            WriteLine("      if ({0} != null)", CamelCase(field.Name));
                            WriteLine("      {");
                        }
                        WriteLine("      switch ({0}.Kind)", pname);
                        WriteLine("      {");
                        foreach (var kind in field.Kinds)
                        {
                            WriteLine("        case SyntaxKind.{0}:", kind.Name);
                        }
                        if (IsOptional(field))
                        {
                            WriteLine("        case SyntaxKind.None:");
                        }
                        WriteLine("          break;");
                        WriteLine("        default:");
                        WriteLine("          throw new ArgumentException(\"{0}\");", pname);
                        WriteLine("      }");
                        if (IsOptional(field))
                        {
                            WriteLine("      }");
                        }
                    }
                }
                else if (!IsAnyList(field.Type) && !IsOptional(field))
                {
                    WriteLine("      if ({0} == null)", CamelCase(field.Name));
                    WriteLine("        throw new ArgumentNullException(nameof({0}));", CamelCase(field.Name));
                }
            }

            Write("      return ({0})InternalSyntax.SyntaxFactory.{1}(", nd.Name, StripPost(nd.Name, "Syntax"));
            if (nd.Kinds.Count > 1)
            {
                Write("kind, ");
            }
            for (int i = 0, n = nodeFields.Count; i < n; i++)
            {
                var field = nodeFields[i];
                if (i > 0)
                    Write(", ");
                if (field.Type == "SyntaxToken")
                {
                    if (IsOptional(field))
                    {
                        Write("(Syntax.InternalSyntax.SyntaxToken){0}?.Green", CamelCase(field.Name));
                    }
                    else
                    {
                        Write("(Syntax.InternalSyntax.SyntaxToken){0}.Green", CamelCase(field.Name));
                    }
                }
                else if (field.Type == "SyntaxList<SyntaxToken>")
                {
                    Write("{0}.Node.ToGreenList<InternalSyntax.SyntaxToken>()", CamelCase(field.Name));
                }
                else if (IsNodeList(field.Type))
                {
                    Write("{0}.Node.ToGreenList<InternalSyntax.{1}>()", CamelCase(field.Name), GetElementType(field.Type));
                }
                else if (IsSeparatedNodeList(field.Type))
                {
                    Write("{0}.Node.ToGreenSeparatedList<InternalSyntax.{1}>()", CamelCase(field.Name), GetElementType(field.Type));
                }
                else if (field.Type == "SyntaxNodeOrTokenList")
                {
                    Write("{0}.Node.ToGreenList<GreenNode>()", CamelCase(field.Name));
                }
                else
                {
                    Write("{0} == null ? null : (InternalSyntax.{1}){0}.Green", CamelCase(field.Name), field.Type);
                }
            }

            // values are at end
            for (int i = 0, n = valueFields.Count; i < n; i++)
            {
                var field = valueFields[i];
                Write(", ");
                Write(CamelCase(field.Name));
            }

            WriteLine(").CreateRed();");
            WriteLine("    }");

            //WriteLine();
        }

        private void WriteRedFactoryParameters(Node nd)
        {
            if (nd.Kinds.Count > 1)
            {
                Write("SyntaxKind kind, ");
            }

            for (int i = 0, n = nd.Fields.Count; i < n; i++)
            {
                var field = nd.Fields[i];
                if (i > 0)
                    Write(", ");
                var type = GetRedPropertyType(field);

                Write("{0} {1}", type, CamelCase(field.Name));
            }
        }

        private string GetRedPropertyType(Field field)
        {
            //if (field.Type == "SyntaxList<SyntaxToken>")
            //    return "SyntaxTokenList";
            return field.Type;
        }

        private string GetDefaultValue(Node nd, Field field)
        {
            if (IsRequiredFactoryField(nd, field))
            {
                Console.WriteLine(nd.Name);
                Console.WriteLine(field.Name);
            }
            System.Diagnostics.Debug.Assert(!IsRequiredFactoryField(nd, field));

            if (IsOptional(field) || IsAnyList(field.Type))
            {
                return string.Format(CultureInfo.InvariantCulture, "default({0})", GetRedPropertyType(field));
            }
            else if (field.Type == "SyntaxToken")
            {
                // auto construct token?
                if (field.Kinds.Count == 1)
                {
                    return string.Format(CultureInfo.InvariantCulture, "SyntaxFactory.Token(SyntaxKind.{0})", field.Kinds[0].Name);
                }
                else
                {
                    return string.Format(CultureInfo.InvariantCulture, "SyntaxFactory.Token(Get{0}{1}Kind(kind))", StripPost(nd.Name, "Syntax"), StripPost(field.Name, "Opt"));
                }
            }
            else
            {
                var referencedNode = GetNode(field.Type);
                return string.Format(CultureInfo.InvariantCulture, "SyntaxFactory.{0}()", StripPost(referencedNode.Name, "Syntax"));
            }
        }

        // Writes Get<Property>Kind() methods for converting between node kind and member token kinds...
        private void WriteKindConverters(Node nd)
        {
            for (int f = 0; f < nd.Fields.Count; f++)
            {
                var field = nd.Fields[f];

                if (field.Type == "SyntaxToken" && CanBeAutoCreated(nd, field) && field.Kinds.Count > 1)
                {
                    WriteLine();
                    WriteLine("    private static SyntaxKind Get{0}{1}Kind(SyntaxKind kind)", StripPost(nd.Name, "Syntax"), StripPost(field.Name, "Opt"));
                    WriteLine("    {");

                    WriteLine("      switch (kind)");
                    WriteLine("      {");

                    for (int k = 0; k < field.Kinds.Count; k++)
                    {
                        var nKind = nd.Kinds[k];
                        var pKind = field.Kinds[k];
                        WriteLine("        case SyntaxKind.{0}:", nKind.Name);
                        WriteLine("          return SyntaxKind.{0};", pKind.Name);
                    }

                    WriteLine("        default:");
                    WriteLine("          throw new ArgumentOutOfRangeException();");
                    WriteLine("      }");
                    WriteLine("    }");
                }
            }
        }

        private IEnumerable<Field> DetermineRedFactoryWithNoAutoCreatableTokenFields(Node nd)
        {
            return nd.Fields.Where(f => !IsAutoCreatableToken(nd, f));
        }

        // creates a factory without auto-creatable token arguments
        private void WriteRedFactoryWithNoAutoCreatableTokens(Node nd)
        {
            var nAutoCreatableTokens = nd.Fields.Count(f => IsAutoCreatableToken(nd, f));
            if (nAutoCreatableTokens == 0)
                return; // already handled by general factory

            var factoryWithNoAutoCreatableTokenFields = new HashSet<Field>(DetermineRedFactoryWithNoAutoCreatableTokenFields(nd));
            var minimalFactoryFields = DetermineMinimalFactoryFields(nd);
            if (minimalFactoryFields != null && factoryWithNoAutoCreatableTokenFields.SetEquals(minimalFactoryFields))
            {
                return; // will be handled in minimal factory case
            }

            WriteLine();

            WriteComment(string.Format(CultureInfo.InvariantCulture, "<summary>Creates a new {0} instance.</summary>", nd.Name), "    ");
            Write("    {0} static {1} {2}(", "public", nd.Name, StripPost(nd.Name, "Syntax"));

            bool hasPreviousParameter = false;
            if (nd.Kinds.Count > 1)
            {
                Write("SyntaxKind kind");
                hasPreviousParameter = true;
            }

            for (int i = 0, n = nd.Fields.Count; i < n; i++)
            {
                var field = nd.Fields[i];

                if (factoryWithNoAutoCreatableTokenFields.Contains(field))
                {
                    if (hasPreviousParameter)
                        Write(", ");

                    Write("{0} {1}", GetRedPropertyType(field), CamelCase(field.Name));

                    hasPreviousParameter = true;
                }
            }
            WriteLine(")");

            WriteLine("    {");

            Write("      return SyntaxFactory.{0}(", StripPost(nd.Name, "Syntax"));

            bool hasPreviousArgument = false;
            if (nd.Kinds.Count > 1)
            {
                Write("kind");
                hasPreviousArgument = true;
            }

            for (int i = 0, n = nd.Fields.Count; i < n; i++)
            {
                var field = nd.Fields[i];

                if (hasPreviousArgument)
                    Write(", ");

                if (factoryWithNoAutoCreatableTokenFields.Contains(field))
                {
                    // pass supplied parameter on to general factory
                    Write("{0}", CamelCase(field.Name));
                }
                else
                {
                    // pass an auto-created token to the general factory
                    Write("{0}", GetDefaultValue(nd, field));
                }

                hasPreviousArgument = true;
            }

            WriteLine(");");

            WriteLine("    }");
        }

        private Field DetermineMinimalOptionalField(Node nd)
        {
            // first if there is a single list, then choose the list because it would not have been optional
            int listCount = nd.Fields.Count(f => IsAnyNodeList(f.Type));
            if (listCount == 1)
            {
                return nd.Fields.First(f => IsAnyNodeList(f.Type));
            }
            else
            {
                // otherwise, if there is a single optional node, use that..
                int nodeCount = nd.Fields.Count(f => IsNode(f.Type) && f.Type != "SyntaxToken");
                if (nodeCount == 1)
                {
                    return nd.Fields.First(f => IsNode(f.Type) && f.Type != "SyntaxToken");
                }
                else
                {
                    return null;
                }
            }
        }

        private IEnumerable<Field> DetermineMinimalFactoryFields(Node nd)
        {
            // special case to allow a single optional argument if there would have been no arguments
            // and we can determine a best single argument.
            Field allowOptionalField = null;

            var optionalCount = OptionalFactoryArgumentCount(nd);
            if (optionalCount == 0)
            {
                return null; // no fields...
            }

            var requiredCount = RequiredFactoryArgumentCount(nd, includeKind: false);
            if (requiredCount == 0 && optionalCount > 1)
            {
                allowOptionalField = DetermineMinimalOptionalField(nd);
            }

            return nd.Fields.Where(f => IsRequiredFactoryField(nd, f) || allowOptionalField == f);
        }

        // creates a factory with only the required arguments (everything else is defaulted)
        private void WriteRedMinimalFactory(Node nd, bool withStringNames = false)
        {
            var optionalCount = OptionalFactoryArgumentCount(nd);
            if (optionalCount == 0)
                return; // already handled w/ general factory method

            var minimalFactoryfields = new HashSet<Field>(DetermineMinimalFactoryFields(nd));

            if (withStringNames && !minimalFactoryfields.Any(f => IsRequiredFactoryField(nd, f) && CanAutoConvertFromString(f)))
                return; // no string-name overload necessary

            WriteLine();

            WriteComment(string.Format(CultureInfo.InvariantCulture, "<summary>Creates a new {0} instance.</summary>", nd.Name), "    ");
            Write("    {0} static {1} {2}(", "public", nd.Name, StripPost(nd.Name, "Syntax"));

            bool hasPreviousParameter = false;
            if (nd.Kinds.Count > 1)
            {
                Write("SyntaxKind kind");
                hasPreviousParameter = true;
            }

            for (int i = 0, n = nd.Fields.Count; i < n; i++)
            {
                var field = nd.Fields[i];

                if (minimalFactoryfields.Contains(field))
                {
                    var type = GetRedPropertyType(field);

                    if (IsRequiredFactoryField(nd, field))
                    {
                        if (hasPreviousParameter)
                            Write(", ");

                        if (withStringNames && CanAutoConvertFromString(field))
                        {
                            type = "string";
                        }

                        Write("{0} {1}", type, CamelCase(field.Name));

                        hasPreviousParameter = true;
                    }
                    else
                    {
                        if (hasPreviousParameter)
                            Write(", ");

                        Write("{0} {1} = default({0})", type, CamelCase(field.Name));

                        hasPreviousParameter = true;
                    }
                }
            }
            WriteLine(")");

            WriteLine("    {");

            Write("      return SyntaxFactory.{0}(", StripPost(nd.Name, "Syntax"));

            bool hasPreviousArgument = false;
            if (nd.Kinds.Count > 1)
            {
                Write("kind");
                hasPreviousArgument = true;
            }

            for (int i = 0, n = nd.Fields.Count; i < n; i++)
            {
                var field = nd.Fields[i];

                if (hasPreviousArgument)
                    Write(", ");

                if (minimalFactoryfields.Contains(field))
                {
                    if (IsRequiredFactoryField(nd, field))
                    {
                        if (withStringNames && CanAutoConvertFromString(field))
                        {
                            Write("{0}({1})", GetStringConverterMethod(field), CamelCase(field.Name));
                        }
                        else
                        {
                            Write("{0}", CamelCase(field.Name));
                        }
                    }
                    else
                    {
                        if (IsOptional(field) || IsAnyList(field.Type))
                        {
                            Write("{0}", CamelCase(field.Name));
                        }
                        else
                        {
                            Write("{0} ?? {1}", CamelCase(field.Name), GetDefaultValue(nd, field));
                        }
                    }
                }
                else
                {
                    var defaultValue = GetDefaultValue(nd, field);
                    Write(defaultValue);
                }

                hasPreviousArgument = true;
            }

            WriteLine(");");

            WriteLine("    }");
        }

        private bool CanAutoConvertFromString(Field field)
        {
            return IsIdentifierToken(field) || IsIdentifierNameSyntax(field);
        }

        private bool IsIdentifierToken(Field field)
        {
            return field.Type == "SyntaxToken" && field.Kinds != null && field.Kinds.Count == 1 && field.Kinds[0].Name == "IdentifierToken";
        }

        private bool IsIdentifierNameSyntax(Field field)
        {
            return field.Type == "IdentifierNameSyntax";
        }

        private string GetStringConverterMethod(Field field)
        {
            if (IsIdentifierToken(field))
            {
                return "SyntaxFactory.Identifier";
            }
            else if (IsIdentifierNameSyntax(field))
            {
                return "SyntaxFactory.IdentifierName";
            }
            else
            {
                throw new NotSupportedException();
            }
        }

        /// <summary>
        /// Anything inside a &lt;Comment&gt; tag gets written out (escaping untouched) as the
        /// XML doc comment.  Line breaks will be preserved.
        /// </summary>
        private void WriteComment(string comment, string indent)
        {
            if (comment != null)
            {
                var lines = comment.Split(new string[] { "\r", "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var line in lines.Where(l => !string.IsNullOrWhiteSpace(l)))
                {
                    WriteLine("{0}/// {1}", indent, line.TrimStart());
                }
            }
        }

        /// <summary>
        /// Anything inside a &lt;Comment&gt; tag gets written out (escaping untouched) as the
        /// XML doc comment.  Line breaks will be preserved.
        /// </summary>
        private void WriteComment(Comment comment, string indent)
        {
            if (comment != null)
            {
                foreach (XmlElement element in comment.Body)
                {
                    string[] lines = element.OuterXml.Split(new string[] { "\r", "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (string line in lines.Where(l => !string.IsNullOrWhiteSpace(l)))
                    {
                        WriteLine("{0}/// {1}", indent, line.TrimStart());
                    }
                }
            }
        }
    }
}
