using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

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

namespace agree.UnitTest
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	///
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class TypeMgrUnitTests : TypeMgr
	{
		public TypeMgrUnitTests(Globals config, Grammar g)
			: base(g, config)
		{
		}

		public HashSet<String> PathsBetween(String t1, String t2)
		{
			return PathsBetween(type_dict[t1], type_dict[t2]);
		}
		public HashSet<String> PathsBetween(Type t1, Type t2)
		{
			HashSet<String> hs = new HashSet<String>();
			if (t1 == t2)
				return hs;
			if (t1.IsSubtypeOf(t2))
			{
				Type tmp = t1;
				t1 = t2;
				t2 = tmp;
			}
			else if (!t1.IsSupertypeOf(t2))
				return hs;

			Action<String, Type> f = null;
			f = (s, t) =>
			{
				String x = (s.Length == 0 ? String.Empty : s + " -> ") + t.Name;
				if (t == t2)
					hs.Add(x);
				else foreach (Type c in t.Children)
					{
						if (c.IsSupertypeOrEqual(t2))
							f(x, c);
					}
			};
			f("", t1);
			return hs;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if false	// update AndToDest for distilled bitarr
		public void CheckForUnusedGlbs()
		{
			HashSet<Type> visited = new HashSet<Type>();
			BitArr z = new BitArr(code_size);
			foreach (Type n1 in type_dict.Values)
				foreach (Type n2 in type_dict.Values)
				{
					if (n1 == n2)
						continue;
					// this version allows us to reuse 'z'
					if (!BitArr.AndToDest(z, n1.m_code, n2.m_code))
						continue;
					Type glb;
					if (!code_dict.TryGetValue(z, out glb))
						throw new Exception(String.Format("missing GLB for {0} and {1}", n1.Name, n2.Name));
					visited.Add(glb);
				}
			foreach (Type n in type_dict.Values)
				if (n.IsGlb && !visited.Contains(n))
					throw new Exception(String.Format("unreferenced GLB {0}", n.Name));
		}
#endif

#if DEBUG
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void CheckGlb()
		{
			int err_count = 0;
			HashSet<BitArr> unique_problems = new HashSet<BitArr>();

			int i;
			Type[] a_t = type_dict.Values.ToArray();
			for (i = 0; i < a_t.Length; i++)
			{
				for (int j = i + 1; j < a_t.Length; j++)
				{
					HashSet<Type> glbs = a_t[i].GlbCheck(a_t[j], TopType);
					if (glbs != null)
					{
						BitArr bc = a_t[i].m_code & a_t[j].m_code;
						unique_problems.Add(bc);
						Type should_use = code_dict[bc];
						Console.WriteLine("greatest lower bound problem between {0} & {1}", a_t[i].Name, a_t[j].Name);
						Console.WriteLine("should use {0} {1}", should_use.Name, bc.OnesPositions().Layout());
						foreach (Type tt in glbs)
							Console.WriteLine("\t{0}\t[level {1} {2}]", tt.Name, tt.m_level, tt.m_code.OnesPositions().Layout());
						err_count++;
					}
				}
			}
			if (unique_problems.Count > 0)
			{
				Console.WriteLine("{0} glb problems, {1} unique", err_count, unique_problems.Count);
				foreach (BitArr bc in unique_problems)
					Console.WriteLine("{0}", code_dict[bc].Name);
			}
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void CheckGlbAlgs()
		{
			foreach (Type t in type_dict.Values)
			{
				HashSet<Type> hs = t.AllAncestors;
				if (hs.Contains(t))
					throw new Exception();
				foreach (Type t2 in type_dict.Values)
				{
					if (hs.Contains(t2))
					{
						if (t2.IsSubtypeOf(t) || t.IsSupertypeOf(t2))
							throw new Exception();
					}
					else
					{
						if (t2.IsSupertypeOf(t) || t.IsSubtypeOf(t2))
							throw new Exception();
					}
				}
				hs = t.AllDescendants;
				if (hs.Contains(t))
					throw new Exception();
				foreach (Type t2 in type_dict.Values)
				{
					if (hs.Contains(t2))
					{
						if (t2.IsSupertypeOf(t) || t.IsSubtypeOf(t2))
							throw new Exception();
					}
					else
					{
						if (t2.IsSubtypeOf(t) || t.IsSupertypeOf(t2))
							throw new Exception();
					}
				}
			}
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void CheckGlbPairForProblems(Type t1, Type t2)
		{
			HashSet<Type> glbs = t1.GlbCheck(t2, TopType);
			if (glbs != null)
			{
				BitArr bc = t1.m_code & t2.m_code;
				Type should_use = code_dict[bc];
				Console.WriteLine("greatest lower bound problem between {0} & {1}", t1.Name, t2.Name);
				Console.WriteLine("should use {0}", should_use.Name);
				foreach (Type tt1 in glbs)
					Console.WriteLine("\t{0}\t[level {1}]", tt1.Name, tt1.m_level);
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void DisplayMaximalFeatures()
		{
			foreach (Type tt in type_dict.Values)
			{
				Console.WriteLine(tt.Name);
				foreach (TypeMgr.FeatureInfo fi in tt._dbg_features().Where(cp => cp.maximal_type == tt))
					Console.WriteLine("\t{0}", fi.feature.ToUpper());
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void DisplayAllFeatures()
		{
			foreach (Type tt in type_dict.Values)
			{
				Console.WriteLine(tt.Name);
				foreach (TypeMgr.FeatureInfo fi in tt._dbg_features())
				{
					Console.WriteLine("\t{0}", fi.feature.ToUpper());
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void DisplayAllTypes()
		{
			foreach (Type tt in type_dict.Values)
			{
				Console.WriteLine(tt.Name);
				foreach (var fi in tt._dbg_features())
				{
					Console.Write("\t{0}", fi.feature.ToUpper());
					if (fi.maximal_type == tt)
						Console.Write("(local)");
					Console.WriteLine();
				}
			}
		}
#endif

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void CheckFeaturesFlag()
		{
			foreach (Type t in type_dict.Values)
			{
				if (t.HasAnyFeatures != (t.FeatureIndexes.Length == 0))
					throw new Exception();
			}
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void CheckRedundantLinks()
		{
			Stack<Type> stk = new Stack<Type>();
			Action<Type> f = null;
			f = node =>
			{
				if (!stk.Contains(node))
				{
					stk.Push(node);
					foreach (Type ch in node.Children)
						f(ch);
					stk.Pop();
				}

				IEnumerable<Type> r = stk.Skip(1).Intersect(node.Parents);
				if (r.Any())
					throw new Exception(String.Format("redundant link {0} to {1}", r.First().Name, node.Name));
			};
			f(TopType);
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void CheckForCycle()
		{
			Stack<Type> stk = new Stack<Type>();
			Action<Type> f = null;
			f = node =>
			{
				if (stk.Contains(node))
					throw new Exception("cycle detected");
				stk.Push(node);
				foreach (Type ch in node.Children)
					f(ch);
				stk.Pop();
			};
			f(TopType);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public int TypeEdgeCount()
		{
			HashSet<Type> visited = new HashSet<Type>();
			int count = 0;
			Action<Type> f = null;
			f = node =>
			{
				if (!visited.Contains(node))
				{
					visited.Add(node);
					count += node.ChildCount;
					foreach (Type ch in node.Children)
						f(ch);
				}
			};
			f(TopType);
			return count;
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void DisplayTree(bool f_all)
		{
			Console.WriteLine("-------------- Type Hierarchy Display -------------------");
			foreach (Type t in type_dict.Values)
			{
				Console.WriteLine(t.Name + ":");
				if (!f_all)
				{
					if (t.Parents.Any())
					{
						Console.Write("\tparents: { ");
						foreach (String s in t.Parents.Select(e => e.Name))
							Console.Write(s + " ");
						Console.WriteLine("}");
					}
					if (t.Children.Any())
					{
						Console.Write("\tchildren: { ");
						foreach (String s in t.Children.Select(e => e.Name))
							Console.Write(s + " ");
						Console.WriteLine("}");
					}
				}
				else
				{
					foreach (String s in t.AllAncestors.Select(e => e.Name))
						Console.WriteLine("\tP : " + s);
					foreach (String s in t.AllDescendants.Select(e => e.Name))
						Console.WriteLine("\tC : " + s);
				}
			}
			Console.WriteLine("---------------------------------------------------------");
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void CheckConjoinedParents(TextWriter tw)
		{
			BitArr ba_conj = new BitArr(code_size);
			foreach (Type tt in type_dict.Values)
			{
				if (tt.Parents.Count > 1)
				{
					ba_conj.SetAll();
					foreach (Type tt_par in tt.Parents)
					{
						if (!ba_conj.AndEq(tt_par.m_code))
							throw new Exception(String.Format("Conjoined parents '{0}' on type {1} do not unify.", tt.Parents.Select(e => e.Name).StringJoin(" "), tt_par.Name));
					}
					if (tw != null)
						tw.WriteLine("{0} := {1}  ... {2}", tt.Name, tt.Parents.Select(e => e.Name).StringJoin(" & "), code_dict[ba_conj].Name);
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void DisplayConstraintTokens()
		{
			foreach (Type tt in type_dict.Values.Where(e => e.m_bfc != null))
			{
				Console.WriteLine(tt.Name);
				foreach (var bfc in tt.m_bfc)
				{
					Console.WriteLine("\t{0}", bfc.ToString());
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void DisplayNonBareTypes()
		{
			int i = 0;
			foreach (Type tt in type_dict.Values.Where(e => e.HasAnyFeatures))
			{
				i++;
				Console.WriteLine(tt.Name + "\t" + tt.IsSubtypeOf(TopType));
			}
			Console.WriteLine(i);
		}

	};

	static class Extensions
	{
		public static String Layout(this IEnumerable<int> i)
		{
			return "{" + i.StringJoin(" ") + "}";
		}
	};
}