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

using glue.Collections.XSpinLock;
using glue.Collections.BitArray;
using glue.Extensions.Enumerable;
using glue;

#pragma warning disable 0649

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	///
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public partial class TypeMgr
	{
		public const int GLB_CACHE_ENTRIES = 143483;	// a prime number

		public GlbCacheEntry[][] glb_cache = new GlbCacheEntry[GLB_CACHE_ENTRIES][];

		public Dictionary<BitArr, Type> code_dict;

		public struct GlbCacheEntry
		{
			public GlbCacheEntry(int k, Edge.Flag f)
			{
				this.k = k;
				this.f = f;
			}
			public int k;
			public Edge.Flag f;
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void UpdateGlbCacheBareTypes()
		{
			foreach (GlbCacheEntry[] rge in glb_cache)
				if (rge != null)
					for (int i = 0; i < rge.Length; i++)
						if (!type_arr[(int)(rge[i].f & MultiIdMask)].IsBare)
							rge[i].f |= Edge.Flag.EtmNonBareType;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void AddGlb(int id1, int id2, Edge.Flag f)
		{
			Debug.Assert(c_t_bits != -1);
			Debug.Assert(id1 != 0 && id2 != 0 && id1 != id2);

			int k, hash = (k = id1 > id2 ? (id2 << c_t_bits) + id1 : (id1 << c_t_bits) + id2) % GLB_CACHE_ENTRIES;
			GlbCacheEntry gce = new GlbCacheEntry(k, f);
			GlbCacheEntry[] rge = glb_cache[hash];

			/// atomic publishing. don't care about rare case of lost entries due to (CPU) cache incoherence
			glb_cache[hash] = rge == null ? new GlbCacheEntry[] { gce } : rge.Append(gce).OrderBy(g => g.k).ToArray();
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool ComputeAndCacheGlb(int id1, int id2, out Edge.Flag glb_id)
		{
			Type t_glb;
			if (!code_dict.TryGetValue(type_arr[id1].m_code & type_arr[id2].m_code, out t_glb))
			{
				glb_id = Edge.Flag.CachedBottom;
				//System.Threading.Tasks.Task.Factory.StartNew(() =>
				//{
				//    foreach (Type c in t1.AllDescendants)
				//        AddGlb(id2, c.m_id, BottomId);
				//    foreach (Type c in t2.AllDescendants)
				//        AddGlb(id1, c.m_id, BottomId);
				//});
				AddGlb(id1, id2, glb_id);
				return false;
			}
			else
			{
				if (t_glb == StringType)
					glb_id = Edge.Flag.EtmString;
				else
				{
					glb_id = (Edge.Flag)t_glb.m_id;
					if (!t_glb.IsBare)
						glb_id |= Edge.Flag.EtmNonBareType;
				}
				AddGlb(id1, id2, glb_id);
				Debug.Assert(GetEdgeType(glb_id).IsBare == ((glb_id & Edge.Flag.EtmNonBareType) == 0));
				return true;
			}
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public int GetGlbCheckTopAndIdentity(int id1, int id2)
		{
			if (id1 == 0 || id1 == id2)
			{
				return id2;
			}
			if (id2 == 0)
			{
				return id1;
			}
			Edge.Flag glb_id;
			int k = id1 > id2 ? (id2 << c_t_bits) + id1 : (id1 << c_t_bits) + id2;
			GlbCacheEntry[] rge = glb_cache[k % GLB_CACHE_ENTRIES];
			if (rge != null)
			{
				int gk, i = 0;
				do
				{
					GlbCacheEntry gce = rge[i];
					if ((gk = gce.k) == k)
					{
						glb_id = gce.f;
						goto found;
					}
				}
				while (gk < k && ++i < rge.Length);
			}
			if (!ComputeAndCacheGlb(id1, id2, out glb_id))
				return -1;
		found:
			if ((glb_id & Edge.Flag.EtmString) != 0)
				return StringId;
			return (int)(glb_id & MultiIdMask);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool GetGlbFeatureConfig(Edge.Flag f1, Edge.Flag f2, out Edge.Flag glb_id)
		{
			Debug.Assert(fcix_by_type != null);

			int id1 = (f1 & Edge.Flag.EtmString) != 0 ? StringId : (int)(f1 & MultiIdMask);
			int id2 = (f2 & Edge.Flag.EtmString) != 0 ? StringId : (int)(f2 & MultiIdMask);

			Debug.Assert(id1 != id2 && id1 != 0 && id2 != 0);

			int k = id1 > id2 ? (id2 << c_t_bits) + id1 : (id1 << c_t_bits) + id2;
			GlbCacheEntry[] rge = glb_cache[k % GLB_CACHE_ENTRIES];
			if (rge != null)
			{
				int gk, i = 0;
				do
				{
					GlbCacheEntry gce = rge[i];
					if ((gk = gce.k) == k)
						return (glb_id = gce.f) != Edge.Flag.CachedBottom;
				}
				while (gk < k && ++i < rge.Length);
			}
			return ComputeAndCacheGlb(id1, id2, out glb_id);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool CanUnify(int id1, int id2)
		{
			//Debug.Assert(id1 != BottomId && id2 != BottomId);

			if (id1 == 0 || id2 == 0 || id1 == id2)
				return true;

			Edge.Flag glb_id;
			int k = id1 > id2 ? (id2 << c_t_bits) + id1 : (id1 << c_t_bits) + id2;
			GlbCacheEntry[] rge = glb_cache[k % GLB_CACHE_ENTRIES];
			if (rge != null)
			{
				int gk, i = 0;
				do
				{
					GlbCacheEntry gce = rge[i];
					if ((gk = gce.k) == k)
						return gce.f != Edge.Flag.CachedBottom;
				}
				while (gk < k && ++i < rge.Length);
			}
			return ComputeAndCacheGlb(id1, id2, out glb_id);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool CanUnify(Edge.Flag f1, Edge.Flag f2)
		{
			int id1 = (int)(f1 & MultiIdMask);
			int id2 = (int)(f2 & MultiIdMask);

			if ((f1 & Edge.Flag.EtmMask) == Edge.Flag.EtmString)
			{
				if ((f2 & Edge.Flag.EtmMask) == Edge.Flag.EtmString)
					return id1 == id2;
				id1 = StringId;
			}
			else if ((f2 & Edge.Flag.EtmMask) == Edge.Flag.EtmString)
				id2 = StringId;
			else if (id1 == 0 || id2 == 0 || id1 == id2)
				return true;

			Edge.Flag glb_id;
			int k = id1 > id2 ? (id2 << c_t_bits) + id1 : (id1 << c_t_bits) + id2;
			GlbCacheEntry[] rge = glb_cache[k % GLB_CACHE_ENTRIES];
			if (rge != null)
			{
				int gk, i = 0;
				do
				{
					GlbCacheEntry gce = rge[i];
					if ((gk = gce.k) == k)
						return gce.f != Edge.Flag.CachedBottom;
				}
				while (gk < k && ++i < rge.Length);
			}
			return ComputeAndCacheGlb(id1, id2, out glb_id);
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Works correctly for string values, hence the boolean return value
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool GetGlbCheckStr(Edge.Flag f1, Edge.Flag f2, out int id_out)
		{
			int id1 = (int)(f1 & MultiIdMask);
			int id2 = (int)(f2 & MultiIdMask);

			if ((f1 & Edge.Flag.EtmMask) == Edge.Flag.EtmString)
			{
				if ((f2 & Edge.Flag.EtmMask) == Edge.Flag.EtmString)
				{
					id_out = StringId;
					return id1 == id2;
				}
				id1 = StringId;
			}
			else if ((f2 & Edge.Flag.EtmMask) == Edge.Flag.EtmString)
				id2 = StringId;
			else if (id1 == id2)
			{
				id_out = id1;
				return id_out != (int)MultiIdMask;
			}

			if (id1 == 0)
			{
				id_out = id2;
				return true;
			}
			else if (id2 == 0)
			{
				id_out = id1;
				return true;
			}
			Edge.Flag glb_id;
			int k = id1 > id2 ? (id2 << c_t_bits) + id1 : (id1 << c_t_bits) + id2;
			GlbCacheEntry[] rge = glb_cache[k % GLB_CACHE_ENTRIES];
			if (rge != null)
			{
				int gk, i = 0;
				do
				{
					GlbCacheEntry gce = rge[i];
					if ((gk = gce.k) == k)
					{
						glb_id = gce.f;
						break;
					}
				}
				while (gk < k && ++i < rge.Length);
			}
			if (!ComputeAndCacheGlb(id1, id2, out glb_id))
				Debugger.Break();
			id_out = (int)(glb_id & MultiIdMask);
			return glb_id != Edge.Flag.CachedBottom;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Type GlbOfMany(IEnumerable<ConstraintPool> iecp)
		{
			return GetGlbOfMany(iecp.Select(cp => cp.IntroducedBy));
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Consider other options for highly spun code
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Type GetGlbOfMany(IEnumerable<Type> _ie)
		{
			using (IEnumerator<Type> ie = _ie.GetEnumerator())
			{
				if (!ie.MoveNext())
					return null;
				int tid = ie.Current.m_id;
				while (ie.MoveNext())
					if ((tid = GetGlbCheckTopAndIdentity(tid, ie.Current.m_id)) == -1)
						return null;
				return type_arr[tid];
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public String GlbCacheInfo()
		{
			int c = 0;
			int min = int.MaxValue;
			int max = int.MinValue;
			foreach (TypeMgr.GlbCacheEntry[] rge in g.tm.glb_cache)
				if (rge != null)
				{
					int l = rge.Length;
					c += l;
					if (l < min)
						min = l;
					if (l > max)
						max = l;
				}

			return String.Format("glb-cache: slots: {0}  tot: {1}  min {2} max {3} avg {4}",
				GLB_CACHE_ENTRIES, c, min, max, (double)c / GLB_CACHE_ENTRIES);
		}
	};
}