using System;
using System.Collections.Generic;
using System.Linq;
using Loyc;
using Loyc.Collections;
using Loyc.Syntax;

namespace LeMP
{
	/// <summary>This interface provides services offered by the lexical macro processor (LeMP).</summary>
	/// <remarks>Macros receive this as their second argument (see <see cref="LexicalMacro"/>)</remarks>
	public interface IMacroContext : IMessageSink
	{
		/// <summary>Returns the message sink, used for writing warnings and errors.</summary>
		/// <remarks>For backward compatibility, IMacroContext itself implements 
		/// IMessageSink also.</remarks>
		IMessageSink Sink { get; }

		/// <summary>Returns a table of "properties" (arbitrary key-value pairs)
		/// that exist in the current scope. This dictionary is "persistent" in the
		/// computer science sense; any changes to these properties affect only the
		/// current scope and child scopes. When the current scope ends, the set of 
		/// properties that existed in the parent scope are restored.</summary>
		/// <remarks>
		/// Scopes are bounded by curly brace nodes (Call nodes named "{}").
		/// </remarks>
		IDictionary<object, object> ScopedProperties { get; }

		/// <summary>Returns a list of ancestors of the current node being 
		/// processed. Normally Ancestors[0] is a #splice node that contains a list 
		/// of all top-level statements in the file, and Ancestors.Last() is the
		/// current node.</summary>
		/// <remarks>You would expect that Ancestors[N] would contain Ancestors[N+1]
		/// as part of the attributes, target or arguments, but this is not always
		/// true. The ancestor list contains original versions of each node; when a
		/// child node is changed by a macro, the parent is not updated in this 
		/// list, but macro processing continues for the descendants of that child,
		/// so the ancestor list may occasionally seem incoherent.</remarks>
		IListSource<LNode> Ancestors { get; }

		/// <summary>Returns a list of nodes that are previous siblings of the current 
		/// node, and have already been processed by the macro processor.</summary>
		/// <remarks>
		/// Previous nodes cannot be altered and will typically end up in the output.
		/// However, if the current macro was invoked because another macro called
		/// PreProcess or <see cref="PreProcessChildren()"/>, the other macro will
		/// not necessarily include the results of the preprocess operation in its
		/// own output. In that case, the output will include neither PreviousSiblings 
		/// nor the output from the current macro.
		/// <para/>
		/// This property is the same as AncestorsAndPreviousSiblings.Last().Item1.
		/// </remarks>
		IListSource<LNode> PreviousSiblings { get; }

		/// <summary>Returns a list of pairs in which the second item is an ancestor
		/// of the current node (except the final item, which is the current node)
		/// and the first item is the previously processed siblings of that node.</summary>
		/// <remarks>
		/// For example, given code such as this:
		/// <code>
		///   using System;
		///   class C {
		///       int x;
		///       int y;
		///       foo();
		///       oof();
		///   }
		/// </code>
		/// If the macro foo() is being processed, this property will contain two
		/// items. The first item is
		/// <code>
		///   ( LNode.List(quote { using System; }), 
		///     quote {
		///       class C {
		///         int x;
		///         int y;
		///         foo();
		///         oof();
		///     } )
		/// </code>
		/// And the second is 
		/// <code>
		///   (LNode.List(quote { int x; }, quote { int y; }), quote(foo()))
		/// </code>
		/// </remarks>
		IListSource<Pair<IListSource<LNode>, LNode>> AncestorsAndPreviousSiblings { get; }

		/// <summary>Gets the logical parent of the current node, which is 
		/// <c>Ancestors[Ancestors.Count - 2]</c>, or null if the current node
		/// is the root node.</summary>
		/// <remarks>Please note that the current node may not actually exist in 
		/// the parent node due to changes made earlier to the current node by 
		/// other macros (or even the same macro); the Parent property still 
		/// returns the old version of the parent node.</remarks>
		LNode Parent { get; }

		/// <summary>Applies all available macros to the current node's children 
		/// and returns the result.</summary>
		/// <remarks>
		/// This method only processes children once. If this method is called 
		/// again for the same node, it returns a cached result.
		/// <para/>
		/// If the currently-running macro fails, the result may be thrown away
		/// and the effort of processing the children will have been wasted. If
		/// the macro succeeds, and its <see cref="LexicalMacroAttribute"/> uses
		/// the default <c>MacroMode.Normal</c> processing mode, the children 
		/// will (normally) be processed again after the macro returns.
		/// </remarks>
		LNode PreProcessChildren();

