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

using glue.Tasks;
using glue.Collections.XSpinLock;

namespace agree.Parse
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	/// S U B S C R I P T I O N   C H A R T
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	abstract public partial class SubscriptionChart<T> : TaskCancellationChart<T> where T : ChartBase<T>.IMotherDaughter, IEquatable<T>
	{
		internal SubscriptionChart(ParserConfiguration config, ISysObj so_owner, String source_text, int c_cols)
			: base(config, so_owner, source_text, c_cols)
		{
			this.srcs_leftof = new LeftNotificationSource[c_cols - 1];
			this.srcs_rightof = new RightNotificationSource[c_cols - 1];
			for (int i=0; i < c_cols - 1; i++)
			{
				srcs_leftof[i] = new LeftNotificationSource(this, i + 1);
				srcs_rightof[i] = new RightNotificationSource(this, i);
			}
		}

		LeftNotificationSource[] srcs_leftof;
		RightNotificationSource[] srcs_rightof;

		internal void SubscribeRightOf(int i_col, ActiveEdge ace)
		{
			srcs_rightof[i_col].Subscribe(ace);
		}

		internal void SubscribeLeftOf(int i_col, ActiveEdge ace)
		{
			srcs_leftof[i_col - 1].Subscribe(ace);
		}

		protected abstract void GenerateRules(IParseChartEdge pce);

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <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>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if false
		internal Task AddEdgeAsync(PassiveEdge pce)
		{
			return StartAttachedChildTask(AddEdgeInner, pce);
		}
#endif

		internal void AddEdgeInner(IParseChartEdge pce)
		{
			if (ParseTime.TotalSeconds > config.ItemTimeoutSeconds)
				throw new ParseTimeoutException();

			// add edge to chart with its default inert sequence stamp. this stamps it with a sequence barrier
			AddEdge(pce);

			// now that there's a sequence ID, print out some stuff
			ChartEdgeReport(pce);

			// a task for generating new rules
			StartAttachedChildTask(GenerateRules, pce);

			// send the edge to interested parties:
			// 1. parties for which this item is to the right
			int i_start = pce.ChartSpan.StartIndex;
			if (i_start > 0)
				srcs_rightof[i_start - 1].SendToAllAsync(pce);

			// 2. parties for which this item is to the left
			int i_end = pce.ChartSpan.EndIndex;
			if (i_end < ColumnCount - 1)
				srcs_leftof[(i_end - 1) + 1].SendToAllAsync(pce);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Clearing subscribers allows references to active edges which were not productive during the parse to be released
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		protected void ClearSubscribers()
		{
			foreach (NotificationSource ns in srcs_leftof.AsEnumerable<NotificationSource>().Concat(srcs_rightof))
				foreach (ActiveEdge ae in ns.EnumerateSeq(AtomicStamp.InertValue))
					ae.Dispose();

			srcs_leftof = null;
			srcs_rightof = null;
		}
	};
}