using System;
using System.Collections.Generic;
using System.Diagnostics;

using glue.Collections.ReadOnly;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// In accordance with DELPH-IN tradition, we distinguish "types" from "entries." (Copestake 2002 p.106)
	/// Entries include everything except Types: lexical entries, grammar rules, start symbols, and node labels.
	/// In common, both share this abstract base class, Instance, which captures the idea of something that can
	/// have constraints. Types participate in a DAG hierarchy whereas Entries instead refer to a single Type. Thus,
	/// while Types and Entries have an is-a (inheritance) relationship with Instance, every Entry additionally has
	/// a has-a (contains) relationship with exactly one Type.
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract partial class Instance : ISysObj
	{
		public Instance(TypeMgr mgr, String name, List<BaseFeatConstraint> bfc)
		{
			this.mgr = mgr;
			this.name = name;

			SetConstraints(bfc);
		}

		/// <summary>
		/// Type manager (TypeMgr) that this type belongs to
		/// </summary>
		public readonly TypeMgr mgr;

		public Flags m_flags;

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		readonly protected String name;
		public String Name { get { return name; } }
		public String SysObjName { get { return name; } }
		public string SysObjDescription { get { return name; } }

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		[Flags]
		public enum Flags
		{
			/// <summary>set for a type with no child types</summary>
			Leaf = 0x00000001,

			/// <summary>set if this type is a system-generated greater lowest bound (glb) type</summary>
			GlbType = 0x00000002,

			/// <summary>set if this type has any appropriate (inherited or local) features</summary>
			HasAnyFeatures = 0x00000004,

			/// <summary></summary>
			HasLocalFeatures = 0x00000008,

			/// <summary>set if there are no appropriate features in this type's subgraph (inclusive)</summary>
			Atomic = 0x00000010,

			/// <summary>useful for skipping well-formedness unification when  a ⊓ b ⊏ glb</summary>
			HasConstraints = 0x00000020,

			Bottom = 0x00000040,

			/// <summary>set when the type's inherited features have been computed</summary>
			LoadedNonLocalFeatures = 0x00010000,

			/// <summary>set when the type's authored constraint has been constructed based on its TDL tokens</summary>
			LoadedDefinition = 0x00020000,

			/// <summary>set when the type has been unified with its own constraint and with its parent types</summary>
			//Expanding				= 0x00040000,
			Expanded = 0x00080000,
		};

		/// <summary>
		/// if a type has no appropriate features, it can be structure-shared
		/// </summary>
		public bool HasAnyFeatures { get { return (m_flags & Flags.HasAnyFeatures) > 0; } }
		public bool IsBare { get { return (m_flags & Flags.HasAnyFeatures) == 0; } }

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public List<BaseFeatConstraint> m_bfc;

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void SetConstraints(List<BaseFeatConstraint> bfc)
		{
			if (bfc != null)
			{
				if (m_bfc == null)
					m_bfc = bfc;
				else
					m_bfc.AddRange(bfc);
				m_flags |= Type.Flags.HasConstraints;
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		[DebuggerDisplay("{m_flags.HasFlag(Flags.LoadedDefinition)?_definition.ToString():\"(definition not loaded yet)\",nq}")]
		protected TfsEdge _definition;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]	// side effects
		public TfsEdge Definition
		{
			get
			{
				if ((m_flags & Flags.LoadedDefinition) == 0)
					throw new Exception("Definition not loaded yet.");
				return _definition;
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		internal void LoadDefinition(Tray tr)
		{
			if ((m_flags & Flags.LoadedDefinition) > 0)
				return;

			Type t = InstanceType;
			_definition = tr.CreateTfs(t, false);
			if (!IsBare && m_bfc != null)
			{
				ConstraintRef cref = new ConstraintRef(_definition);
				using (TdlNavigator bn = new TdlNavigator(tr, t))
				{
					foreach (BaseFeatConstraint bfc in m_bfc)
						bn.AcceptBaseConstraint(cref, bfc);
				}
				m_bfc = null;
			}
			m_flags |= Flags.LoadedDefinition;
		}

		public abstract Type InstanceType { get; }

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public abstract TfsEdge Expanded { get; }

		public IReadOnlyDictionary<String, ISysObj> SysObjChildren
		{
			get { return SysObjHelper<ISysObj>.Empty; }
		}

		public ISysObj SysObjParent
		{
			get { return mgr.g; }
		}
	};
}