using System;
using System.Diagnostics;

using miew.Debugging;
using miew.Enumerable;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// TFS subsumption checking for (e.g.) packing purposes
	/// </summary>
	/// <reference>
	/// Stephan Oepen, John Carroll. 2000. "Ambiguity Packing in Constraint-based Parsing--Practical Results."
	/// In Proceedings of the 1st North American chapter of the Association for Computational Linguistics 
	/// conference. Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 162-169. 
	/// </reference>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public unsafe struct Subsumption
	{
		TypeMgr tm;
		byte[] restrictor;
		Tfs tfs0, tfs1;
		sbyte subsumption;
		byte* rgb0, rgb1;
		byte b_next;
		ushort*[] rgpfix;

		public Subsumption(Tfs tfs0, Tfs tfs1)
		{
			this.tm = tfs0.tm;
			this.restrictor = tm.config.parser.packing_restrictor_map;
			this.rgpfix = tm.rgpfix_allft;
			this.tfs0 = tfs0;
			this.tfs1 = tfs1;
			this.subsumption = Equivalent;
			this.rgb0 = this.rgb1 = null;
			this.b_next = 0;
		}

		public const sbyte None = 0;
		public const sbyte FirstSubsumesSecond = 1;
		public const sbyte SecondSubsumesFirst = 2;
		public const sbyte Equivalent = 3;
		public const sbyte Bottom = -4;	// note: doesn't interfere with subsumption bit-testing

		public void _check(int tid, int m0, int m1)
		{
			ushort* pfix = rgpfix[tid];
			ushort* pfix_end = *pfix++ + pfix;
			while (pfix < pfix_end)
			{
				int i_feat = *pfix++;
				if (restrictor[i_feat] == 1)
					continue;
				int nm0, nm1;
				Edge.Flag f0 = tfs0.TryGetFlagsMark(i_feat, m0, out nm0);
				Edge.Flag f1 = tfs1.TryGetFlagsMark(i_feat, m1, out nm1);
				if (f0 == 0 && f1 == 0)
					continue;
				else if (f0 == 0)
					subsumption &= unchecked((sbyte)0xFD);
				else if (f1 == 0)
					subsumption &= unchecked((sbyte)0xFE);

				/// check for a re-entrancy that is present in one TFS but not the other
				byte* pb0, pb1;
				if (nm0 < 0 && nm1 < 0)
				{
					byte b0 = *(pb0 = rgb0 + ~nm0), b1 = *(pb1 = rgb1 + ~nm1);
					if (b0 + b1 == 0)
						*pb0 = *pb1 = ++b_next;
					else if (b0 == 0)
					{
						subsumption &= unchecked((sbyte)0xFD);
						*pb0 = b1;
					}
					else if (b1 == 0)
					{
						subsumption &= unchecked((sbyte)0xFE);
						*pb1 = b0;
					}
					else if (b0 != b1)
						subsumption = None;
				}
				else if (nm0 < 0)
				{
					subsumption &= unchecked((sbyte)0xFE);
					if (*(pb0 = rgb0 + ~nm0) == 0)
						*pb0 = ++b_next;
				}
				else if (nm1 < 0)
				{
					subsumption &= unchecked((sbyte)0xFD);
					if (*(pb1 = rgb1 + ~nm1) == 0)
						*pb1 = ++b_next;
				}

				if (subsumption <= 0)
					return;

				/// check type subsumption
				Edge.Flag f;
				f0 &= ~(Edge.Flag.Coreference | Edge.Flag.PrunedDuringParsing);
				f1 &= ~(Edge.Flag.Coreference | Edge.Flag.PrunedDuringParsing);
				if (f0 == f1)
					f = f0;
				else
				{
					if (f1 == 0)
						f = f0;
					else if (f0 == 0)
						f = f1;
					else if ((f = tm.UnifyTypes(f0, f1)) == Edge.Flag.Bottom)
					{
						subsumption = Bottom;
						return;
					}

					if (f == f0)
						subsumption &= unchecked((sbyte)0xFE);
					else if (f == f1)
						subsumption &= unchecked((sbyte)0xFD);
					else
					{
#if DEBUG
						Nop.CodeCoverage((f & Edge.Flag.EtmString) != 0);
#endif
						subsumption = None;
					}

					if (subsumption <= 0)
						return;
				}

				if (nm0 != 0 && nm1 != 0 && (f & Edge.Flag.EtmNonBareType) != 0)
				{
					_check((int)(f & Edge.Flag.MultiIdMask), nm0, nm1);
					if (subsumption <= 0)
						return;
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// -1 : tfs0 ⊓ tfs1 = ⊥
		/// 0 : no subsumption relationship (possibly ⊥)
		/// 1 : tfs0 ⊑ tfs1
		/// 2 : tfs1 ⊑ tfs0
		/// 3 : tfs0 == tfs1
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public sbyte Check()
		{
			Edge.Flag f, f0 = tfs0.Edge.FlagsId, f1 = tfs1.Edge.FlagsId;
			Debug.Assert(((f0 | f1) & (Edge.Flag.Coreference | Edge.Flag.PrunedDuringParsing)) == 0);
			if (f0 == f1)
				f = f0;
			else
			{
				if (f1 == 0)
					f = f0;
				else if (f0 == 0)
					f = f1;
				else if ((f = tm.UnifyTypes(f0, f1)) == Edge.Flag.Bottom)
					return Bottom;

				throw new Exception();
				//Nop.CodeCoverage();
				//if (f == f0)
				//    subsumption &= unchecked((sbyte)0xFE);
				//else if (f == f1)
				//    subsumption &= unchecked((sbyte)0xFD);
				//else
				//{
				//    Nop.CodeCoverage();	// this is wrong I think
				//    return TfsSubsumptionNone;
				//}
			}
			int c = -(tfs0.next_coref_mark + 1);
			byte* _pp = stackalloc byte[c - (tfs1.next_coref_mark + 1)];
			rgb0 = _pp;
			rgb1 = _pp + c;

			int m0 = tfs0.Edge.Mark, m1 = tfs1.Edge.Mark;
			if (m0 != 0 && m1 != 0 && (f & Edge.Flag.EtmNonBareType) != 0)
				_check((int)(f & Edge.Flag.MultiIdMask), m0, m1);

			return subsumption;
		}
	};
}