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

using miew.BitArray;
using miew.Enumerable;

#if ILFUNC
using IlFunc;
#endif

#pragma warning disable 0649

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	///
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public unsafe partial class TypeMgr
	{
		public const uint GLB_CACHE_ENTRIES = 300007;//570001;	// a prime number

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

		public Dictionary<BitArr, Type> code_dict;

		public struct GlbCacheEntry
		{
			public GlbCacheEntry(uint k, Edge.Flag f)
			{
				this.k = k;
				this.f = f;
			}
			public uint 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 & Edge.Flag.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);

			uint k, hash = (k = (uint)(id1 < id2 ? (id2 << 16) | id1 : (id1 << 16) | id2)) % GLB_CACHE_ENTRIES;
			GlbCacheEntry gce = new GlbCacheEntry(k, f);

			GlbCacheEntry[] rge = glb_cache[hash];
			if (rge == null)
				glb_cache[hash] = new GlbCacheEntry[] { gce };
			else
			{
				/// ordered insert
				int i = 0, j = 0, c_prev = rge.Length;
				GlbCacheEntry[] rgenew = new GlbCacheEntry[c_prev + 1];
				while (i < c_prev)
				{
					if (k < rge[i].k)
					{
						rgenew[j++] = gce;
						k = int.MaxValue;
					}
					rgenew[j++] = rge[i++];
				}
				if (i == j)
					rgenew[j] = gce;
				/// atomic publishing; don't care about lost entries due to race
				glb_cache[hash] = rgenew;
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool ComputeAndCacheGlb(uint k, int id1, int id2, out Edge.Flag ef_glb)
		{
			Type t_glb;
			bool b_ret;
			/// note: already measured using a direct code_arr and it was slower; presumably it's more important to
			/// keep the type_arr cache warm.
			if (!(b_ret = code_dict.TryGetValue(type_arr[id1].m_code.AndWithHash(type_arr[id2].m_code), out t_glb)))
			{
				ef_glb = Edge.Flag.Bottom;
				// todo: more cache entries can be implied, see "bag of useful techniques" paper
			}
			else
			{
				if (t_glb == StringType)
					ef_glb = Edge.Flag.EtmString;
				else
				{
					ef_glb = (Edge.Flag)t_glb.m_id;
					if ((t_glb.m_flags & Type.Flags.HasAnyFeatures) != 0)
						ef_glb |= Edge.Flag.EtmNonBareType;
				}
			}
			GlbCacheEntry gce = new GlbCacheEntry(k, ef_glb);

			uint hash;
			GlbCacheEntry[] rge = glb_cache[hash = k % GLB_CACHE_ENTRIES];
			if (rge == null)
				glb_cache[hash] = new GlbCacheEntry[] { gce };
			else
			{
				/// ordered insert
				GlbCacheEntry[] rgenew = new GlbCacheEntry[rge.Length + 1];
				int i = 0, j = 0;
				while (i < rge.Length)
				{
					if (k < rge[i].k)
					{
						rgenew[j++] = gce;
						k = int.MaxValue;
					}
					rgenew[j++] = rge[i++];
				}
				if (i == j)
					rgenew[j] = gce;
				/// atomic publishing; don't care about lost entries due to race
				glb_cache[hash] = rgenew;
			}
			return b_ret;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Edge.Flag ComputeAndCacheGlb(uint k, int id1, int id2)
		{
			Type t_glb;
			Edge.Flag ef_glb;
			/// note: already measured using a direct code_arr and it was slower; presumably it's more important to
			/// keep the type_arr cache warm.
			if (!code_dict.TryGetValue(type_arr[id1].m_code.AndWithHash(type_arr[id2].m_code), out t_glb))
			{
				ef_glb = Edge.Flag.Bottom;
				// todo: more cache entries can be implied, see "bag of useful techniques" paper
			}
			else
			{
				if (t_glb == StringType)
					ef_glb = Edge.Flag.EtmString;
				else
				{
					ef_glb = (Edge.Flag)t_glb.m_id;
					if ((t_glb.m_flags & Type.Flags.HasAnyFeatures) != 0)
						ef_glb |= Edge.Flag.EtmNonBareType;
				}
			}
			uint hash = k % GLB_CACHE_ENTRIES;
			GlbCacheEntry gce = new GlbCacheEntry(k, ef_glb);

			/// ordered insert
			GlbCacheEntry[] rgenew, rge = glb_cache[hash];
			if (rge == null)
				rgenew = new GlbCacheEntry[] { gce };
			else
			{
				rgenew = new GlbCacheEntry[rge.Length + 1];
				int i = 0, j = 0;
				while (i < rge.Length)
				{
					if (k < rge[i].k)
					{
						rgenew[j++] = gce;
						k = int.MaxValue;
					}
					rgenew[j++] = rge[i++];
				}
				if (i == j)
					rgenew[j] = gce;
			}
			/// atomic publishing; don't care about lost entries due to race
			glb_cache[hash] = rgenew;
			return ef_glb;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public int GetGlbCheckTopAndIdentity(int id1, int id2)
		{
			if (id1 == 0 || id1 == id2)
				return id2;
			if (id2 == 0)
				return id1;

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

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

			uint k = (uint)(id1 < id2 ? (id2 << 16) | id1 : (id1 << 16) | id2);
			GlbCacheEntry[] rge = glb_cache[k % GLB_CACHE_ENTRIES];
			if (rge != null)
			{
				uint gk, i = 0;
				do
				{
					GlbCacheEntry gce = rge[i];
					if ((gk = gce.k) == k)
						return gce.f >= 0;
				}
				while (gk < k && ++i < rge.Length);
			}
			return ComputeAndCacheGlb(k, id1, id2) >= 0;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool CanUnify(Edge.Flag f1, Edge.Flag f2)
		{
			int id1, id2;
			if ((f1 & Edge.Flag.EtmString) != 0)
			{
				if ((f2 & Edge.Flag.EtmString) != 0)
					return ((f1 ^ f2) & Edge.Flag.MultiIdMask) == 0;
				id1 = StringId;
				id2 = (int)(f2 & Edge.Flag.MultiIdMask);
			}
			else if ((f2 & Edge.Flag.EtmString) != 0)
			{
				id1 = (int)(f1 & Edge.Flag.MultiIdMask);
				id2 = StringId;
			}
			else if ((id1 = (int)(f1 & Edge.Flag.MultiIdMask)) == 0 || (id2 = (int)(f2 & Edge.Flag.MultiIdMask)) == 0 || id1 == id2)
				return true;

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

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Edge.Flag UnifyTypes(Edge.Flag f0, Edge.Flag f1)
		{
			Debug.Assert(f0 != f1 && f0 != 0 && f1 != 0);
			int t0 = (int)(f0 & Edge.Flag.MultiIdMask);
			int t1 = (int)(f1 & Edge.Flag.MultiIdMask);

			Edge.Flag nfex;
			if (((nfex = ((f0 | f1) & (Edge.Flag.Coreference | Edge.Flag.EtmString))) & Edge.Flag.EtmString) != 0)
			{
				if ((f0 & Edge.Flag.EtmString) == 0)
					return t0 == 0 || CanUnify(StringId, t0) ? f1 | nfex : Edge.Flag.Bottom;

				if ((f1 & Edge.Flag.EtmString) == 0)
					return t1 == 0 || CanUnify(StringId, t1) ? f0 | nfex : Edge.Flag.Bottom;

				return (t0 == 0 || t0 == t1) ? (f1 | nfex) : (t1 == 0) ? (f0 | nfex) : Edge.Flag.Bottom;
			}

			if (t0 == 0 || t0 == t1)
				return f1 | nfex;
			if (t1 == 0)
				return f0 | nfex;

			uint k = (uint)(t0 < t1 ? (t1 << 16) | t0 : (t0 << 16) | t1);
			TypeMgr.GlbCacheEntry[] rge = glb_cache[k % TypeMgr.GLB_CACHE_ENTRIES];
			if (rge != null)
			{
				TypeMgr.GlbCacheEntry gce;
				uint gk, i = 0;
				do
					if ((gk = (gce = rge[i]).k) == k)
						return gce.f | nfex;
				while (gk < k && ++i < rge.Length);
			}
			return ComputeAndCacheGlb(k, t0, t1) | nfex;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Edge.Flag UnifyTypesNoCoref(Edge.Flag f0, Edge.Flag f1)
		{
			int t0 = (int)(f0 & Edge.Flag.MultiIdMask);
			int t1 = (int)(f1 & Edge.Flag.MultiIdMask);

			if (((f0 | f1) & Edge.Flag.EtmString) != 0)
			{
				if ((f0 & Edge.Flag.EtmString) == 0)
				{
					if (t0 != 0 && !CanUnify(StringId, t0))
						return Edge.Flag.Bottom;
					return f1;
				}
				else if ((f1 & Edge.Flag.EtmString) == 0)
				{
					if (t1 != 0 && !CanUnify(StringId, t1))
						return Edge.Flag.Bottom;
					return f0;
				}
				else if (t0 == 0 || t0 == t1)
					return f1;
				else if (t1 != 0)
					return Edge.Flag.Bottom;
				return f0;
			}
			Debug.Assert(t0 != 0 && t1 != 0 && t0 != t1);

			uint k = (uint)(t0 < t1 ? (t1 << 16) | t0 : (t0 << 16) | t1);
			TypeMgr.GlbCacheEntry[] rge = glb_cache[k % TypeMgr.GLB_CACHE_ENTRIES];
			if (rge != null)
			{
				TypeMgr.GlbCacheEntry gce;
				uint gk, i = 0;
				do
					if ((gk = (gce = rge[i]).k) == k)
					{
						return f0 = gce.f;
					}
				while (gk < k && ++i < rge.Length);
			}
			return f0 = ComputeAndCacheGlb(k, t0, t1);
		}
		
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool UnifyInTypeNoCoref(ref Edge.Flag f, Edge.Flag nf)
		{
			Debug.Assert(nf >= 0);
			if (nf == 0 || f == nf)
				return true;
			if (f == 0)
			{
				f = nf;
				return true;
			}

			Edge.Flag nfex = (f | nf) & Edge.Flag.EtmString;
			int t0 = (int)(f & Edge.Flag.MultiIdMask);
			int t1 = (int)(nf & Edge.Flag.MultiIdMask);

			if (nfex != 0)
			{
				if ((f & Edge.Flag.EtmString) == 0)
				{
					if (t0 != 0 && !CanUnify(StringId, t0))
						return false;
					f = nf;
				}
				else if ((nf & Edge.Flag.EtmString) == 0)
				{
					if (t1 != 0 && !CanUnify(StringId, t1))
						return false;
				}
				else if (t0 == 0 || t0 == t1)
					f = nf;
				else if (t1 != 0)
					return false;
				return true;
			}

			uint k = (uint)(t0 < t1 ? (t1 << 16) | t0 : (t0 << 16) | t1);
			TypeMgr.GlbCacheEntry[] rge = glb_cache[k % TypeMgr.GLB_CACHE_ENTRIES];
			if (rge != null)
			{
				TypeMgr.GlbCacheEntry gce;
				uint gk, i = 0;
				do
					if ((gk = (gce = rge[i]).k) == k)
					{
						f = gce.f;
						return f != Edge.Flag.Bottom;
					}
				while (gk < k && ++i < rge.Length);
			}
			f = ComputeAndCacheGlb(k, t0, t1);
			return f != Edge.Flag.Bottom;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Type GlbOfMany(IEnumerable<int> ie_fix)
		{
			return GetGlbOfMany(ie_fix.Select(fix => feat_arr[fix].maximal_type));
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <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 load {4:0.000}",
				GLB_CACHE_ENTRIES, c, min, max, (double)c / GLB_CACHE_ENTRIES);
		}

		public Type UnifyTypes(String t0, String t1)
		{
			Edge.Flag f = UnifyTypes((Edge.Flag)type_dict[t0].m_id, (Edge.Flag)type_dict[t1].m_id);
			if (f == Edge.Flag.Bottom)
				return null;
			if ((f & Edge.Flag.EtmString) != 0)
				return StringType;
			return type_arr[(int)(f & Edge.Flag.MultiIdMask)];
		}

		public ushort*[] rgpfix_allft;
		public ushort*[] rgpfix_restr;
		public ushort*[] rgpfix_dargs;
		public ushort*[] rgpfix_da_rs;

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public unsafe void UnsafeInit(int[][] rgrgfix)
		{
			HashSet<int> restr = config.parser.packing_restrictors.Select(s => feat_map[s].i_feat).ToHashSet();
			HashSet<int> dargs = config.parser.deleted_daughters.Select(s => feat_map[s].i_feat).ToHashSet();

			int c_restr, c_dargs, c_da_rs, c_allft;
			c_restr = c_dargs = c_da_rs = c_allft = rgrgfix.Length;

			foreach (int[] rg in rgrgfix)
			{
				c_allft += rg.Length;
				foreach (int fix in rg)
				{
					bool br = !restr.Contains(fix);
					bool bd = !dargs.Contains(fix);
					if (br)
						c_restr++;
					if (bd)
						c_dargs++;
					if (br && bd)
						c_da_rs++;
				}
			}

			ushort* pi_allft = (ushort*)Marshal.AllocHGlobal((c_allft + c_restr + c_dargs + c_da_rs) * sizeof(ushort));
			ushort* pi_restr = pi_allft + c_allft;
			ushort* pi_dargs = pi_restr + c_restr;
			ushort* pi_da_rs = pi_dargs + c_dargs;

			rgpfix_allft = new ushort*[rgrgfix.Length];
			rgpfix_restr = new ushort*[rgrgfix.Length];
			rgpfix_dargs = new ushort*[rgrgfix.Length];
			rgpfix_da_rs = new ushort*[rgrgfix.Length];
			int i = 0;
			foreach (int[] rg in rgrgfix)
			{
				*(rgpfix_allft[i] = pi_allft++) = (ushort)rg.Length;
				ushort* p0 = (rgpfix_restr[i] = pi_restr++);
				ushort* p1 = (rgpfix_dargs[i] = pi_dargs++);
				ushort* p2 = (rgpfix_da_rs[i] = pi_da_rs++);

				foreach (int fix in rg.OrderBy(x => x))
				{
					*pi_allft++ = (ushort)fix;
					bool br = !restr.Contains(fix);
					bool bd = !dargs.Contains(fix);
					if (br)
						*pi_restr++ = (ushort)fix;
					if (bd)
						*pi_dargs++ = (ushort)fix;
					if (br && bd)
						*pi_da_rs++ = (ushort)fix;
				}
				*p0 = (ushort)(pi_restr - p0 - 1);
				*p1 = (ushort)(pi_dargs - p1 - 1);
				*p2 = (ushort)(pi_da_rs - p2 - 1);
				i++;
			}
		}
	};

}