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

using glue;
using glue.Extensions.String;
using glue.Extensions.String.Builder;
using glue.Debugging;
using glue.Extensions.Enumerable;
using glue.Collections.XSpinLock;
using glue.Collections.LockFreeDictionary;

#pragma warning disable 0649

namespace agree
{
	public interface IFreezableTray
	{
		void Freeze();
	}

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Tray (abstract) : 
	/// An array of ConstraintPools, one for each Feature, ordered according to feature indexes for the owning TypeMgr
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	abstract public partial class Tray
	{
		public Tray(int tix, TypeMgr tm, int last_mark_issued, int next_id)
		{
			Nop.X(fsp1, fsp2, fsp3);

			this.TargetTfs = WrapAsTargetTfs(default(Edge));
			this.tix = tix;
			this.tm = tm;
			this.Pools = new ConstraintPool[tm.Features.Count];
			this._unk = new UnknownPoolClass(this, -1);
			this.last_mark_issued = last_mark_issued;
			this.next_id = next_id == -1 ? (TargetId + IdIncrement) : next_id;
		}

		FalseSharing.Padding60 fsp1;
		/// Mark management is global per-tray.
		//protected 
		public
			Int32 last_mark_issued;

		FalseSharing.Padding60 fsp2;

		/// Although less closely coupled to the typical use in the unification helper class, TfsIds are also issued 
		/// by this AtomicSequencer which belongs to this Tray
		int next_id;

		FalseSharing.Padding60 fsp3;

		internal int _protect_mark = 0;

		public void Protect()
		{
			_protect_mark = last_mark_issued;
		}
		public void ResetMark()
		{
			last_mark_issued = Pools.Max(cp => cp.Marks.Max());
		}

		/// A special TfsId is reserved for the unification target
		const int IdIncrement = 1 << 6;
		public const int TargetId = 2 << 6;

		public TfsEdge TargetTfs;

		/// id number for this tray; these are unique across all grammars, i.e. not just unique within a grammar or TypeMgr,
		/// because this is overloaded with the function of allowing an edge to find the grammar it belongs to.
		public readonly int tix;

		/// owning type manager
		public readonly TypeMgr tm;

		/// array of ConstraintPools
		public readonly ConstraintPool[] Pools;

		public int c_truncators = 0;
		public ManualResetEvent/*Slim*/ mre_truncate_ok = new ManualResetEvent/*Slim*/(true);

		/// Allows a ConstratintRef to record this Tray as the one that it will be concerned with, without having to select 
		/// a feature
		readonly ConstraintPool _unk;
		public ConstraintPool UnknownPool { get { return _unk; } }

		/// each Tray provides an registry of (optional) TfsEdge names
		readonly ConcurrentDictionary<TfsEdge, String> names = new ConcurrentDictionary<TfsEdge, String>();

		public void RegisterTfsName(TfsEdge te, String s)
		{
			names[te] = s;
		}

		public String GetTfsName(TfsEdge te)
		{
			String s;
			names.TryGetValue(te, out s);
			return s;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		/// grammar compiled paths
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public void CompileGrammarPaths(GrammarConfig cfg)
		{
			RuleArgsPath = new TrayCompiledFsPath(this, cfg.rule_args_path);
			KeyDaughterPath = new TrayCompiledFsPath(this, cfg.key_daughter_path);
			OrthPath = new TrayCompiledFsPath(this, cfg.orth_path);

			if (cfg.deleted_daughters != null)
				DeletedDaughters = cfg.deleted_daughters.Select(s => Pools[tm.feat_map[s].i_feat]).ToArray();
		}

		public TrayCompiledFsPath RuleArgsPath;
		public TrayCompiledFsPath KeyDaughterPath;
		public TrayCompiledFsPath OrthPath;
		public ConstraintPool[] DeletedDaughters = null;

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Create an edge which will be used in this tray. The edge is not persisted into the tray.
		/// Caller may not submit a non-bare mark if the requirements for a non-bare mark are not met
		/// But caller can optionally submit a bare mark for any new edge; the current design enforces that non-bare
		/// types will always have a non-bare mark
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Edge CreateRecycledEdge(Type t, int m, bool f_coref)
		{
			Debug.Assert(t != null && t.mgr == tm);

			Edge.Flag f;
			if (t == tm.StringType)
				f = Edge.Flag.EtmString;
			else
			{
				f = (Edge.Flag)t.m_id;
				if ((t.m_flags & Type.Flags.HasAnyFeatures) > 0)
					f |= Edge.Flag.EtmNonBareType;
			}

			if (f_coref)
				f |= Edge.Flag.Coreference;

			if ((f & (Edge.Flag.Coreference | Edge.Flag.EtmNonBareType)) != 0)
			{
				if (m == 0)
					m = Interlocked.Increment(ref last_mark_issued);
			}
			else
			{
				/// Caller may not submit a non-bare mark if the requirements for a non-bare mark are not met
				Debug.Assert(m == 0);
			}
			return new Edge(f, m);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Create an edge with the specified type, coreference status, and mark.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Edge CreateRecycledEdge(Edge.Flag f, int m)
		{
			Debug.Assert(tm.GetEdgeType(f).IsBare == ((f & Edge.Flag.EtmNonBareType) == 0));

			if ((f & (Edge.Flag.Coreference | Edge.Flag.EtmNonBareType)) != 0)
			{
				if (m == 0)
					m = Interlocked.Increment(ref last_mark_issued);
			}
			else
			{
				/// Caller may not submit a non-bare mark if the requirements for a non-bare mark are not met
				Debug.Assert(m == 0);
			}

			return new Edge(f, m);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Auto-mark based on the bareness of the type
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Edge CreateEdge(Type t, bool f_coref)
		{
			Debug.Assert(t != null && t.mgr == tm);
			Edge.Flag f;

			if (t == tm.StringType)
				f = Edge.Flag.EtmString;
			else
			{
				f = (Edge.Flag)t.m_id;
				if ((t.m_flags & Type.Flags.HasAnyFeatures) > 0)
					f |= Edge.Flag.EtmNonBareType;
			}

			if (f_coref)
				f |= Edge.Flag.Coreference;

			int m = (f & (Edge.Flag.Coreference | Edge.Flag.EtmNonBareType)) == 0 ? 0 :
							Interlocked.Increment(ref last_mark_issued);

			return new Edge(f, m);
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Auto-mark based on the bareness of the type
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Edge CreateEdge(Edge.Flag f)
		{
			Debug.Assert(tm.GetEdgeType(f).IsBare == ((f & Edge.Flag.EtmNonBareType) == 0));

			return new Edge(f, (f & (Edge.Flag.Coreference | Edge.Flag.EtmNonBareType)) == 0 ? 0 :
							Interlocked.Increment(ref last_mark_issued));
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void ChangeEdgeCoreference(PoolMark pm, bool f_coref)
		{
			Debug.Assert(pm.in_mark != 0);
			Debug.Assert(pm.Pool.tr == this);

			Edge e = pm.Constraint;
			if (f_coref)
			{
				Debug.Assert(!e.IsCoreferenced);
				if (tm.IsTopId(e))
					e = CreateEdge(tm.TopType, true);
				else
					e = CreateRecycledEdge(e.FlagsId | Edge.Flag.Coreference, e.Mark);
				pm.SetConstraint(e);
			}
			else
			{
				Debug.Assert(e.IsCoreferenced);
				if (tm.IsTopId(e))
					pm.RemoveConstraint();
				else
				{
					Debug.Assert(tm.GetEdgeType(e.FlagsId).IsBare == ((e.FlagsId & Edge.Flag.EtmNonBareType) == 0));
					pm.SetConstraint(new Edge(e.FlagsId & ~Edge.Flag.Coreference, (e.FlagsId & Edge.Flag.EtmNonBareType) == 0 ? 0 : e.Mark));
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Create a string value edge
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Edge CreateStringEdge(int sid, bool f_coref)
		{
			Edge.Flag f = Edge.Flag.EtmString | (Edge.Flag)sid;
			int m = 0;
			if (f_coref)
			{
				f |= Edge.Flag.Coreference;
				m = Interlocked.Increment(ref last_mark_issued);
			}
			return new Edge(f, m);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Add a unique string value
		/// constrained strings must never use Edge.MarkBare (or you'd lose the ability to distinguish their values, which must
		/// be isolating), but identical strings should share the same (non-bare) mark, when coreferenced. When unconstrained,
		/// the string type itself should use Edge.MarkBare.
		/// </summary>
		/// <remarks>
		/// LKB treats empty string as distinct from unconstrained string type
		///			s1 := *top* & [ X *top* ].
		///			s2 := s1 & [ X "foo" ].
		///			s3 := s1 & [ X "" ].
		///			s4 := s2 & s3.
		/// </remarks>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Edge CreateStringEdge(String s)
		{
			if (tm.IsPetrified)
				throw new Exception("Type hierarchy has already finished loading, cannot add more strings");

			return CreateStringEdge(tm.strings.GetOrAdd(s), false);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Copy an edge, retaining string contents and shareable aspects. If non-shareable, a new mark is given.
		/// Since the entire flags/id is copied, this works for string values too..
		/// This can also prepare an edge from another tray for use in this tray.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Edge ShallowCopyEdge(Edge e)
		{
			return new Edge(e.FlagsId, e.Mark == 0 ? 0 : Interlocked.Increment(ref last_mark_issued));
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// A system of incrementing ID numbers is used to ensure that re-uses of a stock TFS within the same unification
		/// operation do not lead to conflated coreferences between the instances, which must remain separate. This can
		/// happen when a large TFS uses, for example, more than one difference list (which has internal coreferencing).
		/// The target TFS has a fixed ID value.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public TfsEdge CreateTfs(Type t, bool f_coref)
		{
			return new TfsEdge(Interlocked.Add(ref next_id, IdIncrement) | tix, CreateEdge(t, f_coref));
		}
		public TfsEdge CreateTfs(Edge e)
		{
			return new TfsEdge(Interlocked.Add(ref next_id, IdIncrement) | tix, e);
		}
		public TfsEdge CloneTfs(TfsEdge te)
		{
			Debug.Assert(!te.IsTarget);
			return new TfsEdge(Interlocked.Add(ref next_id, IdIncrement) | tix, te.Edge);
		}
		public TfsEdge WrapAsTargetTfs(Edge e)
		{
			return new TfsEdge(TargetId | tix, e);
		}
		public void ReleaseTfs(TfsEdge te)
		{
			/// this would be a place to do other housekeeping
			RudeTruncate(te.Edge);

			/// unregister the TFS name, if any
			String name;
			names.TryRemove(te, out name);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool TryGetPool(String f, out ConstraintPool cp)
		{
			TypeMgr.FeatureInfo fi;
			if (!tm.feat_map.TryGetValue(f, out fi))
			{
				cp = null;
				return false;
			}
			cp = Pools[fi.i_feat];
			return true;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public ConstraintPool this[String feat]
		{
			get
			{
				TypeMgr.FeatureInfo fi;
				return tm.feat_map.TryGetValue(feat, out fi) ? Pools[fi.i_feat] : null;
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public int PoolMarkCount { get { return Pools.Sum(cp => cp.Count); } }

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		ConstraintPool list_head = null;
		ConstraintPool ListHead { get { return list_head = list_head ?? Pools[tm.f_ix_list_head]; } }

		ConstraintPool list_tail = null;
		ConstraintPool ListTail { get { return list_tail = list_tail ?? Pools[tm.f_ix_list_tail]; } }

		ConstraintPool diff_list_list = null;
		ConstraintPool DiffListList { get { return diff_list_list = diff_list_list ?? Pools[tm.f_ix_dlist_list]; } }

		ConstraintPool diff_list_last = null;
		public ConstraintPool DiffListLast { get { return diff_list_last = diff_list_last ?? Pools[tm.f_ix_dlist_last]; } }

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public IEnumerable<Edge> GetListEdges(Edge e)
		{
			/// e.Type must unify down to tt_list_parent, but we don't need to check this separately because the ListHead pool 
			/// is only appropriate for types that are lists, so the Mark won't be found if e.Type is not a list.
			Edge lh_edge;
			while (ListHead.TryGetEdge(e.Mark, out lh_edge))
			{
				yield return lh_edge;

				if (!ListTail.TryGetEdge(e.Mark, out e) || e.Mark == 0)
					yield break;
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public IEnumerable<ConstraintRef> GetListConstraintRefs(Edge e)
		{
			Edge lh_edge;
			while (ListHead.TryGetEdge(e.Mark, out lh_edge))
			{
				yield return new ConstraintRef(e, ListHead);

				if (!ListTail.TryGetEdge(e.Mark, out e))
					yield break;
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public IEnumerable<ConstraintRef> GetDiffListLists(Edge e)
		{
			Edge ll_edge;
			if (!DiffListList.TryGetEdge(e.Mark, out ll_edge))
				yield break;
			foreach (ConstraintRef cr in GetListConstraintRefs(ll_edge))
				yield return cr;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool GetTfsEdgeAtPath(Edge e_start, FsPath p, out TfsEdge te)
		{
			te = CreateTfs(e_start);
			foreach (String f in p)
			{
				if (te.Edge.Mark == 0)
					return false;
				int i_feat = tm.GetFeatureIndex(f);
				if (i_feat == -1 || !Pools[i_feat].TryGetEdge(te.Edge.Mark, out te.Edge))
					return false;
			}
			return true;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Similar to the above, but returns a ConstraintRef, which provides access to the next-to-last edge in the path, so
		/// that the end of the path can be manipulated.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		public bool GetEdgeAtPath(Edge e_start, FsPath p, out ConstraintRef cref)
		{
			if (p.Count == 0)
			{
				cref = default(ConstraintRef);
				return false;
			}
			Edge e = e_start;
			Edge e_prev;
			ConstraintPool cp;
			for (int i = 0; ; )
			{
				e_prev = e;
				int i_feat = tm.GetFeatureIndex(p[i]);
				if (i_feat == -1 || !(cp = Pools[i_feat]).TryGetEdge(e.Mark, out e))
				{
					cref = default(ConstraintRef);
					return false;
				}
				i++;
				if (i == p.Count)
				{
					cref = new ConstraintRef(e_prev, cp);
					return true;
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public String ToPathList(Edge edge, String label, bool f_marks = false)
		{
			return new PathLister(this, edge, label, f_marks).ToString();
		}

		sealed class PathLister : List<String>
		{
			Tray tr;
			TypeMgr tm;
			ConstraintPool[] Pools;
			Stack<String> stack = new Stack<String>();
			Dictionary<Edge, int> d = null;
			int coref_tag = 0;
			bool f_marks;
			HashSet<PoolMark> reachable = null;

			public PathLister(Tray tr, Edge e, String label, bool f_marks, HashSet<PoolMark> reachable = null)
			{
				this.tr = tr;
				this.tm = tr.tm;
				this.Pools = tr.Pools;
				this.f_marks = f_marks;
				this.reachable = reachable;
				if (label != null)
					this.Add(String.Format(";;;;;;;;; ({0})", label));
				_list_paths(e, null);
			}

			void _list_paths(Edge e, String s_usage)
			{
				bool f_descend = true;
				Type t;
				if (tm.IsTopId(e))
				{
					t = tm.TopType;
					f_descend = false;
				}
				else
					t = tm.GetEdgeType(e.FlagsId);
				String s = String.Format("{0} {1}", stack.Reverse().StringJoin("."), t);
				if (tm.IsStringValue(e.FlagsId))
					s += " “" + tm.GetStringValue(e.FlagsId) + "”";
				if (e.IsCoreferenced)
				{
					int cre;
					if (d == null)
						d = new Dictionary<Edge, int> { { e, cre = coref_tag++ } };
					else if (!d.TryGetValue(e, out cre))
						d.Add(e, cre = coref_tag++);
					else
						f_descend = false;
					s += " #" + cre.ToString();
				}
#if DEBUG
				if (f_marks)
					s += "  (" + Edge.FormatMark(e) + ")";
#endif
				if (s_usage != null)
					s += " in-use by " + s_usage;

				this.Add(s);
				if (f_descend && e.IsConstrained)
				{
					/// this determines the order of coref tag numbering. it used to be specified here with 
					/// .OrderBy(cp => cp.IntroducedBy.m_level) but this is not needed so long as the feature index order
					/// in the type is kept this way, as it is at this time.
					foreach (int i_feat in tm.GetEdgeType(e.FlagsId)._deprec_feat_indexes)
					{
						Edge q;
						ConstraintPool cp = Pools[i_feat];
						String usage = null;
#if DEBUG
						if (reachable != null && reachable.Contains(new PoolMark(cp, e.Mark)))
							usage = tr.Roots.FindRootData(e.Mark).ToString();
#endif

						if (cp.TryGetEdge(e.Mark, out q))
						{
							stack.Push(cp.Feature.ToUpper());
							_list_paths(q, usage);
							stack.Pop();
						}
					}
				}
			}

			public override string ToString() { return this.StringJoin(Environment.NewLine) + Environment.NewLine; }
		};

		public int max_truncators = int.MaxValue;
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void RudeTruncate(Edge e)
		{
			Debug.Assert((e.FlagsId & (Edge.Flag.Coreference | Edge.Flag.EtmNonBareType)) != 0 && e.Mark != 0);

			if (Nop.single_threaded || c_truncators >= max_truncators)
			{
				using (ThreadPriorityRegion q = new ThreadPriorityRegion(ThreadPriority.AboveNormal))
				{
					new RudeTruncator(this, e);
				}
			}
			else
			{
#if false
				rt_top_marks.UnionWith(PoolMarksBelow(e));
#else
				if (Interlocked.Increment(ref c_truncators) == 1)
					mre_truncate_ok.Reset();
				new Task(() =>
					 {
						 using (ThreadPriorityRegion q = new ThreadPriorityRegion(ThreadPriority.AboveNormal))
						 {
							 //var q = Thread.CurrentThread;
							 //ThreadPriority pri_prev = q.Priority;

							 /// add ourselves to a list which allows our thread to be discovered in case we are lagging 
							 /// too much in our work
							 //truncators.Add(q.Thread);

							 new RudeTruncator(this, e);

							 //truncators.Remove(q.Thread);

							 /// restore thread priority in case someone boosted us
							 //q.Priority = pri_prev;

							 if (Interlocked.Decrement(ref c_truncators) == 0)
								 mre_truncate_ok.Set();
						 }
					 }).Start();
#endif
			}
		}

		//public ConcurrentHashSet<Thread> truncators = new ConcurrentHashSet<Thread>();

		//public ConcurrentHashSet<int> rt_top_marks = new ConcurrentHashSet<int>();

		sealed class RudeTruncator
		{
			TypeMgr tm;
			HashSet<Edge> corefs = null;

			public RudeTruncator(Tray tr, Edge e)
			{
				this.tm = tr.tm;
				_rude_truncate(e);
			}

			void _rude_truncate(Edge e)
			{
				foreach (ConstraintPool cp in tm.rgcp_by_type[(int)(e.FlagsId & tm.MultiIdMask)])
				{
					Edge ne;
					if (cp.TryRemoveEdge(e.Mark, out ne))
					{
						bool f_descend = true;
						if ((ne.FlagsId & Edge.Flag.Coreference) != 0)
						{
							if (corefs == null)
								corefs = new HashSet<Edge> { ne };
							else if (!corefs.Contains(ne))
								corefs.Add(ne);
							else
								f_descend = false;
						}
						if (f_descend && (ne.FlagsId & Edge.Flag.EtmNonBareType) != 0)
							_rude_truncate(ne);
					}
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public IEnumerable<PoolMark> AllPoolMarks(Edge e)
		{
			foreach (int fix in tm.GetEdgeType(e.FlagsId)._deprec_feat_indexes)
				yield return new PoolMark(Pools[fix], e.Mark);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public IEnumerable<ConstraintPool> ConstrainedPoolsOnly(Edge e)
		{
			if (!e.IsConstrained)
				yield break;
			foreach (int fix in tm.GetEdgeType(e.FlagsId)._deprec_feat_indexes)
			{
				ConstraintPool cp = Pools[fix];	// 'this[fix]' ?
				if (cp.ContainsInMark(e.Mark))
					yield return cp;
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public IEnumerable<PoolMark> ConstrainedPoolMarksOnly(Edge e)
		{
			if (!e.IsConstrained)
				yield break;
			foreach (int fix in tm.GetEdgeType(e.FlagsId)._deprec_feat_indexes)
			{
				ConstraintPool cp = Pools[fix];
				if (cp.ContainsInMark(e.Mark))
					yield return new PoolMark(cp, e.Mark);
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Includes coreferences, of course
		/// Constrained means it is has at least one non-top or coreferenced feature. We count on the fact that such edges are 
		/// always vacant from the constraint pool
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public int ConstrainedFeaturesCount(Edge e)
		{
			int c = 0;
			if (e.IsConstrained)
			{
				foreach (int fix in tm.GetEdgeType(e.FlagsId)._deprec_feat_indexes)
					if (Pools[fix].ContainsInMark(e.Mark))
						c++;
			}
			return c;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public HashSet<PoolMark> PoolMarksBelow(Edge edge)
		{
			return edge.IsConstrained ? new PoolMarkGatherer(this, edge) : PoolMark.EmptyPoolMarkHashSet;
		}

		sealed class PoolMarkGatherer : HashSet<PoolMark>
		{
			ConstraintPool[] Pools;
			TypeMgr tm;

			public PoolMarkGatherer(Tray tr, Edge e)
			{
				this.Pools = tr.Pools;
				this.tm = tr.tm;
				_gather(e);
			}

			void _gather(Edge e)
			{
				foreach (int fix in tm.GetEdgeType(e.FlagsId)._deprec_feat_indexes)
				{
					ConstraintPool cp;
					Edge next_edge;
					if ((cp = Pools[fix]).TryGetEdge(e.Mark, out next_edge))
					{
						PoolMark pm = new PoolMark(cp, e.Mark);
						if (!this.Contains(pm))
						{
							this.Add(pm);
							if (!tm.IsTopId(next_edge))
								_gather(next_edge);
						}
					}
				}
			}
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if false
		public HashSet<int> MarksBelow(Edge edge)
		{
			return edge.IsConstrained ? new MarkGatherer(this, edge) : MarkGatherer.Empty;
		}

		sealed class MarkGatherer : HashSet<int>
		{
			public static HashSet<int> Empty = new HashSet<int>();
			ConstraintPool[] Pools;
			TypeMgr tm;

			public MarkGatherer(Tray tr, Edge e)
			{
				this.Pools = tr.Pools;
				this.tm = tr.tm;
				_gather(e);
			}

			void _gather(Edge e)
			{
				foreach (int fix in tm.GetEdgeType(e.FlagsId)._deprec_feat_indexes)
				{
					ConstraintPool cp;
					Edge next_edge;
					if ((cp = Pools[fix]).TryGetEdge(e.Mark, out next_edge))
					{
						if (!this.Contains(next_edge.Mark))
						{
							this.Add(pm);
							if (!tm.IsTopId(next_edge))
								_gather(next_edge);
						}
					}
				}
			}

			public IEnumerator<int> GetEnumerator()
			{
				throw new NotImplementedException();
			}

			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
			{
				throw new NotImplementedException();
			}
		};
#endif

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool AnyConstraints(Edge e)
		{
			if (e.IsConstrained)
			{
				foreach (int fix in tm.GetEdgeType(e.FlagsId)._deprec_feat_indexes)
					if (Pools[fix].ContainsInMark(e.Mark))
						return true;
			}
			return false;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public IEnumerable<ConstraintRef> AllConstraintRefs(Edge e)
		{
			foreach (int fix in tm.GetEdgeType(e.FlagsId)._deprec_feat_indexes)
				yield return new ConstraintRef(e, Pools[fix]);
		}

		public IEnumerable<ConstraintRef> ConstrainedRefsOnly(Edge e)
		{
			if (!e.IsConstrained)
				yield break;
			foreach (int fix in tm.GetEdgeType(e.FlagsId)._deprec_feat_indexes)
			{
				ConstraintPool cp = Pools[fix];
				if (cp.ContainsInMark(e.Mark))
					yield return new ConstraintRef(e, Pools[fix]);
			}
		}
	};

}