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

using glue.Debugging;
using glue.Extensions.Enumerable;
using glue.Tasks;

using agree;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("#{id}")]
	public abstract partial class Unification
	{
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		TypeMgr tm;

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		Tray tr_dst;

		Dictionary<TfsEdge, UnifCoref> corefs = null;

		public Tray TargetTray { get { return tr_dst; } }

		public Unification(Tray tr_dst)
		{
			this.tr_dst = tr_dst;
			this.tm = tr_dst.tm;
#if DEBUG
			if (Debugger.IsAttached)
				id = System.Threading.Interlocked.Increment(ref next_id);
#endif
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// a unification equivalence class
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		sealed partial class UnifCoref : List<TfsEdge>
		{
			readonly public List<PoolMark> pmx = new List<PoolMark>();
			public Edge Master;

			public void AddPm(PoolMark pm)
			{
				Debug.Assert(!pmx.Contains(pm));
				pmx.Add(pm);
			}
			public void AddPms(IEnumerable<PoolMark> pms)
			{
				Debug.Assert(!pmx.Intersect(pms).Any());
				pmx.AddRange(pms);
			}
			public void RemovePm(PoolMark pm)
			{
				bool b = pmx.Remove(pm);
				Debug.Assert(b);
			}
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		[Flags]
		enum Options : byte
		{
			None = 0,
			ForceExpand = 1,
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// add the specified edge to the specified equivalence class
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void AddTfsMark(UnifCoref uc, TfsEdge te)
		{
			Debug.Assert(!uc.Contains(te, TfsEdge.TfsMarkComparer.Instance));
			corefs[te] = uc;
			uc.Add(te);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// when at least one of the two edges is coreferenced, it is always possible to resolve the coreference with no
		/// more than one binary unification operation. that is, you'd never have to, say, unify both inputs with an
		/// existing equivalence class in two separate operations. see the five cases below.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		bool _unify_corefs(Options o, ConstraintPool cp, int m, TfsEdge te1, TfsEdge te2, out Edge e)
		{
			Debug.Assert(!te1.Equals(te2));
			Edge.Flag f1 = te1.Edge.FlagsId, f2 = te2.Edge.FlagsId;
			Debug.Assert(((f1 | f2) & Edge.Flag.Coreference) > 0);

			UnifCoref uc, uc1 = null, uc2 = null;
			if (corefs == null)
				corefs = new Dictionary<TfsEdge, UnifCoref>(TfsEdge.TfsMarkComparer.Instance);
			else
			{
				if ((f1 & Edge.Flag.Coreference) != 0)
					corefs.TryGetValue(te1, out uc1);
				if ((f2 & Edge.Flag.Coreference) != 0)
					corefs.TryGetValue(te2, out uc2);
			}

			/// there are five possible cases when encountering at least one coreference on the two source edges
			if (uc1 == null && uc2 == null)
			{
				Debug.Assert(m != 0);
				/// did not pick up any coreference from either edge; start a new one.
#if DEBUG
				uc = new UnifCoref(this);
#else
				uc = new UnifCoref();
#endif
				if ((f1 & Edge.Flag.Coreference) != 0)
					AddTfsMark(uc, te1);
				if ((f2 & Edge.Flag.Coreference) != 0)
					AddTfsMark(uc, te2);

				uc.AddPm(new PoolMark(cp, m));
				if (!_unify(o, te1, te2, out uc.Master))
					return _fail(out e);
				e = uc.Master;
				AddTfsMark(uc, tr_dst.WrapAsTargetTfs(e));
				return true;
			}
			else if (uc1 == uc2)
			{
				/// picked up the exact same coreference from the two different edges; not much to do.
				if (m != 0)
					uc1.AddPm(new PoolMark(cp, m));

				e = uc1.Master;
				return true;
			}
			else if (uc2 == null)
			{
				/// found left side coreference, but not right.
				uc = uc1;

				if ((f2 & Edge.Flag.Coreference) != 0)
					AddTfsMark(uc, te2);

				te1 = tr_dst.TargetTfs;
				te1.Edge = uc1.Master;
			}
			else if (uc1 == null)
			{
				/// found right side coreference, but not left.
				Debug.Assert(m != 0);	// todo: note: asymmetry
				uc = uc2;

				if ((f1 & Edge.Flag.Coreference) != 0)
					AddTfsMark(uc, te1);

				te2 = tr_dst.TargetTfs;
				te2.Edge = uc2.Master;
			}
			else
			{
				/// merge existing left and right coreferences.
				uc = uc1;
				uc.AddPms(uc2.pmx);

				foreach (TfsEdge te_xfer in uc2)
					AddTfsMark(uc, te_xfer);

				te1 = te2 = tr_dst.TargetTfs;
				te1.Edge = uc1.Master;
				te2.Edge = uc2.Master;
			}
			if (m != 0)
				uc.AddPm(new PoolMark(cp, m));

			if (!_unify(o, te1, te2, out uc.Master))
				return _fail(out e);

			e = uc.Master;
			Debug.Assert(uc.Contains(tr_dst.WrapAsTargetTfs(e), TfsEdge.TfsMarkComparer.Instance));
			return true;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// unify.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		unsafe bool _unify(Options o, TfsEdge te1, TfsEdge te2, out Edge e)
		{
			Debug.Assert(!te1.Equals(te2));
			/// find the best mark to recycle if one or more of the input TFSs are within the target TFS

			int m1 = te1.Edge.Mark;
			int m2 = te2.Edge.Mark;
			Debug.Assert(te1.Edge.Mark != te2.Edge.Mark || te1.Edge.Mark == 0 || te1.TfsId != te2.TfsId);
			int m = 0;
			Edge.Flag nf, f1 = te1.Edge.FlagsId, f2 = te2.Edge.FlagsId;
			bool b1, tgt1 = (te1.ttid & 0xFFFFFFC0) == Tray.TargetId, tgt2 = (te2.ttid & 0xFFFFFFC0) == Tray.TargetId;

			// ensure that, a destructive input TFS--if any--is on the left. swap if necessary.
			if ((m1 | m2) != 0)
			{
				if (tgt1)
				{
					m = m1;
					if (m2 == 0 || !tgt2)
						goto done;
					else if (m1 == 0)
						goto swap;
					else if ((f2 & Edge.Flag.Coreference) == 0 || (f1 & Edge.Flag.Coreference) != 0)
						goto done;
				}
				else if (!tgt2)
					goto done;
			swap:
				{
					TfsEdge te = te2;
					te2 = te1;
					te1 = te;
					/// swap done, reload
					m = m1 = m2;
					m2 = te2.Edge.Mark;
					f1 = f2;
					f2 = te2.Edge.FlagsId;
					b1 = tgt2;
					tgt2 = tgt1;
					tgt1 = b1;
				}
			}
		done:

			if ((f1 & Edge.Flag.EtmMask) == Edge.Flag.EtmString || (f2 & Edge.Flag.EtmMask) == Edge.Flag.EtmString)
				return tm.strings.Unify(m, te1.Edge, te2.Edge, tr_dst, out e);

			/// find glb of the types of the two TFS nodes
			int tid1 = (int)(f1 & tm.MultiIdMask);
			int tid2 = (int)(f2 & tm.MultiIdMask);
			if (tid2 == 0 || tid1 == tid2)
				nf = f1;
			else if (tid1 == 0)
				nf = f2;
			else
			{
				int k = tid1 > tid2 ? (tid2 << tm.c_t_bits) + tid1 : (tid1 << tm.c_t_bits) + tid2;
				TypeMgr.GlbCacheEntry[] rge = tm.glb_cache[k % TypeMgr.GLB_CACHE_ENTRIES];
				TypeMgr.GlbCacheEntry gce;
				if (rge != null)
				{
					int gk, i = 0;
					do
						if ((gk = (gce = rge[i]).k) == k)
							goto got_glb;
					while (gk < k && ++i < rge.Length);
				}
				tm.ComputeAndCacheGlb(tid1, tid2, out gce.f);
			got_glb:
				if ((nf = gce.f) == Edge.Flag.CachedBottom)
					return _fail(out e);
			}
			Debug.Assert(tm.GetEdgeType(nf).IsBare == ((nf & Edge.Flag.EtmNonBareType) == 0));

			/// create new edge, generating a new mark if one is needed because the type is non-bare or the edge is coreferenced
			nf |= ((f1 | f2) & Edge.Flag.Coreference);
			e = tr_dst.CreateRecycledEdge(nf, m);

			if ((nf & Edge.Flag.EtmNonBareType) != 0)
			{
				/// Type has features and now has a non-zero mark.
				m = e.Mark;

				Debug.Assert(m != 0 && m != m2);
				/// if the marks are equal (and not in cloned TFSes), they must both be 0, a case of non-bare GLB result
				Debug.Assert(m1 != m2 || m1 == 0 || te1.TfsId != te2.TfsId);

				TfsEdge nte1 = te1;
				TfsEdge nte2 = te2;
				Edge* pne1 = &nte1.Edge;
				Edge* pne2 = &nte2.Edge;
				if (m1 == 0) *pne1 = default(Edge);
				if (m2 == 0) *pne2 = default(Edge);
				Edge ne;

				ConstraintPool[] rgcp = tm.rgcp_by_type[(int)(nf & tm.MultiIdMask)];
				foreach (ConstraintPool cp in rgcp)
				{
					b1 = m1 != 0 && cp.TryGetEdge(m1, out *pne1);
					if ((m2 != 0 && cp.TryGetEdge(m2, out *pne2)) || b1)
					{
#if DEBUG
						ne = default(Edge);
#endif
						if (*(ulong*)pne1 == *(ulong*)pne2)
						{
							if (pne1->Mark == 0)
							{
								ne = *pne1;
								goto opt;
							}
							else if (tgt1 & tgt2)
							{
								Debug.Assert(pne1->IsCoreferenced);
								ne = *pne1;
								goto opt;
							}
							else if (!pne1->IsCoreferenced)
							{
								ne = tr_dst.CreateEdge(pne1->FlagsId);
								goto opt;
							}
						}

						// the following is not the case when picking up non-persisted corefs from m1!=m2 retrieving 
						// identical non-zero marks with different types
						//Debug.Assert(pne1->Mark == 0 || pne1->Mark != pne2->Mark);

						if (((pne1->FlagsId | pne2->FlagsId) & Edge.Flag.Coreference) != 0)
						{
							if (m == m1 && (pne1->FlagsId & Edge.Flag.Coreference) != 0)
							{
								if (!_unify_corefs(o, null, 0, nte1, nte2, out ne))
									goto cleanup_fail;
							}
							else
							{
								if (!_unify_corefs(o, cp, m, nte1, nte2, out ne))
									goto cleanup_fail;
							}
						}
						else if (!_unify(o, nte1, nte2, out ne))
							goto cleanup_fail;

					opt:
						/// If the result is not one of the input types and it has features, or if we are expanding the type
						/// hierarchy, unify with the constraints on the type
						if ((ne.FlagsId & Edge.Flag.EtmNonBareType) != 0)
						{
							int ntid = (int)(ne.FlagsId & tm.MultiIdMask);
							Debug.Assert(ntid != tm.StringId);
							if ((o & Options.ForceExpand) > 0 ||
								((int)(pne1->FlagsId & tm.MultiIdMask) != ntid && (int)(pne2->FlagsId & tm.MultiIdMask) != ntid))
							{
								/// the following can change the edge
								if (!_unify(Options.None, tr_dst.WrapAsTargetTfs(ne), tm.type_arr[ntid].Expanded.Clone(), out ne))
									goto cleanup_fail;
							}
						}

						/// persist the new edge.
						if (ne.FlagsId == 0)
						{
							/// ...or remove it if destructive
							if (m == m1)
								cp.RemoveEdge(m);
						}
						else if (m != m1 || *(ulong*)&ne != *(ulong*)pne1)
							cp.SetEdge(m, ne);

						/// if destructive in the target, remove the edge that wasn't retained
						if (m2 != 0 && tgt2)
						{
							Debug.Assert(m == m1);

							if (pne2->IsCoreferenced)
								corefs[nte2].RemovePm(new PoolMark(cp, m2));
							cp.RemoveEdge(m2);
						}
						continue;

					cleanup_fail:
						foreach (ConstraintPool dcp in rgcp)
						{
							/// if non-destructive, we only have to clean up for as far as we progressed.
							if (m != m1 && dcp == cp)
								break;
							if (dcp.TryRemoveEdge(m, out ne) && ne.Mark != 0)
								tr_dst.RudeTruncate(ne);
						}
						return false;
					}
				}
			}
			return true;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void _persist_coref_edges()
		{
			if (corefs == null)
				return;
			foreach (var tfsc in corefs.Values.Distinct())
			{
				Edge e = tfsc.Master;
				foreach (PoolMark pm in tfsc.pmx)
					pm.Pool.SetEdge(pm.in_mark, e);
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void _remove_vacuous_corefs()
		{
			if (corefs == null)
				return;
			//var to_remove = corefs.Values.Where(cr => cr.pmx.Count == 1).ToArray();
			foreach (var tfsc in corefs.Values.Distinct()/*.ToArray()*/)
			{
				//Debug.Assert(tfsc.pmx.Count > 0);
				if (tfsc.pmx.Count == 1)
				{
					tr_dst.ChangeEdgeCoreference(tfsc.pmx.First(), false);
					tfsc.pmx.Clear();
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public partial class Expander : Unification
		{
			public Expander(Tray target_tray) : base(target_tray) { }

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			///
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public bool ExpandEntry(TfsEdge te_def, TfsEdge te_inst_type_exp, out TfsEdge result)
			{
				result = tr_dst.CreateTfs(default(Edge));
				if (!_unify(Options.ForceExpand, te_def, te_inst_type_exp, out result.Edge))
					return false;

				if (corefs != null)
				{
					_persist_coref_edges();
					_remove_vacuous_corefs();
				}
				return true;
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			///
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public bool ExpandType(Type host_type, out TfsEdge result)
			{
#if DEBUG
				_t_exp = host_type;
#endif
				result = host_type.Definition;

				if ((host_type.m_flags & Type.Flags.HasAnyFeatures) == 0)
					return true;

				Type[] iea = host_type.Parents.Where(p => p.HasAnyFeatures).ToArray();

				/// If the conjoined parents are distinct as they should be, then we don't need to clone the top level 
				/// TFSs and we will only clone interior usages.
				Debug.Assert(iea.IsDistinct());

				if (iea.Length == 0)
					iea = tm.rgt_top_singleton;

				for (int i = 0; i < iea.Length; i++)
				{
					if (!_unify(Options.ForceExpand, result, iea[i].Expanded, out result.Edge))
						return _fail(out result);

					if (i == 0)
						result = tr_dst.WrapAsTargetTfs(result.Edge);
				}

				if (corefs != null && corefs.Count > 0)
				{
					_persist_coref_edges();
					_remove_vacuous_corefs();
				}

				/// don't return a target TFS or really weird stuff will happen
				Debug.Assert(result.IsTarget);
				result = tr_dst.CreateTfs(result.Edge);
				return true;
			}
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public class Checker : Unification
		{
			public Checker(Tray target_tray) : base(target_tray) { }

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			///
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public bool Check(TfsEdge te1, TfsEdge te2)
			{
				Edge e;
				// todo: consider the ability to set destination mark to -1 such that we never switch to destructive 
				// unify, and don't write out any edges. is this possible without n-way? seems like coreferencing might
				// need 'scratch' space, except in the n-way approach
				if (!_unify(Options.None, te1, te2, out e))
					return false;

				tr_dst.RudeTruncate(e);
				return true;
			}
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public class Unifier : Unification
		{
			public Unifier(Tray target_tray) : base(target_tray) { }

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			///
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public bool Unify(TfsEdge te1, TfsEdge te2, out Edge result)
			{
				if (!_unify(Options.None, te1, te2, out result))
					return false;

				if (corefs != null)
				{
					_persist_coref_edges();
					//_remove_vacuous_corefs();
				}
				return true;
			}
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// todo: decide if we want to resurrect this
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public class Async : Unifier
		{
			public Async(Tray target_tray) : base(target_tray) { }
			public Task CleanupTask { get { return Tasks.CompletedTask; } }
			public Task ResultTask { get { return Tasks.CompletedTask; } }
			public void Fail() { }
			public Task<Edge> UnifyAsync(TfsEdge e1, TfsEdge e2) { throw new InvalidOperationException(); }
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public class Partial : Async
		{
			public Partial(Tray target_tray) : base(target_tray) { }

			int e_daughter_mark;
			Edge e_partial;

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public void CompleteSkeleton(TfsEdge mother, TfsEdge daughter, Edge partial, out Edge result)
			{
				Debug.Assert(corefs != null);
				//Debug.Assert(tfs_id != Tray.TargetId);
				Debug.Assert(ResultTask.IsCompleted);

				this.e_daughter_mark = daughter.Edge.Mark;
				this.e_partial = partial;

				/// Copy remainder to the destination tray
				result = tr_dst.ShallowCopyEdge(mother.Edge);
				_copy_remainder(mother, result);
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			void _copy_remainder(TfsEdge te_src, Edge e_dst)
			{
				ConstraintPool[] rgcp_src = ((te_src.ttid ^ tr_dst.tix) & 0x3F) == 0 ? tr_dst.Pools : te_src.Tray.Pools;
				int m = te_src.Edge.Mark;
#if DEBUG
				TfsEdge te_original_source = te_src;
				foreach (int fix in te_original_source.Type._deprec_feat_indexes)
#else
				foreach (int fix in te_src.Type._deprec_feat_indexes)	// todo
#endif
				{
					ConstraintPool cp = rgcp_src[fix];
					if (cp.TryGetEdge(m, out te_src.Edge))
					{
						Edge ne_dst;
						if ((te_src.Edge.FlagsId & Edge.Flag.Coreference) > 0)
						{
							UnifCoref tfsc;
							if (corefs.TryGetValue(te_src, out tfsc))
								ne_dst = tfsc.Master;
							else
							{
								if (te_src.Edge.Mark == e_daughter_mark)
									ne_dst = e_partial;
								else
								{
									ne_dst = tr_dst.ShallowCopyEdge(te_src.Edge);
									_copy_remainder(te_src, ne_dst);
								}
#if DEBUG
								tfsc = new UnifCoref(this);
#else
								tfsc = new UnifCoref();
#endif
								tfsc.Master = ne_dst;

								corefs.Add(te_src, tfsc);
								corefs.Add(tr_dst.WrapAsTargetTfs(tfsc.Master), tfsc);

							}
							tfsc.AddPm(new PoolMark(cp, e_dst.Mark));
						}
						else if (te_src.Edge.Mark == e_daughter_mark)
						{
							ne_dst = e_partial;
						}
						else if (te_src.Edge.Mark == Edge.MarkBare)
						{
							ne_dst = te_src.Edge;
						}
						else
						{
							ne_dst = tr_dst.ShallowCopyEdge(te_src.Edge);
							_copy_remainder(te_src, ne_dst);
						}
						if (tr_dst.Pools != rgcp_src)
							cp = tr_dst.Pools[fix];
						cp.SetEdge(e_dst.Mark, ne_dst);
					}
					else
						Debug.Assert(!cp.ContainsInMark(e_dst.Mark));
				}
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			///
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public bool UnifyAndComplete(TfsEdge mother, TfsEdge daughter, TfsEdge candidate, out TfsEdge full)
			{
				Edge e, partial;
				if (!Unify(daughter, candidate, out partial))
				{
					full = default(TfsEdge);
					return false;
				}
				CompleteSkeleton(mother, daughter, partial, out e);
				full = tr_dst.CreateTfs(e);
				return true;
			}
		};


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public class Pruning : Partial
		{
			public Pruning(Tray target_tray) : base(target_tray) { }

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// Prune a sub-graph, and then remove any unused coreferences (and unmark the edge coreference bit) which 
			/// have only a single use remaining.
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public void PruneBelow(ConstraintPool cp, int m)
			{
				if (_prune_below(cp, m))
					cp.SetEdge(m, Edge.PrunedEdge);

				//_remove_vacuous_corefs();
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// This differs from Tray.RudeTruncate because this is smart about not pruning into coreference subtrees that
			/// are still needed
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public bool _prune_below(ConstraintPool cp, int m)
			{
				Edge ne;
				if (!cp.TryGetEdge(m, out ne))
					return false;

				if (ne.IsCoreferenced)
				{
					/// remove an edge that leads to this coreference...
					UnifCoref coref = corefs[tr_dst.WrapAsTargetTfs(ne)];
					coref.RemovePm(new PoolMark(cp, m));

					/// ...but only remove below it if that was its last use. This allows sub-graphs to 
					/// be pruned without disturbing the coreferenced portions that are still accessible 
					/// from without.

					if (coref.pmx.Count > 0)
						goto skip_subtree_pruning;
				}

				if (ne.Mark != 0 && (ne.FlagsId & Edge.Flag.EtmNonBareType) != 0)
				{
					foreach (ConstraintPool ncp in tm.rgcp_by_type[tm.GetEdgeTypeId(ne.FlagsId)])
						_prune_below(ncp, ne.Mark);
				}
			skip_subtree_pruning:
				cp.RemoveEdge(m);
				return true;
			}
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		bool _fail()
		{
			Async a = this as Async;
			if (a != null)
				a.Fail();
			return false;
		}
		public bool _fail(out Edge e)
		{
			e = default(Edge);
			return _fail();
		}
		public bool _fail(out TfsEdge te)
		{
			te = default(TfsEdge);
			return _fail();
		}
	};
}