using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using glue.Extensions.String;
using glue.Extensions.Enumerable;
using glue.Tasks;
using glue.Tokenization;

namespace agree.Parse
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	/// 
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class UnificationParseChart<T> : SubscriptionChart<T> where T : ChartBase<T>.IMotherDaughter, IEquatable<T>
	{
		public UnificationParseChart(
				ParserConfiguration config,
				ISysObj so_owner,
				String source_text,
				IEnumerable<ISelf> start_symbols,
				int chart_span,
				IEnumerable<IParseChartEdge> initial_tokens)
			: base(config, so_owner, source_text, chart_span)
		{
			this.initial_tokens = initial_tokens;
			this.start_symbols = start_symbols;
		}

		readonly IEnumerable<IParseChartEdge> initial_tokens;
		readonly internal IEnumerable<ISelf> start_symbols;

		public abstract bool CanUnify(T t1, T t2);
		public abstract bool UnifyPartial(IGrammarRule mother, T daughter, T candidate, out T result, bool f_last);
		public abstract Task<T> UnifyPartialAsync(IGrammarRule mother, T daughter, T candidate, bool f_last);
		public abstract IGrammarRule IncorporatePart(IGrammarRule mother, T result);
		public virtual QuickCheckResult QuickCheck(T t1, T t2) { return QuickCheckResult.ProceedToUnify; }

		public enum QuickCheckResult
		{
			Failed = 0,
			ProceedToUnify = 1,
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Guarantee a parent task context so that nested tasks will be attached as child tasks.
		/// Do not specify AttachedToParent (or rethrown exception will propagate into the parent task, which is not
		/// accessible to this function) and do not specify the chart's CancelTok (or this task's act of cancelling the
		/// parse will put a task-canceled exception into the continuation, and trump/erase the actual exception)
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public Task ParseAsync()
		{
			var hs = initial_tokens.UnionMany(tok => tok.ChartSpan.Range);
			if (hs.Count != ColumnCount)
			{
				String m2 = Enumerable.Range(0, ColumnCount).Except(hs).StringJoin(", ");
				throw new ParseException("No tokens were submitted to the parser for the following chart column positions: {0}.", m2);
			}

			timer.Start();
			return StartAttachedChildTask(ParseInner).ContinueWith(
							t =>
							{
								timer.Stop();
								ClearSubscribers();
								if (t.Exception != null)
								{
									CancelParse();
									AggregateException aex = t.Exception.Flatten();
									var rgex = aex.InnerExceptions.OfType<ParseException>().Distinct(e => e.Id).ToArray();
									if (rgex.Length == 1)
										throw rgex[0];
									throw aex;
								}
							},
							CancellationToken.None,
							TaskContinuationOptions.ExecuteSynchronously,
							TaskScheduler.Default);
		}

		void ParseInner()
		{
			//return 
			//return TaskFactory.ContinueWhenAll(initial_tokens.Select(tok => StartAttachedChildTask(AddEdgeInner, tok)).ToArray(), rgt =>
			//    {
			//        if (rgt.Any(t => t.Exception != null))
			//        {
			//            glue.Debugging.Nop.X();
			//        }
			//        return Tasks.CompletedTask;
			//    });

			//return TaskEx.WhenAll(initial_tokens.Select(tok => 


			//));
			foreach (IParseChartEdge tok in initial_tokens)
				StartAttachedChildTask(AddEdgeInner, tok);
		}
	};
}