context: using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.IO;
  using System.Numerics;
  using Loyc.Utilities;
  using S = Loyc.Syntax.CodeSymbols;
  using System.Diagnostics;
  using Loyc.Syntax.Lexing;
  using Loyc.Collections;
  using System.Globalization;
  using Loyc.Syntax.Impl;
  
  namespace Loyc.Syntax.Les
  {
  	/// <summary>Prints a Loyc tree in LES (Loyc Expression Syntax) format.</summary>
  	/// <remarks>Unless otherwise noted, the default value of all options is false.</remarks>
  	public class Les2Printer
  	{
  		Les2PrinterWriter _out;
  		IMessageSink _errors;
  
  		public IMessageSink ErrorSink { get { return _errors; } set { _errors = value ?? MessageSink.Null; } }
  
  		Les2PrinterOptions _o;
  		public Les2PrinterOptions Options { get { return _o; } }
  		public void SetOptions(ILNodePrinterOptions options)
  		{
  			_o = options as Les2PrinterOptions ?? new Les2PrinterOptions(options);
  		}
  
  		#region Constructors and default Printer
  		
  		[ThreadStatic]
  		static Les2Printer _printer;
  
  		internal static void Print(ILNode node, StringBuilder target, IMessageSink sink, ParsingMode mode, ILNodePrinterOptions options = null)
  		{
  			var p = _printer = _printer ?? new Les2Printer(new StringBuilder(), null);
  			var oldOptions = p._o;
  			var oldWriter = p._out;
  			var oldSink = p.ErrorSink;
  			p.ErrorSink = sink;
  			p.SetOptions(options);
  			p._out = new Les2PrinterWriter(target, p.Options.IndentString ?? "\t", p.Options.NewlineString ?? "\n", "", p.Options.SaveRange);
  
  			if (mode == ParsingMode.Expressions)
  				p.Print(node, StartStmt, "");
  			else
  				p.Print(node, StartStmt, ";");
  
  			p._out = oldWriter;
  			p._o = oldOptions;
  			p.ErrorSink = oldSink;
  		}
  
  		internal Les2Printer(StringBuilder target, ILNodePrinterOptions options = null)
  		{
  			SetOptions(options);
  			_out = new Les2PrinterWriter(target, _o.IndentString ?? "\t", _o.NewlineString ?? "\n");
  		}
  
  		#endregion
  
  		/// <summary>Top-level non-static printing method</summary>
  		internal void Print(ILNode node, Precedence context, string terminator = null)
  		{
  			_out.BeginNode(node);
  			int parenCount = WriteAttrs(node, ref context);
  			_out.FlushIndent().Indent(PrinterIndentHint.Subexpression);
  
  			if (!node.IsCall() || node.BaseStyle() == NodeStyle.PrefixNotation) {
  				PrintPrefixNotation(node, context);
  			} else do {
  				if (AutoPrintBracesOrBracks(node, context))
  					break;
  				if (!LesPrecedence.Primary.CanAppearIn(context)) {
  					_out.WriteOpening("(@[] ");
  					parenCount++;
  					context = StartSubexpr;
  				}
  				int args = node.ArgCount();
  				if (args == 1 && AutoPrintPrefixOrSuffixOp(node, context))
  					break;
  				if (args == 2 && AutoPrintInfixOp(node, context))
  					break;
  				PrintPrefixNotation(node, context);
  			} while (false);
  
  			_out.Dedent();
  			PrintSuffixTrivia(node, parenCount, terminator);
  			_out.EndNode();
  		}
  
  		#region Infix, prefix and suffix operators
  
  		private bool AutoPrintInfixOp(ILNode node, Precedence context)
  		{
  			var prec = GetPrecedenceIfOperator(node, node.Name, OperatorShape.Infix, context);
  			if (prec == null)
  				return false;
  			Print(node[0], prec.Value.LeftContext(context));
  			SpaceIf(prec.Value.Lo < _o.SpaceAroundInfixStopPrecedence);
  			bool sa = prec.Value.Lo < _o.SpaceAroundInfixStopPrecedence;
  			WriteOpName(node.Name, node.Target, prec.Value, spaceAfter: sa);
  			Print(node[1], prec.Value.RightContext(context));
  			return true;
  		}
  
  		private bool AutoPrintPrefixOrSuffixOp(ILNode node, Precedence context)
  		{
  			Symbol bareName;
  			if (Les2PrecedenceMap.ResemblesSuffixOperator(node.Name, out bareName) && Les2PrecedenceMap.IsNaturalOperator(bareName.Name)) {
  				var prec = GetPrecedenceIfOperator(node, bareName, OperatorShape.Suffix, context);
  				if (prec == null)
  					return false;
  				Print(node[0], prec.Value.LeftContext(context));
  				SpaceIf(prec.Value.Lo < _o.SpaceAfterPrefixStopPrecedence);
  				WriteOpName(bareName, node.Target, prec.Value);
  			} else {
  				var prec = GetPrecedenceIfOperator(node, bareName, OperatorShape.Prefix, context);
  				if (prec == null)
  					return false;
  				var spaceAfter = prec.Value.Lo < _o.SpaceAfterPrefixStopPrecedence;
  				WriteOpName(node.Name, node.Target, prec.Value, spaceAfter);
  				Print(node[0], prec.Value.RightContext(context));
  			}
  			return true;
  		}
  		
  		private void WriteOpName(Symbol op, ILNode target, Precedence prec, bool spaceAfter = false)
  		{
  			_out.BeginNode(target);
  
  			// Note: if the operator has a space after it, there's a subtle reason why 
  			// we want to print that space before the trivia and not after. Consider
  			// the input "a == //comment\n    b". After trivia injection this becomes
  			// (@[@%trailing(@%SLComment("comment"))] @==)(a, @[@%newline] b).
  			// Because the injector associates the newline with a different node than the
  			// single-line comment, there's no easy way to strip out the newline during
  			// the parsing process. So, to make the trivia round-trip, we use 
  			// _out.Newline(pending: true) when printing a single-line comment, which 
  			// suppresses the newline if it is followed immediately by another newline.
  			// But if we print a space after the trivia, then this suppression does not
  			// occur and we end up with two newlines. Therefore, we must print the space first.
  			if (target.AttrCount() == 0)
  				target = null; // optimize the usual case
  			if (target != null)
  				PrintPrefixTrivia(target);
  			if (!Les2PrecedenceMap.IsNaturalOperator(op.Name))
  				PrintStringCore('`', false, op.Name);
  			else {
  				Debug.Assert(op.Name.StartsWith("'"));
  				_out.Write(op.Name.Substring(1));
  			}
  			SpaceIf(spaceAfter);
  			if (target != null)
  				PrintSuffixTrivia(target, 0, null);
  
  			_out.EndNode();
  		}
  
  		private void SpaceIf(bool cond)
  		{
  			if (cond) _out.Space();
  		}
  
  		protected Les2PrecedenceMap _prec = Les2PrecedenceMap.Default;
  
  		private Precedence? GetPrecedenceIfOperator(ILNode node, Symbol opName, OperatorShape shape, Precedence context)
  		{
  			int ac = node.ArgCount();
  			if ((ac == (int)shape || ac == -(int)shape) && HasTargetIdWithoutPAttrs(node))
  			{
  				var bs = node.BaseStyle();
  				bool naturalOp = Les2PrecedenceMap.IsNaturalOperator(opName.Name);
  				if ((naturalOp && bs != NodeStyle.PrefixNotation) ||
  					(bs == NodeStyle.Operator && node.Name != null))
  				{
  					var result = _prec.Find(shape, opName);
  					if (!result.CanAppearIn(context) || !result.CanMixWith(context))
  						return null;
  					return result;
  				}
  			}
  			return null;
  		}
  
  		private bool HasTargetIdWithoutPAttrs(ILNode node)
  		{
  			var t = node.Target;
  			return t.IsId() && !HasPAttrs(t);
  		}
  		
  		#endregion
  
  		#region Other stuff: braces, TODO: superexpressions, indexing, generics, tuples
  
  		private bool AutoPrintBracesOrBracks(ILNode node, Precedence context)
  		{
  			var name = node.Name;
  			if (name == S.Array && node.IsCall() && !HasPAttrs(node.Target)) {
  				PrintArgList(node.Args(), node.BaseStyle() == NodeStyle.StatementBlock, "[", ']', null, node.Target);
  				return true;
  			} else if (name == S.Tuple && node.ArgCount() != 1 && node.IsCall() && !HasPAttrs(node.Target)) {
  				PrintArgList(node.Args(), node.BaseStyle() == NodeStyle.StatementBlock, "(", ')', "; ", node.Target);
  				return true;
  			} else if (name == S.Braces && node.IsCall() && !HasPAttrs(node.Target)) {
  				if (context.Lo == StartStmt.Lo)
  					_out.Dedent(PrinterIndentHint.Subexpression).Indent(PrinterIndentHint.NoIndent);
  				PrintArgList(node.Args(), node.BaseStyle() != NodeStyle.Expression, "{", '}', null, node.Target);
  				return true;
  			}
  			return false;
  		}
  
  		private void PrintArgList(NegListSlice<ILNode> args, bool stmtMode, string leftDelim, char rightDelim, string separator = null, ILNode target = null)
  		{
  			if (target != null)
  				PrintPrefixTrivia(target);
  			_out.Write(leftDelim);
  			_out.JustWroteSymbolOrSpecialId = false;
  			if (target != null)
  				PrintSuffixTrivia(target, 0, "");
  			if (stmtMode) {
  				_out.Indent();
  				bool anyNewlines = false;
  				var  childContext = rightDelim == '}' ? StartStmt : StartSubexpr;
  				separator = separator ?? ";";
  				foreach (var stmt in args) {
  					if ((_o.PrintTriviaExplicitly || stmt.AttrNamed(S.TriviaAppendStatement) == null)) {
  						_out.Newline();
  						anyNewlines = true;
  					} else
  						SpaceIf(_o.SpacesBetweenAppendedStatements);
  					Print(stmt, childContext, separator);
  				}
  				_out.Dedent();
  				if (anyNewlines)
  					_out.Newline();
  				else
  					SpaceIf(_o.SpacesBetweenAppendedStatements);
  			} else {
  				for (int i = 0; i < args.Count; )
  					Print(args[i], StartSubexpr, ++i == args.Count ? "" : separator ?? ", ");
  			}
  			_out.Write(rightDelim);
  			_out.JustWroteSymbolOrSpecialId = false;
  		}
  
  		#endregion
  
  		/// <summary>Context: beginning of main expression (potential superexpression)</summary>
  		protected static readonly Precedence StartStmt = Precedence.MinValue;
  		/// <summary>Context: beginning of subexpression (potential superexpression)</summary>
  		protected static readonly Precedence StartSubexpr = new Precedence(Precedence.MinValue.Left + 1);
  
  		void PrintPrefixNotation(ILNode node, Precedence context)
  		{
  			switch(node.Kind) {
  				case LNodeKind.Id:
  					PrintIdOrSymbol(node.Name, false); break;
  				case LNodeKind.Literal:
  					PrintLiteralCore(node); break;
  				case LNodeKind.Call: default:
  					Print(node.Target, LesPrecedence.Primary.LeftContext(context), "(");
  					PrintArgList(node.Args(), node.BaseStyle() == NodeStyle.StatementBlock, "", ')', null);
  					break;
  			}
  		}
  
  		bool HasPAttrs(ILNode node)
  		{
  			foreach (var attr in node.Attrs())
  				if (!IsConsumedTrivia(attr))
  					return true;
  			return false;
  		}
  
  		private int WriteAttrs(ILNode node, ref Precedence context)
  		{
  			bool wroteBrack = false, needParen = (context.Lo > StartSubexpr.Lo);
  			int parenCount = 0;
  			foreach (var attr in node.Attrs()) {
  				if (attr.IsIdNamed(S.TriviaInParens)) {
  					MaybeCloseBrack(ref wroteBrack);
  					parenCount++;
  					_out.WriteOpening('(');
  					// need extra paren when writing @[..] because (@[..] ...) doesn't count as %inParens
  					needParen = true;
  					continue;
  				}
  				if (MaybePrintTrivia(attr, needSpace: false, testOnly: wroteBrack)) {
  					if (wroteBrack) {
  						MaybeCloseBrack(ref wroteBrack);
  						MaybePrintTrivia(attr, needSpace: false);
  					}
  					continue;
  				}
  
gt: 				if (!wroteBrack) {