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

using miew.Concurrency;
using miew.Debugging;

#pragma warning disable 0649

namespace agree
{
	using unif_stats = ParseControl.Stats._unification;

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	/// S U B S C R I P T I O N   C H A R T
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	abstract public partial class SubscriptionChart : ChartBase
	{
		internal SubscriptionChart(ParseControl ctrl)
			: base(ctrl)
		{
			this.srcs_leftof = new NotificationSource[c_columns - 1];
			this.srcs_rightof = new NotificationSource[c_columns - 1];
			for (int i = 0; i < c_columns - 1; i++)
			{
				srcs_leftof[i] = new NotificationSource(this, LEFT, i + 1);
				srcs_rightof[i] = new NotificationSource(this, RIGHT, i);
			}
		}

		NotificationSource[] srcs_leftof, srcs_rightof;
		protected const bool LEFT = true, RIGHT = false;

		protected abstract void GenerateActiveEdges(IParseObj pce);
		protected abstract bool SubsumptionPacking(IParseObj pce);

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		internal void Subscribe(bool f_left, ActiveEdge ace, ref unif_stats u_stats)
		{
			if (f_left)
				srcs_leftof[ace.CompletedSpan.StartIndex - 1].Subscribe(ace, ref u_stats);
			else
				srcs_rightof[ace.CompletedSpan.EndIndex].Subscribe(ace, ref u_stats);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Accept new edges into the chart. Atomically with insertion into the chart, stamp the edge with an interlocking
		/// sequence barrier in order to separate interested parties into two groups: those who registered previously--and 
		/// who will receive the edge now, versus those who register at any point afterwards, who will receive the edge via 
		/// the retroactive edge delivery mechanism which is part of the subscription process.
		/// </summary>
		/// <remarks>
		/// We check subsumption before making the new passive edge visible ('active'), because if proactive packing is 
		/// performed we don't want to deal with the complications of having the edge ever having been published. The 
		/// purpose of putting the edge in the chart at all, however, as we just did, is so that anyone who tries to determine 
		/// sequencing relative to this edge will be blocked from proceeding further up the chart cell until this
		/// subsumption check is complete. The bloking ensures deterministic correctness of maximal packing: the global 
		/// sequencing system is used to partition edges within a cell for the purposes of evaluating 
		/// subsumption. In this case, since the objects under comparison are within the same list (unlike the passive-to-
		/// active edge subscription mechanism), and the operation (subsumption checking) is commutative, we only need to 
		/// evaluate within the lower partition.
		/// </remarks>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void AddParseObj(IParseObj ce)
		{
			if (ctrl.IsParseCancelled)
				return;

			/// add passive edge to chart
			this[ce.TokenSpan].Add(ce);

			/// now that there's a sequence ID, print out some stuff
			if (ctrl.config.system.tw_debug != null)
				ChartEdgeReport(ce);

			/// see notes above
			if (ctrl.parser_config.packing != Config.Parser.PackingOpts.None && SubsumptionPacking(ce))
				return;

			/// make the new edge visible
			ce.SetStateActive();

			/// a task for generating new rules
			ctrl.StartAttachedChildTask(GenerateActiveEdges, ce);

			unif_stats u_stats = new unif_stats(ParseControl.Stats.Visibility.Private);

			/// send the edge to interested parties:
			/// 1. parties for which this item is to the right
			int i_start = ce.TokenSpan.StartIndex;
			if (i_start > 0)
				srcs_rightof[i_start - 1].SendToAllSubscribers(ce, ref u_stats);

			/// 2. parties for which this item is to the left
			int i_end = ce.TokenSpan.EndIndex;
			if (i_end < ColumnCount - 1)
				srcs_leftof[(i_end - 1) + 1].SendToAllSubscribers(ce, ref u_stats);

			ctrl.stats.Parsing.Unification.Add(u_stats);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Clearing subscribers allows references to active edges which were not productive during the parse to be released
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void ClearSubscribers()
		{
			for (int i = 0; i < c_columns - 1; i++)
			{
				srcs_leftof[i].Clear();
				srcs_rightof[i].Clear();
			}
			srcs_leftof = null;
			srcs_rightof = null;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		/// N O T I F I C A T I O N   S O U R C E 
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		internal struct NotificationSource
		{
			readonly SubscriptionChart m_chart;
			readonly int i_col;
			readonly bool f_left;
			ActiveEdge m_first;

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public NotificationSource(SubscriptionChart chart, bool f_left, int i_col)
			{
				this.f_left = f_left;
				this.m_chart = chart;
				this.i_col = i_col;
				this.m_first = null;
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// This is the normal (non-retroactive) subscription delivery mechanism which sends new edges to existing
			/// active edges. Also opportunistically try to patch over defunct active edges that we might encounter. Do not 
			/// send to active edges with a higher sequence number than the new edge. Also try not to forward to subscribers 
			/// who are on packing hold although this can't be guaranteed because active objects are subject to asynchronous 
			/// entry of that state, causing them to possibly switch to inactive after we observe them. On the other 
			/// hand, regarding visible objects, the 'pending' state is only exited, never entered, so it will be successfully 
			/// banned but possibly missed, this latter condition being handled by the atomic sequence, which ensures
			/// that anything missed is sent retroactively.
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			internal void SendToAllSubscribers(IParseObj ce, ref unif_stats u_stats)
			{
				int i_seq = ce.SequenceId;

				for (ActiveEdge ae = m_first; ae != null; ae = ae.m_next)
				{
					if ((ae.State & SequenceControl.stb_Remove) > 0)
						continue;

					if (!ae.SeqIdBelow(i_seq))
						continue;

#if false
					var pe = ce as PassiveEdge.Derived;
					if (pe != null && pe.IsHold())
						pe.DirectCancel();
#endif

					if (ce.IsActive())
						ae.TryMatchEdge(ce, ref u_stats);
				}
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// Subscribe an active edge to this notification list.
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public void Add(ActiveEdge ae_new)
			{
				Debug.Assert(ae_new.State == default(byte));
				ae_new.SetStateRaw(SequenceControl.PENDING);

				ActiveEdge _tmp;
				do
					_tmp = ae_new.m_next = m_first;
				while (Interlocked.CompareExchange(ref m_first, ae_new, _tmp) != _tmp);

				m_chart.StampSequence(ae_new);
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// Add the target to the list of subscribers. Because rule_targets is a list that is interlocked with the
			/// ChartEdge stamping sequence, Add() atomically imposes an (arbitrary) partition upon all past (and future)
			/// ChartEdges such that those which arrive prior to the sequencing point are sent by the retroactive mechanism,
			/// and ChartEdges sent afterwards will be posted to the target via the subscription delivery mechanism.
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public void Subscribe(ActiveEdge ace, ref unif_stats u_stats)
			{
				/// add to a linked list and set a sequence barrier
				this.Add(ace);
				ace.SetStateActive();
				int i_seq = ace.SequenceId;

				ChartCell._multi_cell_enum e = f_left ?
					new ChartCell._multi_cell_enum(m_chart, 0, 1, i_col - 1, -1) :
					new ChartCell._multi_cell_enum(m_chart, i_col + 1, 0, 0, 1);

				/// partitioning according to the sequence stamp, only send ChartEdges with a lower
				/// sequence number to the newly connected target.
				for (IParseObj ce, ce_prev = null; e.MoveNext(); ce_prev = ce)
				{
					if (((ce = e.Current).State & (SequenceControl.stb_Remove | SequenceControl.stb_Hold)) > 0)
						continue;

					if (ce.SeqIdAbove(i_seq))
						continue;

#if false
					var pe = ce as PassiveEdge.Derived;
					if (pe != null && pe.IsHold())
						pe.DirectCancel();
#endif

					if (ce.IsActive())
						ace.TryMatchEdge(ce, ref u_stats);
				}
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// release ActiveEdge references in the linked-list so the objects can be collected
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public void Clear()
			{
				ActiveEdge ae = m_first;
				while (ae != null)
				{
					ActiveEdge _tmp = ae.m_next;
					ae.m_next = null;
					ae = _tmp;
				}
				m_first = null;
			}
		};
	};
}