		/// <summary>Gets a list of the remaining nodes (arguments/statements or 
		/// attributes) after this point in the code stream.</summary>
		/// <remarks>
		/// The list is null when processing a target node.
		/// <para/>
		/// For example, if your macro is called "macro" and it appears in the
		/// following context:
		/// <code>{
		///   a();
		///   macro(b());
		///   c();
		///   d();
		/// }</code>
		/// Then this list will contain two items, c() and d(). Similarly, if
		/// the context is <c>[a, b, macro(c), d, e]</c> then the list will contain
		/// the items d and e.
		/// </remarks>
		IListSource<LNode> RemainingNodes { get; }

		/// <summary>Returns true if the current node is in the attribute list of 
		/// its immediate parent.</summary>
		bool IsAttribute { get; }

		/// <summary>Returns true if the current node is the target of its parent
		/// call node.</summary>
		bool IsTarget { get; }

		/// <summary>Gets or sets a value that indicates whether to drop all 
		/// remaining node after the current one when the current macro returns.
		/// This property has no effect if the macro rejects the input by returning 
		/// null.</summary>
		/// <remarks>See remarks at <see cref="MacroContext.GetArgsAndBody"/>.</remarks>
		bool DropRemainingNodes { get; set; }

		/// <summary>Runs the macro processor on the specified node(s).</summary>
		/// <param name="input">The node or node list to process.</param>
		/// <param name="asRoot">If false, the nodes are treated as children of the 
		///   current node (using the current list of ancestors as a basis); if true,
		///   the list of parent nodes is cleared.</param>
		/// <param name="resetOpenNamespaces">If false, the set of open namespaces
		///   stays the same; if true it is cleared to the set of pre-opened 
		///   namespaces (<see cref="MacroProcessor.PreOpenedNamespaces"/>) and
		///   macros defined with <see cref="RegisterMacro"/> are forgotten.</param>
		/// <param name="resetProperties">If true, <see cref="ScopedProperties"/>
		///   is reset to contain only predefined properties.</param>
		/// <param name="areAttributes">This parameter controls the value of 
		///   <see cref="IsAttribute"/> while the items in the list are processed.</param>
		/// <remarks>The node(s)</remarks>
		LNodeList PreProcess(LNodeList input, bool asRoot = false, bool resetOpenNamespaces = false, bool resetProperties = false, bool areAttributes = false);
		/// <inheritdoc cref="PreProcess(LNodeList, bool, bool, bool, bool)"/>
		LNode PreProcess(LNode input, bool asRoot = false, bool resetOpenNamespaces = false, bool resetProperties = false, bool isTarget = false);

		/// <summary>Gets information about all macros registered with the macro 
		/// processor, including macros whose namespace has not been opened with
		/// <c>#importMacros</c>.</summary>
		IReadOnlyDictionary<Symbol, VList<MacroInfo>> AllKnownMacros { get; }

		/// <summary>Gets the next number to use as a suffix for temporary variables (without incrementing it).</summary>
		int NextTempCounter { get; }
		/// <summary>Gets the next number to use as a suffix for temporary variables, then increments it.</summary>
		int IncrementTempCounter();

		/// <summary>Registers a new macro in the current scope.</summary>
		/// <param name="macroInfo">Information about the macro</param>
		/// <remarks>
		/// The macro will be forgotten at the end of the current scope (regardless 
		/// of which namespace it is assigned to).
		/// <para/>
		/// Macros defined in child scopes do not shadow macros in outer scopes; 
		/// if there are macros with the same name in the outer scopes, conflicts are 
		/// handled in the same way as with groups of macros that are imported in the 
		/// same scope. For example, <c>Mode = <see cref="MacroMode.PriorityOverride"/></c>
		/// can be used to make macros that override normal-priority macros.</remarks>
		void RegisterMacro(MacroInfo macroInfo);

		/// <summary>Gets the list of namespaces that are being searched for macros in 
		/// the current scope. LeMP allows you to modify this collection.</summary>
		/// <remarks>The naming scheme of namespaces is not standardized in cases where
		/// the namespace name is not a "normal" identifier. It is recommended that 
		/// macros be placed in namespaces whose names are valid identifiers in most 
		/// languages (C++, C#, Java, Python, etc.). It's okay to put macros in nested
		/// namespaces; the Symbol.Name will have a format like "Namespace1.Namespace2".
		/// </remarks>
		ICollection<Symbol> OpenMacroNamespaces { get; }
	}

	/// <summary>Standard extension methods for <see cref="IMacroContext"/>.</summary>
	public static class MacroContext
	{
		public static LNode CurrentNode(this IMacroContext ctx) { return ctx.Ancestors[ctx.Ancestors.Count-1]; }
		
