//#define TEST
//#define DUMP
//#define LOG
//#define WRITE
#define DUM_DUM

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

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

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Below are the higher-level parts of ParseControl; those that deal with initializing, starting, and coordinating 
	/// the morphology and parsing jobs.
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed partial class ParseControl : ISysObj, IDisposable
	{
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Task MorphologyJob(TokenSet tok_set)
		{
			this.la = new LexicalAnalysis(this, lexicon, tok_set);
			return la.Analyze();
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void PostMorphologyJob()
		{
			stats.Morphology.SetComplete();
#if DUM_DUM
			/// we can start initializing the chart during the dependency filtering
			var init = StartAttachedChildTask<ParseChart>(() => new ParseChart(this));
#endif
			VerifyCoverage();

			CheckChartDependencies();
#if DUM_DUM
			/// ...now we need it
			ParseChart empty_chart = init.Result;
#else
			ParseChart empty_chart = new ParseChart(this);
#endif

			/// adding (any) tokens into the chart immediately triggers asynchronous parsing. Using attached 
			/// child tasks has two benefits:
			/// (a.) since subtasks are attached to the root task, it's ok for this function to end before 
			/// parsing is complete;
			/// (b.) we don't have to worry about the parser completing all available submitted work before
			/// this function has finished submitting all tokens.

			foreach (IParseObj tok in chart_tokens)
				StartAttachedChildTask(empty_chart.AddParseObj, tok);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void VerifyCoverage()
		{
			var missing = Enumerable.Range(0, la.SourceItem.MinimalSpanCount).Except(la.UnionMany(ta => ta.TokenSpan.Range));
			if (missing.Any())
			{
				String msg = Span.FromIndexList(missing)
								.Select(sp => String.Format("\"{0}\" {1}",
												ts_input.Source.MinimalSpanText(sp),
												ts_input.Source.MinimalSpanToCharacterSpan(sp)))	// todo: use ts.CharacterSpan
								.StringJoin(", ");
				throw new LexicalAnalysisException("No morpholexical solutions for: {0}.", msg);
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void CheckChartDependencies()
		{
			int c_columns = la.ChartSize;
			if (parser_config.ChartDependencyPathsArray == null)
				chart_tokens = la;
			else
			{
				chart_tokens = ChartDependencyFilter();
				var hs = chart_tokens.UnionMany(tok => tok.TokenSpan.Range);
				if (hs.Count != c_columns)
				{
					String msg = Enumerable.Range(0, c_columns).Except(hs).StringJoin(", ");
					throw new LexicalCoverageException("No tokens were submitted to the parser for the following chart column positions: {0}.", msg);
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public MotherDaughterTfs UnifySection(
			TfsSection daughter,
			Tfs candidate,
			bool f_args_delete,
			bool f_restrict,
			ref Stats._unification u_stats)
		{
			MotherDaughterTfs mdtfs;
			if (parser_config.unifier == Config.Parser.UnifierType.qd)
				mdtfs = new UnificationQd(this, f_args_delete, f_restrict).UnifySection(daughter, candidate);
			else if (parser_config.unifier == Config.Parser.UnifierType.n_way)
				mdtfs = UnificationNWay.UnifySection(this, daughter, candidate, f_args_delete, f_restrict);
			else
				mdtfs = Unification.UnifySection(daughter, candidate, f_args_delete, f_restrict, this);

			u_stats.RecordEvent(mdtfs);
			return mdtfs;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public MotherDaughterTfs UnifySections(
			MotherDaughterTfs mother,
			int i_first_arg,
			Tfs[] candidates,
			bool f_args_delete,
			bool f_restrict,
			ref Stats._unification u_stats)
		{
			MotherDaughterTfs mdtfs;
			if (parser_config.unifier == Config.Parser.UnifierType.qd)
				mdtfs = new UnificationQd(this, f_args_delete, f_restrict).UnifySections(mother, i_first_arg, candidates);
			else if (parser_config.unifier == Config.Parser.UnifierType.n_way)
				mdtfs = UnificationNWay.UnifySections(this, mother, i_first_arg, candidates, f_args_delete, f_restrict);
			else
				throw new Exception();

			u_stats.RecordEvent(mdtfs);
			return mdtfs;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool QuickCheckMorph(Tfs tfs1, Tfs tfs2)
		{
			if (!parser_config.f_qc_morph || parser_config.qcs == null)
				return false;

			//stats.Morphology.QuickCheck.c_evaluated++;
			if (!parser_config.qcs.CanSkip(tfs1, tfs2))
				return false;

			//stats.Morphology.QuickCheck.c_avoided++;
			return true;
		}
		public bool QuickCheckParse(Tfs tfs1, Tfs tfs2)
		{
			if (!parser_config.f_qc_parse || parser_config.qcs == null)
				return false;

			stats.Parsing.QuickCheck.c_evaluated++;
			if (!parser_config.qcs.CanSkip(tfs1, tfs2))
				return false;

			stats.Parsing.QuickCheck.c_avoided++;
			return true;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		[DebuggerDisplay("{token.GetHashCode().ToString(\"X8\"),nq} {type.Name,nq} {System.String.Join(\".\",target_path),nq}")]
		struct ChartDependency : IEquatable<ChartDependency>
		{
			public LexicalAnalysis.AnalysisStack token;
			public Type type;
			public FsPath target_path;

			public bool Equals(ChartDependency other)
			{
				if (target_path == null && other.target_path != null)
					return false;
				return token == other.token && type == other.type &&
					target_path.SequenceEqual(other.target_path, StringComparer.InvariantCultureIgnoreCase);
			}

			public override int GetHashCode()
			{
				int hc = 0;
				if (token != null)
					hc += token.GetHashCode();
				if (type != null)
					hc += type.GetHashCode();
				if (target_path != null)
					hc += target_path.Aggregate(0, (av, s) => av ^ s.GetHashCode());
				return hc;
			}
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Get chart dependencies for the specified token, if any
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		IEnumerable<ChartDependency> GetDependencies(LexicalAnalysis.AnalysisStack tok)
		{
			Tfs fs = tok.Tfs;
			foreach (var cdp in parser_config.ChartDependencyPathsArray)
			{
				TfsSection ts = fs.GetSection(cdp.path1);
				if (ts != null)
					yield return new ChartDependency
					{
						token = tok,
						type = ts.Type,
						target_path = cdp.path2
					};
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Filter tokens whose dependencies are not met out of the specified sequence
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		IEnumerable<IParseObj> ChartDependencyFilter()
		{
			Debug.Assert(parser_config.ChartDependencyPathsArray != null);

			if (parser_config.ChartDependencyScope == Config.Parser.DependencyScope.TwoWay)
				throw new NotImplementedException("two-way chart dependencies not implemented, use 'unidirectional-chart-dependencies' if possible");

			var stacks = la.OfType<LexicalAnalysis.AnalysisStack>().ToArray();

			/// Group all dependencies according to the introducing token
			var probation = stacks.SelectMany(tok => GetDependencies(tok)).Distinct().GroupBy(cd => cd.token);

			if (!probation.Any())
				Nop.CodeCoverage();

			/// Tokens which have no dependency are accepted.
			var satisfied = stacks.Except(probation.Select(grp => grp.Key)).ToHashSet();

			/// eek. find those probationary tokens for which all of their chart dependencies have at least one satisfied
			/// token (in a non-overlapping chart position) containing a compatible type at the specified path.
			/// 
			/// this was nice before I had to redo it when I realized that the satisfying tokens are not limited to the
			/// initial set, but must spread transitively as new ones become satisfied...

			int last_count;
			do
			{
				last_count = satisfied.Count;

				satisfied.UnionWith(probation
					.Where(grp => !satisfied.Contains(grp.Key) && grp.All(dpnd => satisfied
											.Where(other_tok => !other_tok.TokenSpan.Overlaps(grp.Key.TokenSpan))
											.Any(other_tok =>
											{
												TfsSection ts = other_tok.Tfs.GetSection(dpnd.target_path);
												if (ts == null)
													return false;
												int id1 = dpnd.type.m_id;
												int id2 = ts.Type.m_id;
												if (id1 == 0 || id2 == 0 || id1 == id2)
													return true;
												return tm.CanUnify(id1, id2);
											})))
					.Select(tok_deps => tok_deps.Key));
			}
			while (satisfied.Count > last_count);
			return satisfied;
		}
	};
}