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

using miew.Enumerable;

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

		readonly Type t;

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

		protected TargetTfs PreExpand()
		{
			TargetTfs tt = Unification.UnifyForceExpand(Definition, InstanceType.Expanded);
			if (tt == null)
				throw new TfsException("Error expanding entry '{0}': could not unify with its instance type(s).", this.Name);
			tt.Name = Name + " - Expanded";
			return tt;
		}

		/// to avoid expanding lexical a dynamic expanding entry if all you need is the hash
		long _exp_tfs_hash = -1;
		public long ExpandedTfsHash
		{
			get
			{
				if (_exp_tfs_hash == -1)
				{
					if (this is StaticExpandEntry)
						_exp_tfs_hash = ((StaticExpandEntry)this).Expanded.TfsHash();
					else
						_exp_tfs_hash = ((DemandExpandEntry)this).GetExpanded().TfsHash();
				}
				return _exp_tfs_hash;
			}
		}
	};


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

		public override Tfs EnsureExpanded(out bool f_did)
		{
			f_did = false;
			Tfs _tmp;

			if ((_tmp = _expanded) != null)
				return _tmp;

			Stopwatch stopw = Stopwatch.StartNew();
			TargetTfs tt = PreExpand();

			if ((_tmp = _expanded) != null)
				return _tmp;

			Tfs _new;
			if (tt.EdgeCount == 0)
				_new = new BareTfs(tt.Type);
			else
				_new = tt.ToArrayTfs();
			Interlocked.Add(ref tm.ms_expand, (int)stopw.ElapsedMilliseconds);

			if ((_tmp = Interlocked.CompareExchange(ref _expanded, _new, null)) != null)
				return _tmp;
			f_did = true;
			return _new;
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Expand-on-demand support
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract partial class DemandExpandEntry : Entry
	{
		public DemandExpandEntry(Type t, String name, List<BaseFeatConstraint> bfc)
			: base(t, name, bfc)
		{
			/// an ID for the expanded TFS that will remain the same after weak resurrection
			exp_tfs_id = Interlocked.Increment(ref Tfs.next_tfs_id);
		}

		public override Tfs EnsureExpanded(out bool f_did)
		{
			f_did = true;
			return GetExpanded();
			//throw new NotImplementedException("use GetExpanded() for DynamicExpandEntry");
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Hook the expansion of lexical entries and keep a weak reference. The published reference to a lexical entry or
		/// entries can be released but a weak reference will remain in the object for possible future recovery.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		WeakReference wr = null;
		int exp_tfs_id;

		public Tfs GetExpanded()
		{
			/// we have to count weak reference recoveries as unifications (and pay the penalty of re-installing
			/// them atomically) or else the regression test results for number of unifications won't exactly match
			Tfs _exp;
			if (wr != null && (_exp = (Tfs)wr.Target) != null)
				return _exp;

			_exp = PreExpand().ToArrayTfs();
			_exp.id = exp_tfs_id;

			if (wr == null)
				wr = new WeakReference(_exp, false);
			else
				wr.Target = _exp;
			return _exp;
		}
	};

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

		public Rule(Type t, BinaryReader br)
			: base(t, br)
		{
		}

		HashSet<Rule> _key_mothers = null;

		public bool IsSpanOnly = false;

		private MotherDaughterTfs _mdtfs;

		public override Tfs EnsureExpanded(out bool f_did)
		{
			f_did = false;
			Tfs _tmp;

			if ((_tmp = _expanded) != null)
				return _tmp;

			Stopwatch stopw = Stopwatch.StartNew();
			TargetTfs tt = PreExpand();

			if ((_tmp = _expanded) != null)
				return _tmp;

			_mdtfs = tt.ToMotherDaughterTfs(null);

			Interlocked.Add(ref tm.ms_expand, (int)stopw.ElapsedMilliseconds);

			if ((_tmp = Interlocked.CompareExchange(ref _expanded, _mdtfs, null)) != null)
				return _tmp;
			f_did = true;
			return _mdtfs;
		}

		public MotherDaughterTfs RuleTfs
		{
			get
			{
				bool f_dont_care;
				return _mdtfs ?? (MotherDaughterTfs)EnsureExpanded(out f_dont_care);
			}
		}

		public TfsSection[] RuleDaughters
		{
			get
			{
				bool f_dont_care;
				return (_mdtfs ?? (MotherDaughterTfs)EnsureExpanded(out f_dont_care)).RuleDaughters;
			}
		}

		public int KeyDaughterIndex
		{
			get
			{
				bool f_dont_care;
				return (_mdtfs ?? (MotherDaughterTfs)EnsureExpanded(out f_dont_care)).KeyDaughterIndex;
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <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)
		{
			/// 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)
				if (tm.da.UnifyCheck(Expanded, mother.RuleDaughters[mother.KeyDaughterIndex]))
					_key_mothers.Add(mother);
		}

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

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

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

		public GrammarRule(Type t, BinaryReader br)
			: base(t, br)
		{
		}

		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);

			/// find the rules which are compatible with each non-KEY daughter position of this rule
			_non_key_daughters = new HashSet<Rule>[RuleDaughters.Length];
			for (int i = 0; i < RuleDaughters.Length; i++)
			{
				if (i != KeyDaughterIndex)
				{
					_non_key_daughters[i] = new HashSet<Rule>();
					TfsSection rd = RuleDaughters[i];
					foreach (Rule candidate in daughter_check)
						if (tm.da.UnifyCheck(candidate.Expanded, rd))
							_non_key_daughters[i].Add(candidate);
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool CheckDaughterCompatibility(Rule rule, int i_arg)
		{
			return _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)
			: base(t, br)
		{
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <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)
			: base(t, br)
		{
		}
	};


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

		public NodeLabelTemplate(Type t, String name, List<BaseFeatConstraint> bfc)
			: base(t, name, bfc)
		{
			TypeMgr tm = t.tm;
			Tfs exp = this.Definition;// this.Expanded;
			ConstraintRef cr_label;
			if (!exp.GetEdgeAtPath(exp.Edge, tm.config.nodeLabels.LabelPath, out cr_label))
				throw new Exception(String.Format("Node label template '{0}' has no label at '{1}'",
							Name,
							tm.config.nodeLabels.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.i_feat));

			effective = exp.GetSection(exp.tm.CreateEdge(t_new, exp.Edge.Mark, false), 0);

			//if (!
			effective_local = exp.GetSection(tm.config.nodeLabels.LocalPath);
			//effective_local = default(TfsEdge);
		}

		public NodeLabelTemplate(Type t, BinaryReader br)
			: base(t, br)
		{
		}

		public String Label { get { return label; } }

	};

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

		public NodeMetaTemplate(Type t, String name, List<BaseFeatConstraint> bfc)
			: base(t, name, bfc)
		{
			TypeMgr tm = t.tm;
			Tfs exp = this.Definition;//.Expanded;
			ConstraintRef cref;

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

			if (!exp.GetEdgeAtPath(exp.Edge, tm.config.nodeLabels.SuffixPath, out cref))
				throw new Exception(String.Format("Node meta template '{0}' has no suffix at '{1}'",
							Name,
							tm.config.nodeLabels.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(fix => tm.feat_arr[fix].maximal_type != tm.tt_meta));

			effective = exp.GetSection(tm.config.nodeLabels.RecursivePath);
			if (effective == null)
				throw new Exception();
		}

		public NodeMetaTemplate(Type t, BinaryReader br)
			: base(t, br)
		{
		}

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