		/// <summary>Splits the current node into a pair of "argument" and "body" 
		/// lists, potentially treating ctx.RemainingNodes as the "body" list.</summary>
		/// <param name="ctx">Context of the current macro.</param>
		/// <param name="orRemainingNodes">Whether to use ctx.RemainingNodes as
		/// the second list if there is no {braces node} at the end of 
		/// ctx.CurrentNode().Args.</param>
		/// <returns>A pair where the first item is "arguments" and the second is 
		/// the "body". If no body was detected then the second list is empty and
		/// the first list is simply ctx.CurrentNode().Args.</returns>
		/// <remarks>
		/// EC# supports a syntax specially designed for macro calls: 
		/// <code>macroName(args) { stmts; }</code>
		/// This is stored as a call node with a body, in braces, as its final parameter,
		/// i.e. it is equivalent to
		/// <code>macroName(args, { stmts; });</code>
		/// A similar, but more general feature called "superexpressions" exists in LES.
		/// <para/>
		/// Some macros would additionally like to apply themselves to all remaining
		/// nodes in the current list of statements or expressions, i.e.
		/// <code>macroName(args); stmts;</code>
		/// LeMP supports this through the <see cref="IMacroContext.DropRemainingNodes"/>
		/// and <see cref="IMacroContext.RemainingNodes"/> APIs. If your macro wants
		/// to apply itself to all remaining statements or expressions in the 
		/// current sequence of nodes, it can set the DropRemainingNodes property 
		/// to true and then simply incorporate RemainingNodes into its own output
		/// (if you need to return multiple statements from your macro, use 
		/// <c>list.AsLNode(CodeSymbols.Splice)</c> to convert a VList{LNode} to an 
		/// LNode.)
		/// <para/>
		/// This extension method helps you by detecting whether the current node
		/// has a body in braces or not. If the braces are present, the returned
		/// pair consists of the args shortened by one (i.e.
		/// <c>ctx.CurrentNode().Args.WithoutLast(1)</c>) and the Args of the "{}"
		/// braces node. Otherwise, <c>ctx.CurrentNode().Args</c> is the first item
		/// in the pair.
		/// <para/>
		/// In the latter case, if <c>orRemainingNodes</c> then this method sets
		/// <c>ctx.DropRemainingNodes</c> to true and uses <c>ctx.RemainingNodes</c>
		/// as the second list. Otherwise the second list is left blank.
		/// </remarks>
		public static Pair<LNodeList, LNodeList> GetArgsAndBody(this IMacroContext ctx, bool orRemainingNodes)
		{
			var node = ctx.CurrentNode();
			var args = node.Args;
			LNode last = null;
			var body = LNode.List();
			if (node.ArgCount != 0 && (last = args.Last).Calls(CodeSymbols.Braces)) {
				body = last.Args;
				args = args.WithoutLast(1);
			} else if (orRemainingNodes && ctx.RemainingNodes != null) {
				body = LNode.List(ctx.RemainingNodes);
				ctx.DropRemainingNodes = true;
			} 
			return Pair.Create(args, body);
		}

		/// <summary>Transforms an option list in the format <c>option1(v1), option2(v2)</c> 
		/// or <c>option1: v1, option2: v2</c> into a sequence of (key, value) pairs.
		/// If the format of a given node is invalid, this function yields <c>(node, null)</c>.</summary>
		/// <remarks>
		/// <c>option1: v1, option2: v2</c> is parsed into <c>@`'::=`(option1, v1), 
		/// @`'::=`(option2, v2)</c> in EC# or <c>@`':`(option1, v1), @`':`(option2, v2)</c> in LES.
		/// </remarks>
		public static IEnumerable<KeyValuePair<LNode, LNode>> GetOptions(LNodeList optionList)
		{
			Symbol lesNamedArg = (Symbol)"'<:";
			foreach (var option in optionList) {
				if ((option.ArgCount == 2 && option.Name.IsOneOf(CodeSymbols.NamedArg, CodeSymbols.Colon, lesNamedArg)) && option.Args[0].IsId)
				{
					LNode key = option.Args[0];
					LNode value = option.Args.Last;
					yield return new KeyValuePair<LNode, LNode>(key, value);
				}
				else if (option.Args.Count == 1 && option.Target.IsId)
					yield return new KeyValuePair<LNode, LNode>(option.Target, option.Args[0]);
				else
					yield return new KeyValuePair<LNode, LNode>(option, null);
			}
		}
	}
}
