//#define HEAP_PROCESS
//#define HEAP_PRIVATE_STATIC
//#define HEAP_PRIVATE
//#define GLOBAL_ALLOC
#define VIRTUAL_ALLOC

using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;

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

#pragma warning disable 0649

using Va = miew.Memory.Virtual;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// ParseControl : each instance of this class coordinates all aspects of parsing a single sentence:
	/// - morphology phase (note: but not tokenization)
	/// - coverage and chart dependencies
	/// - construct parse chart
	/// - main parse/packing phase
	/// - derivations/unpacking phase
	/// - statistics management
	/// - task management, concurrency, system options
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed partial class ParseControl : Allocator, ISysObj, IDisposable
	{
		public ParseControl(Grammar g)
			: base(g.tm)
		{
			this.config = g.config;
			this.parser_config = config.parser;
			this.g = g;
			this.tm = g.tm;
			this.lexicon = g.lex;
			this.stats = new Stats(this);

			tf = new TaskFactory(
						CancellationToken.None,
						TaskCreationOptions.AttachedToParent,
						TaskContinuationOptions.AttachedToParent,
						TaskScheduler.Default);
		}

		public ParseControl(Grammar g, TokenSet ts_input)
			: this(g)
		{
			this.ts_input = ts_input;
		}

		/// <summary>
		/// Invariants
		/// </summary>
		public readonly Config config;

		public readonly Config.Parser parser_config;

		public readonly Grammar g;

		public readonly Lexicon lexicon;

		/// <summary>
		/// Pertaining to this parse
		/// </summary>

		public TokenSet ts_input;

		public String InputText { get { return ts_input.Source.Text; } }

		public IEnumerable<IParseObj> chart_tokens;

		public LexicalAnalysis la;

		public ParseChart chart;

		/// <summary>
		/// Statistics for this parse
		/// </summary>

		readonly public Stats stats;

		readonly Stopwatch timer = new Stopwatch();

		/// <summary>
		/// Task management
		/// </summary>

		readonly TaskFactory tf;

		int c_tasks = 1;

		int cancel = 0;

		public bool f_parse_phase_complete = false;

		public bool AnyPacking { get { return parser_config.packing != Config.Parser.PackingOpts.None; } }

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Guarantee a parent task context so that nested tasks will be attached as child tasks.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		Task<ParseControl> StartRootTask(Func<Task> root_func)
		{
			timer.Start();

			if (parser_config.c_tasks_max == -1)
			{
				root_func();
				AfterChart();
				return Tasks.FromResult(this);
			}

			//return Task.Factory.StartNew(
			//    () => root_func().Wait(),
			//    CancellationToken.None,
			//    TaskCreationOptions.None,
			//    TaskScheduler.Default)
			return root_func()
			.ContinueWith<ParseControl>(t =>
				{
					AggregateException aex = t.Exception;
					if (aex != null)
					{
						aex = aex.Flatten();
						var rgex = aex.InnerExceptions.OfType<ParseException>().ToArray();
						if (rgex.Length == 1)
							throw rgex[0];
						throw aex;
					}
					return AfterChart();
				}, TaskContinuationOptions.ExecuteSynchronously);
		}

		ParseControl AfterChart()
		{
			stats.Parsing.Chart.SetElapsed(timer.ElapsedMilliseconds);
			f_parse_phase_complete = true;
			chart.ReleaseChart();	// doesn't seem to help
			chart.ClearSubscribers();

			if (parser_config.packing == Config.Parser.PackingOpts.None)
				stats.Parsing.Unpacking.c_derivations = stats.Parsing.Chart.c_root_edges;

			return this;
		}

		public Task<ParseControl> Parse(TokenSet ts_input)
		{
			this.ts_input = ts_input;
			Task<ParseControl> tpc = StartRootTask(() =>
			{
				/// continue from the morphology job ... to the post morphology job
				/// this returns immediately, but the root task won't be signaled until all child tasks complete.
				return SynchronousContinuation(MorphologyJob(ts_input), PostMorphologyJob);
			});

			/// non-parse activities go here; they will not delay the delivery of the parse chart
			if (parser_config.f_autotune)
				tpc.ContinueWith(t => g.tm.Autotune());
			return tpc;
		}

		public bool TryEnterMultithread()
		{
			int ctm = parser_config.c_tasks_max;
			if (ctm < int.MaxValue)
			{
				if (!config.system.MultiThreading || c_tasks >= ctm)
					return false;
				Interlocked.Increment(ref c_tasks);
			}
			return true;
		}

		public void LeaveMultithread()
		{
			if (parser_config.c_tasks_max < int.MaxValue)
				Interlocked.Decrement(ref c_tasks);
		}

		public
#if NODE_INDEX
		new 
#endif
 void Dispose()
		{
			stats.Totals.ms_time = timer.ElapsedMilliseconds;
#if NODE_INDEX
			base.Dispose();
#endif
			stats.Disconnect();
		}

#if false
		public Task<Y> SynchronousContinuation<X, Y>(Task<X> t_prev, Func<X, Y> f)
		{
			if (cfg.c_tasks_max == -1)
				return Tasks.FromResult(f(t_prev.Result));

			return t_prev.ContinueWith<Y>(t =>
				{
					return f(t.Result);
				}, TaskContinuationOptions.ExecuteSynchronously);
		}
		public Task<X> SynchronousContinuation<X>(Task t_prev, Func<X> f)
		{
			if (cfg.c_tasks_max == -1)
				return Tasks.FromResult(f());

			return t_prev.ContinueWith<X>(t =>
			{
				return f();
			}, TaskContinuationOptions.ExecuteSynchronously);
		}
#endif

		public Task SynchronousContinuation(Task t_prev, Action a)
		{
			if (parser_config.c_tasks_max == -1)
			{
				a();
				return Tasks.CompletedTask;
			}
			else
				return t_prev.ContinueWith(t => a(), TaskContinuationOptions.ExecuteSynchronously);
		}

		static void _nop(Task[] tsk) { }

		public Task WhenAll(Action[] rga)
		{
			if (parser_config.c_tasks_max == int.MaxValue)
			{
				Task[] rgt = new Task[rga.Length];
				for (int i = 0; i < rga.Length; i++)
					rgt[i] = tf.StartNew(rga[i]);
				rga = null;
				return tf.ContinueWhenAll(rgt, _nop);
			}

			for (int i = 0; i < rga.Length; i++)
				rga[i]();
			return Tasks.CompletedTask;
		}

		[DebuggerHidden()]
		internal Task StartAttachedChildTask(Action a)
		{
			if (parser_config.c_tasks_max == int.MaxValue)
				return tf.StartNew(a);

			if (c_tasks >= parser_config.c_tasks_max)
			{
				a();
				return Tasks.CompletedTask;
			}
			Interlocked.Increment(ref c_tasks);
			return tf.StartNew(() =>
				{
					a();
					Interlocked.Decrement(ref c_tasks);
				});
		}

		[DebuggerHidden()]
		internal Task<X> StartAttachedChildTask<X>(Func<X> f)
		{
			if (parser_config.c_tasks_max == int.MaxValue)
				return tf.StartNew(f);

			if (c_tasks >= parser_config.c_tasks_max)
			{
				return Tasks.FromResult(f());
			}
			Interlocked.Increment(ref c_tasks);
			return tf.StartNew<X>(() =>
				{
					X x = f();
					Interlocked.Decrement(ref c_tasks);
					return x;
				});
		}

		[DebuggerHidden()]
		internal Task StartAttachedChildTask<X>(Action<X> ax, X x)
		{
			if (parser_config.c_tasks_max == int.MaxValue)
				return tf.StartNew(() => ax(x));

			if (c_tasks >= parser_config.c_tasks_max)
			{
				ax(x);
				return Tasks.CompletedTask;
			}
			Interlocked.Increment(ref c_tasks);
			return tf.StartNew(() =>
				{
					ax(x);
					Interlocked.Decrement(ref c_tasks);
				});
		}

		//internal void ParallelLoop<T>(IEnumerable<T> ie, Action<T> a)
		//{
		//    if (parser_config.c_tasks_max == int.MaxValue)
		//        Parallel.ForEach(ie, a);
		//    else
		//        foreach (T t in ie)
		//            a(t);
		//}

		public bool IsParseCancelled
		{
			get
			{
				if (cancel > 0)
					return true;
				if (timer.Elapsed.TotalSeconds <= parser_config.ItemTimeoutSeconds)
					return false;
				if (Interlocked.CompareExchange(ref cancel, 1, 0) == 0)
				{
					timer.Stop();
					double ms = timer.ElapsedMilliseconds / 1000.0;
					throw new ParseTimeoutException("({0:0.00} sec.)", ms);
				}
				return true;
			}
		}

		public string SysObjName
		{
			get { return String.Format("pc-{0:X}", this.GetHashCode()); }
		}

		public string SysObjDescription
		{
			get { return String.Format("{0}", ts_input.Source.Text); }
		}

		public miew.ReadOnly.IReadOnlyDictionary<string, ISysObj> SysObjChildren
		{
			get { return chart.SysObjChildren; }
		}

		public ISysObj SysObjParent
		{
			get { return g; }
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public unsafe class Allocator
#if NODE_INDEX
		: IDisposable
#endif
	{
		[DllImport("kernel32.dll", SetLastError = true)]
		static extern IntPtr HeapCreate(uint flOptions, UIntPtr dwInitialSize, UIntPtr dwMaximumSize);

		[DllImport("kernel32.dll", SetLastError = false)]
		static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, UIntPtr dwBytes);

		[DllImport("kernel32.dll", SetLastError = true)]
		static extern bool HeapDestroy(IntPtr hHeap);

		[DllImport("kernel32.dll", SetLastError = true)]
		static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem);

		[DllImport("kernel32.dll")]
		static extern IntPtr GlobalAlloc(uint uFlags, UIntPtr dwBytes);

		[DllImport("kernel32.dll")]
		static extern IntPtr GlobalFree(IntPtr hMem);

		[DllImport("kernel32.dll")]
		static extern UInt32 GetLargePageMinimum();

#if HEAP_PRIVATE_STATIC
		static Allocator()
		{
		    heap = HeapCreate(0, new UIntPtr(1024 * 1024 * 20), UIntPtr.Zero);
		}
#endif

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool UnifyCheck(Tfs tfs0, Tfs tfs1)
		{
			Config.Parser.UnifierType ut = tm.config.parser.checker;
			if (ut == Config.Parser.UnifierType.n_way)
				return NWayCheck.Check(tfs0, tfs1);
			else if (ut == Config.Parser.UnifierType.qd)
				return new UnificationQd(tm).Check(tfs0, tfs1);
			else
				return Unification.Check(tfs0, tfs1);
		}

		public Allocator(TypeMgr tm)
		{
			this.tm = tm;
#if NODE_INDEX
#if HEAP_PRIVATE
			uint flOptions = 0;
			if (!tm.config.system.MultiThreading)
				flOptions |= 1;
			heap = HeapCreate(flOptions, new UIntPtr(1024 * 1024 * 1), UIntPtr.Zero);
#elif VIRTUAL_ALLOC
			heap = Va.VirtualAlloc(IntPtr.Zero, new UIntPtr(CB_VA_HEAP), Va.AllocationType.RESERVE, Va.MemoryProtection.NOACCESS);

			if (heap.ToInt64() == 0)
				throw new Exception();
#else
			this.m_sl = new SpinLock(false);
			this.hglobals = new List<IntPtr>();
#endif
#endif
		}

		public TypeMgr tm;

#if NODE_INDEX
#if HEAP_PRIVATE_STATIC
		static
#endif
#if HEAP_PRIVATE_STATIC || HEAP_PRIVATE || VIRTUAL_ALLOC
		IntPtr heap;
#endif
#if HEAP_PROCESS || HEAP_PRIVATE_STATIC || GLOBAL_ALLOC
		List<IntPtr> hglobals;
		SpinLock m_sl;
#endif

#if VIRTUAL_ALLOC
		const long CB_VA_COMMIT_CHUNK = 0x8000;
		const long CB_VA_HEAP = (long)2 * (long)1024 * (long)1024 * (long)1024;
		long o_alloc = 0;
		long o_commit = 0;
#endif

		int cb_alloc = 0;
#endif

		public void* Alloc(int cb)
		{
#if NODE_INDEX
			if (cb <= 0)
				throw new Exception();
#if HEAP_PROCESS
			IntPtr ip = Marshal.AllocHGlobal(cb);
#elif GLOBAL_ALLOC
			IntPtr ip = GlobalAlloc(0, new UIntPtr((uint)cb));
#elif VIRTUAL_ALLOC
			long o_end = Interlocked.Add(ref o_alloc, cb);
			if (o_end >= CB_VA_HEAP)
				throw new Exception();

			long _tmp = o_commit;
			if (o_end >= _tmp)
			{
				long q = (((o_end - _tmp) + CB_VA_COMMIT_CHUNK) & ~(CB_VA_COMMIT_CHUNK - 1));
				Va.VirtualAlloc(new IntPtr(heap.ToInt64() + _tmp), new UIntPtr((uint)q), Va.AllocationType.COMMIT, Va.MemoryProtection.READWRITE);
				Interlocked.CompareExchange(ref o_commit, _tmp + q, _tmp);
			}
			IntPtr ip = new IntPtr(heap.ToInt64() + o_end - cb);
#else
			IntPtr ip = HeapAlloc(heap, 0, new UIntPtr((uint)cb));
#endif

#if HEAP_PROCESS || HEAP_PRIVATE_STATIC || GLOBAL_ALLOC
			bool entered = false;
			m_sl.Enter(ref entered);
			cb_alloc += cb;
			hglobals.Add(ip);
			m_sl.Exit(false);
#else
			cb_alloc += cb;
#endif
			return ip.ToPointer();
#else
			return null;
#endif
		}

#if NODE_INDEX
		public void Dispose()
		{
#if DEBUG
			if (this.GetType() == typeof(Allocator))
				throw new Exception();
#endif
#if HEAP_PRIVATE || VIRTUAL_ALLOC
			IntPtr _tmp = heap;
			if (_tmp != IntPtr.Zero && Interlocked.CompareExchange(ref heap, IntPtr.Zero, _tmp) == _tmp)
			{
#if VIRTUAL_ALLOC
				if (!Va.VirtualFree(_tmp, UIntPtr.Zero, 0x8000))
					throw new Exception();
#else
				HeapDestroy(_tmp);
#endif
				//Task.Factory.StartNew(() => HeapDestroy(_tmp));
			}
#else
			List<IntPtr> _tmp = hglobals;
			if (_tmp != null && Interlocked.CompareExchange(ref hglobals, null, _tmp) == _tmp)
			{
				Task.Factory.StartNew(() =>
				{
					for (int i = 0; i < _tmp.Count; i++)
#if HEAP_PROCESS
						Marshal.FreeHGlobal(_tmp[i]);
#elif GLOBAL_ALLOC
						GlobalFree(_tmp[i]);
#else
						HeapFree(heap, 0, _tmp[i]);
#endif
				});
			}
#endif
		}
#endif
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class ParseException : Exception
	{
		public ParseException(String fmt, params Object[] args)
			: base(fmt == null ? String.Empty : String.Format(fmt, args))
		{
		}
		public ParseException()
			: this(null)
		{
		}
	};

	public class ParseTimeoutException : ParseException
	{
		public ParseTimeoutException()
		{
		}
		public ParseTimeoutException(String fmt, params Object[] args)
			: base(String.Format(fmt, args))
		{
		}
	};

	public class LexicalAnalysisException : ParseException
	{
		public LexicalAnalysisException(String fmt, params Object[] args)
			: base(fmt == null ? String.Empty : String.Format(fmt, args))
		{
		}
	};

	public class LexicalCoverageException : ParseException
	{
		public LexicalCoverageException(String fmt, params Object[] args)
			: base(fmt == null ? String.Empty : String.Format(fmt, args))
		{
		}
	};
}