//#define DUMP
//#define CIL
//#define DEREFERENCE_VTABLE

#if DEREFERENCE_VTABLE && !CIL
#error 'DEREFERENCE_VTABLE' requires 'CIL'
#endif

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading.Tasks;
using System.Threading;
using System.Runtime.InteropServices;

using miew.Debugging;
using miew.Enumerable;
using miew.Tokenization;

using SysType = System.Type;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Planning a quick-check strategy in advance preserves a specified order for checking (the ends of) multiple 
	/// quick-check paths, yet without unneccesarily re-traversing any part of the TFS.
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class QuickCheckStrategy
	{
		static readonly Char[] dot_space = { '.', ' ' };
		static readonly Char[] outer_trim = { '\"', '\'', '(', ')', '[', ']' };

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// opcodes for the quick-check strategy steps
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public enum Op
		{
			Top = 1,
			Feature = 2,
			Check = 3,
			ContinueFrom = 4,
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// a step in a quick-check strategy
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		[DebuggerDisplay("{ToString(),nq}")]
		public struct Entry
		{
			public Entry(Op op, int arg)
			{
				this.op = op;
				this.arg = arg;
				this.next_section = 0;
				this.ix_slot = 0;
			}
			public Entry(Op op, int arg, int next_section, int ix_slot)
			{
				this.op = op;
				this.arg = arg;
				this.next_section = next_section;
				this.ix_slot = ix_slot;
			}
			public Op op;
			public int arg;
			public int next_section;
			public int ix_slot;

#if DEBUG
			public override string ToString()
			{
				String s = String.Format("{0,6} -> {1:0000}", ix_slot == -1 ? "" : "[" + ix_slot.ToString("0000") + "]", next_section);
				switch (op)
				{
					case Op.ContinueFrom:
						s += String.Format("{0,13} {1:0000}", op, arg);
						break;
					case Op.Top:
					case Op.Check:
						s += String.Format("{0,13}", op);
						break;
					case Op.Feature:
						s += String.Format("{0,13} {1,4} ({2})",
							op,
							arg,
							Grammar._singleton.tm.feat_arr[arg].feature.ToUpper());
						break;
				}
				return s;
			}
#endif
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		TypeMgr tm;
		Entry[] entries;
		int c_slots;

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Constructor. Analyzes the specified paths to develop the strategy.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public QuickCheckStrategy(TypeMgr tm, IEnumerable<String> paths)
		{
			this.tm = tm;

			List<Entry> ent = new List<Entry>();
			Dictionary<String, int> path_map = new Dictionary<String, int>();
			Dictionary<int, int> slot_map = new Dictionary<int, int>();
			IList<String> rap = tm.config.grammar.rule_args_path;

			foreach (String _p in paths)
			{
				String[] path = _p.Trim(outer_trim).ToLower().Split(dot_space, StringSplitOptions.RemoveEmptyEntries);

				if (path.Take(rap.Count).SequenceEqual(rap))
					path = path.Skip(rap.Count).ToArray();

				int ix_continue;
				int j;
				for (j = path.Length; j > 0; j--)
				{
					if (path_map.TryGetValue(path.Take(j).StringJoin("."), out ix_continue))
					{
						if (!slot_map.ContainsKey(ix_continue))
							slot_map.Add(ix_continue, slot_map.Count);
						ent.Add(new Entry(Op.ContinueFrom, ix_continue));
						goto emit_start;
					}
				}
				ent.Add(new Entry(Op.Top, 0));
				ix_continue = -1;

			emit_start:
				for (; j < path.Length; j++)
				{
					if (ix_continue != -1)
						ix_continue = -1;
					else
					{
						String s_p = path.Take(j + 1).StringJoin(".");
						//Console.WriteLine("{0,3} {1}", ent.Count, s_p);
						path_map.Add(s_p, ent.Count);
					}
					ent.Add(new Entry(Op.Feature, tm.feat_map[path[j]].i_feat));
				}
				ent.Add(new Entry(Op.Check, 0));
			}

			c_slots = slot_map.Count;
			entries = new Entry[ent.Count];

			int next = ent.Count;
			for (int i = ent.Count - 1; i >= 0; i--)
			{
				Entry e = ent[i];

				int i_slot;
				if (!slot_map.TryGetValue(i, out i_slot))
					i_slot = -1;

				int arg = e.arg;
				if (e.op == Op.ContinueFrom)
					arg = slot_map[arg];

				entries[i] = new Entry(e.op, arg, next, i_slot);
				if (e.op == Op.ContinueFrom || e.op == Op.Top)
					next = i;
			}

#if DUMP
			int ix = 0;
			foreach (Entry e in entries)
				Console.WriteLine("{0:0000} {1}", ix++, e.ToString());
#endif
#if CIL
			/// Generate a customized, unrolled CIL function for this quick-check strategy.
			CanSkip = emit_CanSkip();
#endif
		}

#if CIL
		public delegate bool D(Tfs tfs1, Tfs tfs2);

		readonly public D CanSkip;

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Emit unrolled code which executes the strategy on the given TFSes at runtime.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public D emit_CanSkip()
		{
			const BindingFlags bf = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

			DynamicMethod dm = new DynamicMethod(
									String.Empty,
									typeof(bool),
									new SysType[]
										{
											typeof(QuickCheckStrategy),	// arg.0 'this'
											typeof(Tfs),				// arg.1 'tfs1'
											typeof(Tfs)					// arg.2 'tfs2'
										},
									typeof(QuickCheckStrategy),
									false);
			ILGenerator il = dm.GetILGenerator();

			Dictionary<int, Label> section_labels = entries
													.Select(ent => ent.next_section)
													.Distinct()
													.ToDictionary(eix => eix, eix => il.DefineLabel());
			Retrolabel _ret_true = default(Retrolabel);

			/// v===================== begin emiited code =====================v
			il.DeclareLocal(typeof(Edge*));						// loc.0 'rge1'
			il.DeclareLocal(typeof(Edge*));						// loc.1 'rge2'
			il.DeclareLocal(typeof(Edge));						// loc.2 'e1'
			il.DeclareLocal(typeof(Edge));						// loc.3 'e2'

#if DEREFERENCE_VTABLE
			il.DeclareLocal(typeof(IntPtr));					// loc.4 'Tfs.TryGetEdge'
			il.DeclareLocal(typeof(IntPtr));					// loc.5 'Tfs.TryGetEdge'

			/// loc.4 = &tfs1.TryGetEdge;
			il.Emit(OpCodes.Ldarg_1);
			il.Emit(OpCodes.Ldvirtftn, typeof(Tfs).GetMethod("TryGetEdge"));
			il.Emit(OpCodes.Stloc_S, (sbyte)4);

			/// loc.5 = &tfs2.TryGetEdge;
			il.Emit(OpCodes.Ldarg_2);
			il.Emit(OpCodes.Ldvirtftn, typeof(Tfs).GetMethod("TryGetEdge"));
			il.Emit(OpCodes.Stloc_S, (sbyte)5);

			String call_type = "calli";
#else
			String call_type = "callvirt";
#endif

			/// Edge* rge1 = stackalloc Edge[c_slots];
			il.EmitLdcI4(Marshal.SizeOf(typeof(Edge)) * c_slots);
			il.Emit(OpCodes.Localloc);
			il.Emit(OpCodes.Stloc_0);

			/// Edge* rge2 = stackalloc Edge[c_slots];
			il.EmitLdcI4(Marshal.SizeOf(typeof(Edge)) * c_slots);
			il.Emit(OpCodes.Localloc);
			il.Emit(OpCodes.Stloc_1);

			Debug.Assert(entries[0].op == Op.Top);
			int i_feat, i_slot, ix = 0;
			do
			{
				Label _sect_label;
				if (section_labels.TryGetValue(ix, out _sect_label))
					il.MarkLabel(_sect_label);

				switch (entries[ix].op)
				{
					case Op.Top:
						/// e1 = tfs1.Edge;
						il.Emit(OpCodes.Ldarg_1);
						il.Emit(OpCodes.Ldfld, typeof(Tfs).GetField("Edge", bf));
						il.Emit(OpCodes.Stloc_2);

						/// e2 = tfs2.Edge;
						il.Emit(OpCodes.Ldarg_2);
						il.Emit(OpCodes.Ldfld, typeof(Tfs).GetField("Edge", bf));
						il.Emit(OpCodes.Stloc_3);
						break;

					case Op.ContinueFrom:
						i_slot = entries[ix].arg;

						/// e1 = rge1[i_slot];
						il.Emit(OpCodes.Ldloc_0);
						il.EmitLdcI4(i_slot * Marshal.SizeOf(typeof(Edge)));
						il.Emit(OpCodes.Add);
						il.Emit(OpCodes.Ldobj, typeof(Edge));
						il.Emit(OpCodes.Stloc_2);

						/// e2 = rge2[i_slot];
						il.Emit(OpCodes.Ldloc_1);
						il.EmitLdcI4(i_slot * Marshal.SizeOf(typeof(Edge)));
						il.Emit(OpCodes.Add);
						il.Emit(OpCodes.Ldobj, typeof(Edge));
						il.Emit(OpCodes.Stloc_3);
						break;

					case Op.Feature:
						i_feat = entries[ix].arg;

						/// if ((e1.FlagsId & Edge.Flag.EtmNonBareType) == 0 || ...
						il.Emit(OpCodes.Ldloca_S, (sbyte)2);
						il.Emit(OpCodes.Ldfld, typeof(Edge).GetField("FlagsId", bf));
						il.EmitLdcI4((int)Edge.Flag.EtmNonBareType);
						il.Emit(OpCodes.And);
						il.Emit(OpCodes.Brfalse, section_labels[entries[ix].next_section]);

						/// ... (e2.FlagsId & Edge.Flag.EtmNonBareType) == 0 ...
						il.Emit(OpCodes.Ldloca_S, (sbyte)3);
						il.Emit(OpCodes.Ldfld, typeof(Edge).GetField("FlagsId", bf));
						il.EmitLdcI4((int)Edge.Flag.EtmNonBareType);
						il.Emit(OpCodes.And);
						il.Emit(OpCodes.Brfalse, section_labels[entries[ix].next_section]);

						/// ... !tfs1.TryGetEdge(arg, e1.Mark, out e1) ...
						il.Emit(OpCodes.Ldarg_1);
						il.EmitLdcI4(i_feat);
						il.Emit(OpCodes.Ldloca_S, (sbyte)2);
						il.Emit(OpCodes.Ldfld, typeof(Edge).GetField("Mark", bf));
						il.Emit(OpCodes.Ldloca_S, (sbyte)2);
#if DEREFERENCE_VTABLE
						il.Emit(OpCodes.Ldloc_S, (sbyte)4);
						il.EmitCalli(OpCodes.Calli,
									CallingConventions.HasThis,
									typeof(bool),
									new SysType[] { typeof(int), typeof(int), typeof(Edge).MakeByRefType() },
									null);
#else
						il.Emit(OpCodes.Callvirt, typeof(Tfs).GetMethod("TryGetEdge"));
#endif
						il.Emit(OpCodes.Brfalse, section_labels[entries[ix].next_section]);

						/// ... !tfs2.TryGetEdge(arg, e2.Mark, out e2)) ...
						il.Emit(OpCodes.Ldarg_2);
						il.EmitLdcI4(i_feat);
						il.Emit(OpCodes.Ldloca_S, (sbyte)3);
						il.Emit(OpCodes.Ldfld, typeof(Edge).GetField("Mark", bf));
						il.Emit(OpCodes.Ldloca_S, (sbyte)3);
#if DEREFERENCE_VTABLE
						il.Emit(OpCodes.Ldloc_S, (sbyte)5);
						il.EmitCalli(OpCodes.Calli,
									CallingConventions.HasThis,
									typeof(bool),
									new SysType[] { typeof(int), typeof(int), typeof(Edge).MakeByRefType() },
									null);
#else
						il.Emit(OpCodes.Callvirt, typeof(Tfs).GetMethod("TryGetEdge"));
#endif
						il.Emit(OpCodes.Brfalse, section_labels[entries[ix].next_section]);

						i_slot = entries[ix].ix_slot;
						if (i_slot != -1)
						{
							/// rge1[i_slot] = e1;
							il.Emit(OpCodes.Ldloc_0);
							il.EmitLdcI4(i_slot * Marshal.SizeOf(typeof(Edge)));
							il.Emit(OpCodes.Add);
							il.Emit(OpCodes.Ldloc_2);
							il.Emit(OpCodes.Stobj, typeof(Edge));

							/// rge2[i_slot] = e2;
							il.Emit(OpCodes.Ldloc_1);
							il.EmitLdcI4(i_slot * Marshal.SizeOf(typeof(Edge)));
							il.Emit(OpCodes.Add);
							il.Emit(OpCodes.Ldloc_3);
							il.Emit(OpCodes.Stobj, typeof(Edge));
						}
						break;

					case Op.Check:
						/// if (!tm.CanUnify(e1.FlagsId, e2.FlagsId)) ...
						il.Emit(OpCodes.Ldarg_0);
						il.Emit(OpCodes.Ldfld, typeof(QuickCheckStrategy).GetField("tm", bf));
						il.Emit(OpCodes.Ldloca_S, (sbyte)2);
						il.Emit(OpCodes.Ldfld, typeof(Edge).GetField("FlagsId", bf));
						il.Emit(OpCodes.Ldloca_S, (sbyte)3);
						il.Emit(OpCodes.Ldfld, typeof(Edge).GetField("FlagsId", bf));
						il.Emit(OpCodes.Call, typeof(TypeMgr)
												.GetMethod("CanUnify",
															bf,
															null,
															new SysType[] { typeof(Edge.Flag), typeof(Edge.Flag) },
															null));

						/// emit 'true' return code the first time it's needed
						if (_ret_true.Equals(default(Retrolabel)))
						{
							Label _skip = il.DefineLabel();
							il.Emit(OpCodes.Brtrue_S, _skip);

							/// return true;
							_ret_true = new Retrolabel(il);
							il.EmitLdcI4(1);
							il.Emit(OpCodes.Ret);
							il.MarkLabel(_skip);
						}
						else
							il.Emit(OpCodes.Brfalse, _ret_true);
						break;
				}
				ix++;
			}
			while (ix < entries.Length);

			/// return false;
			il.EmitLdcI4(0);
			il.Emit(OpCodes.Ret);
			/// ^===================== end emiited code =====================^

			Console.WriteLine("Compiled quick-check strategy to CIL (using '{0}'): {1} bytes", call_type, il.ILOffset);
			return (D)dm.CreateDelegate(typeof(D), this);
		}

#else
		struct EdgePair
		{
			public Edge e1;
			public Edge e2;
		};

		struct EdgePair2
		{
			public Edge.Flag e1_f;
			public int e1_m;
			public Edge.Flag e2_f;
			public int e2_m;
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Execute the strategy on the given TFSes at runtime.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public unsafe bool CanSkip(Tfs tfs1, Tfs tfs2)
		{
			EdgePair* _rge = stackalloc EdgePair[c_slots + 1];
			EdgePair* rge = _rge + 1;

			EdgePair _ep_top;
			_ep_top.e1 = tfs1.Edge;
			_ep_top.e2 = tfs2.Edge;
			if (tfs2 is TfsSection)
				tfs2 = ((TfsSection)tfs2).mother;

			Debug.Assert(entries[0].op == Op.Top);
			EdgePair* ep = &_ep_top;

			fixed (Entry* _pent = entries)
			{
				for (Entry* pent_cur = _pent + 1, pent_end = pent_cur + entries.Length; pent_cur < pent_end; )
				{
					switch (pent_cur->op)
					{
						case Op.Top:
							ep = &_ep_top;
							break;
						case Op.ContinueFrom:
							ep = rge + pent_cur->arg;
							break;
						case Op.Feature:
							if ((ep->e1.FlagsId & ep->e2.FlagsId & Edge.Flag.EtmNonBareType) != 0)
							{
								int i_feat;
								EdgePair2* ep_next = (EdgePair2*)(rge + pent_cur->ix_slot);		// -1 is ok ix_slot here
								if ((ep_next->e1_f = tfs1.TryGetFlagsMark((i_feat = pent_cur->arg), ep->e1.Mark, out ep_next->e1_m)) != 0 &&
									(ep_next->e2_f = tfs2.TryGetFlagsMark(i_feat, ep->e2.Mark, out ep_next->e2_m)) != 0)
								{
									ep = (EdgePair*)ep_next;
									break;
								}
							}
							pent_cur = _pent + pent_cur->next_section;
							continue;	// note: not incrementing 'pent_cur'
						case Op.Check:
							Edge.Flag f1, f2;
							if ((f1 = ep->e1.FlagsId) != 0 && f1 != (f2 = ep->e2.FlagsId) && f2 != 0 && !tm.CanUnify(f1, f2))
								return true;
							break;
					}
					pent_cur++;
				}
			}
			return false;
		}
#endif
	};
}