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

using glue.Extensions.Enumerable;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract partial class Entry : Instance
	{
		public Entry(Type t, String name, List<BaseFeatConstraint> bfc)
			: base(t.mgr, name, bfc)
		{
			this.t = t;
			this.m_flags = (t.m_flags & Flags.HasAnyFeatures);
		}

		readonly Type t;

		public override Type InstanceType { get { return t; } }
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract partial class StaticExpandEntry : Entry
	{
		public StaticExpandEntry(Type t, String name, List<BaseFeatConstraint> bfc)
			: base(t, name, bfc)
		{
		}

		TfsEdge _expanded;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]

		public override TfsEdge Expanded
		{
			get
			{
				if (_expanded.IsNull)
				{
					Tray tr = mgr.g.loadtray;
					Stopwatch stopw = Stopwatch.StartNew();

					TfsEdge _tmp;
					if (!new Unification.Expander(tr).ExpandEntry(_definition, InstanceType.Expanded, out _tmp))
						throw new TfsException("Error expanding entry '{0}': could not unify with its instance type(s).", this.Name);

					if (!_tmp.PseudoAtomicStoreTo(ref _expanded))
						_tmp.Dispose();
					else
					{
						_expanded.RegisterName(Name);
						m_flags |= Flags.Expanded;		// fix fix
					}

					Interlocked.Add(ref mgr.ms_expand, (int)stopw.ElapsedMilliseconds);
				}
				return _expanded;
			}
		}

		public virtual bool ReleaseExpanded()
		{
			TfsEdge _tmp = _expanded;
			if (default(TfsEdge).PseudoAtomicStoreTo(ref _expanded))
			{
				m_flags &= ~Flags.Expanded;			// fix fix
				_tmp.Dispose();
				return true;
			}
			return false;
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract partial class DemandExpandEntry : Entry
	{
		public DemandExpandEntry(Type t, String name, List<BaseFeatConstraint> bfc)
			: base(t, name, bfc)
		{
		}

		Task<TfsEdge> _expand_task = null;

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]	// side effects
		public override TfsEdge Expanded
		{
			get
			{
				bool _dont_care;
				return CheckExpand(out _dont_care);
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public virtual void ReleaseExpanded()
		{
			Task<TfsEdge> tk = _expand_task;
			if (tk != null)
			{
				if (Interlocked.CompareExchange(ref _expand_task, null, tk) == tk)
					tk.Result.Dispose();
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public TfsEdge CheckExpand(out bool f_did)
		{
			f_did = false;
			if (_expand_task == null)
			{
				// If we're in a task already, do the job synchronously. In either case, only the race winner proceeds with the job.
				if (glue.Debugging.Nop.single_threaded || Task.CurrentId.HasValue)
				{
					TaskCompletionSource<TfsEdge> tcs = new TaskCompletionSource<TfsEdge>();
					if (Interlocked.CompareExchange(ref _expand_task, tcs.Task, null) == null)
					{
						tcs.SetResult(_do_expand());
						f_did = true;
					}
				}
				else
				{
					Task<TfsEdge> t = new Task<TfsEdge>(_do_expand);
					if (Interlocked.CompareExchange(ref _expand_task, t, null) == null)
					{
						t.Start(TaskScheduler.Default);
						f_did = true;
					}
				}
			}
			return _expand_task.Result;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		TfsEdge _do_expand()
		{
			Tray tr = mgr.g.loadtray;
			Stopwatch stopw = Stopwatch.StartNew();

			TfsEdge _tmp;
			if (!new Unification.Expander(tr).ExpandEntry(_definition, InstanceType.Expanded, out _tmp))
				throw new TfsException("Error expanding entry '{0}': could not unify with its instance type(s).", this.Name);

			_tmp.RegisterName(Name);

			Interlocked.Add(ref mgr.ms_expand, (int)stopw.ElapsedMilliseconds);
			return _tmp;
		}

	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq} ({t._feature_info(),nq})")]
	public abstract class Rule : StaticExpandEntry, ParseChart.IParseChartRule
	{
		public Rule(Type t, String name, List<BaseFeatConstraint> bfc)
			: base(t, name, bfc)
		{
		}

		public Rule(Type t, BinaryReader br, Dictionary<int, Tray> tray_map)
			: base(t, br, tray_map)
		{
		}

		TfsEdge[] daughters = null;
		int i_key_daughter = 0;
		HashSet<Rule> _key_mothers = null;
		bool f_span_only = false;

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// locate the positions of this rule's daughters in the instance Type's expanded TFS, and cache them
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void InitializeDaughters()
		{
			daughters = Expanded.RuleDaughters.ToArray();

			if (daughters.Length == 0)
				throw new Exception();

			/// locate the daughter entry points in the expanded contents
			i_key_daughter = 0;

			Type t_key = mgr.tt_key_daughter;
			if (t_key.IsTop)
				t_key = null;

			/// find the first KEY daughter, i.e. the first daughter where the type at the KEY daughter path (if
			/// specified) matches the KEY daughter type (if specified). In the absence of such a match, the
			/// parser will default to active rightwards expansion starting at daughter index 0.
			/// As noted in a comment elsewhere, the KEY daughter index remains constant for active edges derived
			/// from this rule, so we don't need to redo this during parsing; instead we can just copy this value
			/// over.
			for (int i = 0; i < daughters.Length; i++)
			{
				TfsEdge rd = daughters[i];
				if (i_key_daughter == 0 && t_key != null)
				{
					if (rd.Tray.KeyDaughterPath.GetType(rd.Edge.Mark) == t_key)
						i_key_daughter = i;
				}
			}
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Check rule compatibility. There are two cache directions: first, each rule pre-checks and caches the mother
		/// rules for which this rule is compatible with that mother's KEY daughter. This cache is used by both grammar 
		/// rules--which build both upwards and downwards--and lexical rules--which build upwards only. GrammarRule 
		/// subclasses this function to add the down cache for non-key-daughters.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public virtual void AnalyzeRuleCompatibility(IEnumerable<Rule> key_mother_check, IEnumerable<Rule> daughter_check)
		{
			Tray tr = this.Expanded.Tray;

			/// find the mother rules for which this rule is compatible with its KEY daughter
			_key_mothers = new HashSet<Rule>();
			foreach (Rule mother in key_mother_check)
			{
				var up = new Unification.Partial(tr);
				TfsEdge full;
				if (up.UnifyAndComplete(mother.Expanded, mother.RuleDaughters[mother.KeyDaughterIndex], this.Expanded.Clone(), out full))
				{
					_key_mothers.Add(mother);
					full.Dispose();
				}
			}
		}

		public TfsEdge Contents { get { return Expanded; } }

		public ISysObj License { get { return this; } }

		public IList<TfsEdge> RuleDaughters
		{
			get { return daughters; }
		}

		public HashSet<Rule> CompatibleKeyMothers
		{
			get { return _key_mothers; }
		}

		public int KeyDaughterIndex { get { return i_key_daughter; } }

		IEnumerable<TfsEdge> ParseChart.IMotherDaughter.RuleDaughters
		{
			get { throw new InvalidOperationException("IList access via IGrammarRule is preferable"); }
		}

		public override bool ReleaseExpanded()
		{
			if (!base.ReleaseExpanded())
				return false;
			daughters = null;
			return true;
		}

		public bool IsSpanOnly
		{
			get { return f_span_only; }
			set { f_span_only = value; }
		}

		public bool SpinCompare(TfsEdge other)
		{
			throw new NotImplementedException();
		}

		public override string ToString()
		{
			return String.Format("{0}", Name);
		}

		public void Dispose()
		{
			throw new InvalidOperationException("Cannot release an object which is a permanent part of the grammar in this way.");
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public partial class GrammarRule : Rule, ParseChart.IGrammarRule
	{
		public GrammarRule(Type t, String name, List<BaseFeatConstraint> bfc)
			: base(t, name, bfc)
		{
		}

		public GrammarRule(Type t, BinaryReader br, Dictionary<int, Tray> tray_map)
			: base(t, br, tray_map)
		{
		}

		HashSet<Rule>[] _non_key_daughters = null;

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public override void AnalyzeRuleCompatibility(IEnumerable<Rule> key_mother_check, IEnumerable<Rule> daughter_check)
		{
			base.AnalyzeRuleCompatibility(key_mother_check, daughter_check);

			Tray tr = this.Expanded.Tray;

			/// find the rules which are compatible with each non-KEY daughter position of this rule
			_non_key_daughters = new HashSet<Rule>[RuleDaughters.Count];
			for (int i = 0; i < RuleDaughters.Count; i++)
			{
				if (i != KeyDaughterIndex)
				{
					_non_key_daughters[i] = new HashSet<Rule>();
					TfsEdge rd = RuleDaughters[i];
					foreach (Rule candidate in daughter_check)
					{
						var up = new Unification.Partial(tr);
						TfsEdge full;
						if (up.UnifyAndComplete(this.Expanded, rd, candidate.Expanded.Clone(), out full))
						{
							_non_key_daughters[i].Add(candidate);
							full.Dispose();
						}
					}
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool CheckDaughterCompatibility(ISysObj rule, int i_arg)
		{
			return rule is Rule && _non_key_daughters[i_arg].Contains(rule);
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public partial class LexicalRule : Rule
	{
		public LexicalRule(Type t, String name, List<BaseFeatConstraint> bfc)
			: base(t, name, bfc)
		{
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	public partial class MorphologicalRule : LexicalRule
	{
		MorphologySubrule[] morph_subrules = null;

		public MorphologicalRule(Type t, String name, List<BaseFeatConstraint> bfc, List<MorphologySubrule> morph_subrules)
			: base(t, name, bfc)
		{
			if (morph_subrules != null)
				this.morph_subrules = morph_subrules.ToArray();
		}

		public IList<MorphologySubrule> Subrules { get { return morph_subrules; } }

		public override string ToString()
		{
			return base.ToString() + String.Format("  subrules: {0}", morph_subrules.Length);
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class StartSymbol : StaticExpandEntry
	{
		public StartSymbol(Type t, String name, List<BaseFeatConstraint> bfc)
			: base(t, name, bfc)
		{
		}
		public StartSymbol(Type t, BinaryReader br, Dictionary<int, Tray> tray_map)
			: base(t, br, tray_map)
		{
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	abstract public class NodeLabel : StaticExpandEntry
	{
		public NodeLabel(Type t, String name, List<BaseFeatConstraint> bfc)
			: base(t, name, bfc)
		{
		}
		public NodeLabel(Type t, BinaryReader br, Dictionary<int, Tray> tray_map)
			: base(t, br, tray_map)
		{
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{Name}")]
	public class NodeLabelTemplate : NodeLabel
	{
		String label;
		public TfsEdge effective;
		public TfsEdge effective_local;

		public NodeLabelTemplate(Tray tr, Type t, String name, List<BaseFeatConstraint> bfc)
			: base(t, name, bfc)
		{
			LoadDefinition(tr);

			TypeMgr tm = t.mgr;
			TfsEdge exp = this.Definition;// this.Expanded;
			ConstraintRef cr_label;
			if (!tr.GetEdgeAtPath(exp.Edge, tm.config.NodeLabelConfiguration.LabelPath, out cr_label))
				throw new Exception(String.Format("Node label template '{0}' has no label at '{1}'",
							Name,
							tm.config.NodeLabelConfiguration.LabelPath.StringJoin(".")));
			label = tm.GetStringValue(cr_label.Constraint.FlagsId);

			/// Sharing the Mark of 'Expanded' but with a more derived type (computed here) causes 
			/// the 'label' feature to be ignored when unifying for node labels
			Type t_new = tm.GlbOfMany(exp.AllPools.Exclude(cr_label.Pool));

			effective = tr.CreateTfs(tr.CreateRecycledEdge(t_new, exp.Edge.Mark, false));

			//if (!
			tr.GetTfsEdgeAtPath(exp.Edge, tm.config.NodeLabelConfiguration.LocalPath, out effective_local);
			//effective_local = default(TfsEdge);
		}

		public NodeLabelTemplate(Type t, BinaryReader br, Dictionary<int, Tray> tray_map)
			: base(t, br, tray_map)
		{
		}

		public String Label { get { return label; } }

	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{Name}")]
	public class NodeMetaTemplate : NodeLabel
	{
		String meta_prefix;
		String meta_suffix;
		public TfsEdge effective;

		public NodeMetaTemplate(Tray tr, Type t, String name, List<BaseFeatConstraint> bfc)
			: base(t, name, bfc)
		{
			LoadDefinition(tr);

			TypeMgr tm = t.mgr;
			TfsEdge exp = this.Definition;//.Expanded;
			ConstraintRef cref;

			if (!tr.GetEdgeAtPath(exp.Edge, tm.config.NodeLabelConfiguration.PrefixPath, out cref))
				throw new Exception(String.Format("Node meta template '{0}' has no prefix at '{1}'",
							Name,
							tm.config.NodeLabelConfiguration.PrefixPath.StringJoin(".")));
			meta_prefix = tm.GetStringValue(cref.Constraint.FlagsId);

			if (!tr.GetEdgeAtPath(exp.Edge, tm.config.NodeLabelConfiguration.SuffixPath, out cref))
				throw new Exception(String.Format("Node meta template '{0}' has no suffix at '{1}'",
							Name,
							tm.config.NodeLabelConfiguration.SuffixPath.StringJoin(".")));
			meta_suffix = tm.GetStringValue(cref.Constraint.FlagsId);

			/// Sharing the Mark of 'Expanded' but with a more derived type (computed here) causes 
			/// the 'prefix' and 'suffix' features to be ignored when unifying
			/// for now, assuming that the prefix and suffix features are introduced by meta
			Type t_new = tm.GlbOfMany(exp.AllPools.Where(cp => cp.IntroducedBy != tm.tt_meta));

			if (!tr.GetTfsEdgeAtPath(exp.Edge, tm.config.NodeLabelConfiguration.RecursivePath, out effective))
				throw new Exception();

			//effective = tr.CreateTfs(tr.CreateRecycledEdge(t_new.m_id, exp.Mark, false));
		}

		public NodeMetaTemplate(Type t, BinaryReader br, Dictionary<int, Tray> tray_map)
			: base(t, br, tray_map)
		{
		}

		public String Prefix { get { return meta_prefix; } }
		public String Suffix { get { return meta_suffix; } }
	};

}