package org.aksw.sparqlify.core.algorithms;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.aksw.commons.collections.CartesianProduct;
import org.aksw.commons.collections.generator.Generator;
import org.aksw.commons.collections.generator.GeneratorBlacklist;
import org.aksw.jena_sparql_api.views.E_RdfTerm;
import org.aksw.jena_sparql_api.views.RestrictedExpr;
import org.aksw.jena_sparql_api.views.VarDefinition;
import org.aksw.sparqlify.algebra.sql.exprs2.S_Coalesce;
import org.aksw.sparqlify.algebra.sql.exprs2.SqlAggFunction;
import org.aksw.sparqlify.algebra.sql.exprs2.SqlExpr;
import org.aksw.sparqlify.algebra.sql.nodes.Projection;
import org.aksw.sparqlify.algebra.sql.nodes.SqlOp;
import org.aksw.sparqlify.algebra.sql.nodes.SqlOpExtend;
import org.aksw.sparqlify.algebra.sql.nodes.SqlOpGroupBy;
import org.aksw.sparqlify.core.TypeToken;
import org.aksw.sparqlify.core.cast.TypeSystem;
import org.aksw.sparqlify.core.domain.input.Mapping;
import org.aksw.sparqlify.core.domain.input.MappingUnion;
import org.aksw.sparqlify.core.interfaces.SqlTranslator;
import org.aksw.sparqlify.trash.ExprCommonFactor;
import org.apache.jena.sparql.core.Var;
import org.apache.jena.sparql.core.VarExprList;
import org.apache.jena.sparql.expr.Expr;
import org.apache.jena.sparql.expr.ExprAggregator;
import org.apache.jena.sparql.expr.ExprVar;
import org.apache.jena.sparql.expr.aggregate.Aggregator;

import com.google.common.base.Joiner;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;



/**
 * Groups RDF Terms by their signature.
 * 
 * @author raven
 *
 */
//class RdfTermGrouper {
//	public add(E_RdfTerm rdfTerm) {
//		rdfTerm.getSignature
//		
//	}
//}

public class MappingRefactor {


	/*
	public static List<Mapping> refactorToUnion(List<Mapping> ms, Var var) {
		List<Mapping> result = new ArrayList<Mapping>();
		
		for(Mapping m : ms) {
			refactorToUnion(m, var, result);
		}
	
		return result;
	}

	
	group by ?s ?p 
	
	public static List<Mapping> refactorToUnion(Mapping m, Var var, List<Mapping> result) {
		
		if(result == null) {
			result = new ArrayList<Mapping>();
		}
		
		
		VarDefinition varDef = m.getVarDefinition();

		Collection<RestrictedExpr> defs = varDef.getDefinitions(var);
			
		if(defs.size() > 1) {
			for(RestrictedExpr restExpr : defs) {
				
				Multimap<Var, RestrictedExpr> map = HashMultimap.create(varDef.getMap());
				map.removeAll(var);
				map.put(var, restExpr);
				
				VarDefinition newVd = new VarDefinition(map);
				Mapping newM = new Mapping(newVd, m.getSqlOp());
				
				result.add(newM);
			}
		} else {
			result.add(m);
		}

		return result;
	}
	*/

	
	
	/**
	 * 
	 * 
	 * 
	 * 
	 * 
	 * @param ms
	 * @param groupExprs
	 * @return
	 */
	public static ListMultimap<String, Mapping> groupBy(List<Mapping> ms, List<Expr> groupExprs) {
		
	
		return null;
	}
	
	
	/**
	 * 
	 * 
	 * @param ms
	 * @param groupExpr
	 * @return A list of MappingUnion that can be further grouped.
	 */
	public static ListMultimap<String, Mapping> groupBy(List<Mapping> ms, Expr groupExpr) {
		
		return null;
	}


	/*
	public static Mapping groupBy(Mapping m, VarExprList groupVars, List<ExprAggregator> aggregators, SqlTranslator sqlTranslator, TypeSystem typeSystem, ExprDatatypeNorm exprNormalizer) {
		

		Mapping a = m;
		for(Var var : groupVars.getVars()) {
			Expr expr = groupVars.getExpr(var);
			if(expr == null) {
				expr = new ExprVar(var);
			}
			
			groupBy(a, var, expr, sqlTranslator, exprNormalizer, typeSystem);
			
		}
		
		return null;
	}
	*/
	
	

	
	// For each rdfTerm signature cluster, cluster the contexts by their datatype
	

	
	// If there are multiple rewrites of the group expression, the question is whether we have to
	// duplicate the relation or whether these rewrites have compatible types so that we can
	// coalesce them
	
	// type groups are characterized by a set of super types
	// Any sub type of Int will be placed in the group for 'Int'
	
	public static String createClusterKey(List<SqlExprContext> contexts, ExprDatatypeNorm exprNormalizer)
	{

		
		String result = "";
		for(SqlExprContext context : contexts) {
			
			E_RdfTerm e = context.getRewrite().asConstRdfTerm();
			//context.getRewrite().getProjection().

			//Map<String, TypeToken> typeMap = context.getRewrite().getProjection().getTypeMap();
			// TODO We should group types such as int, byte, short, etc together
			//TypeToken groupType = getTypeGroup(context.getSqlExpr().getDatatype(),);
			
			//Expr valueType = exprNormalizer.normalize(e.getLexicalValue(), typeMap);
			//result += "" + e.getType() + valueType + e.getDatatype();
			result += "| " + e.getType() + e.getDatatype();
		}
		
		return result;
	}
	
	
	public static TypeToken getTypeGroup(TypeToken type, TypeSystem typeSystem) {
		Set<TypeToken> typeGroupTypes = new HashSet<TypeToken>(Arrays.asList(TypeToken.Int));

		TypeToken result = null;
		
		// Check if the type is compatible with any of the groups
		for(TypeToken superType : typeGroupTypes) {
			
			
			boolean belongsToGroup = typeSystem.isSuperClassOf(type, superType);
			if(belongsToGroup) {
				if(result != null) {
					throw new RuntimeException("Type " + type + " already reassigned to group " + superType + " although already a member of " + result);
				}
				
				result = superType;
			}
		}
		
		if(result == null) {
			result = type;
		}
		
		return result;
	}
	
	
	
	public static <T, U extends Collection<T>> List<List<T>> transpose(Collection<U> items) {
		List<List<T>> result = new ArrayList<List<T>>();

		// Compute maximum length of a row
		for(U c : items) {
			//n = Math.max(n, c.size());
			List<T> tmp = new ArrayList<T>(c.size());
			result.add(tmp);
		}

//		for(int i = 0; i < n; ++i) {
//			List<T> tmp = new ArrayList<T>();
//			result.add(null);
//		}
		
		int y = 0;
		for(U c : items) {
			int x = 0;
			
			List<T> col = result.get(x);
			for(T t : c) {
				
				col.set(y, t);

				++x;
			}			
			++y;
		}
		
		return result;
	}		

	
//	ExprBindingSubstitutor subst = new ExprBindingSubstitutorImpl();
//	e1 = subst.substitute(sparqlExpr, binding);
	//Map<List<SqlExprContext>> varContexts = new ArrayList<List<>>

	class RdfTermTypeGroup {
		private String groupKey;

		// Map Cluster
		private Map<TypeToken, SqlTypeGroup> groupTypeToMembers;
	}
	
	class SqlTypeGroup  {
		RdfTermTypeGroup parent;

		TypeToken groupType;
		
		private List<SqlExprContext> contexts;
	}
	
	
	
	/**
	 * 
	 * 
	 * @param ms
	 * @param expr
	 * @return
	 */
	public static MappingUnion groupBy(Mapping m, VarExprList groupVars, List<ExprAggregator> aggregators, SqlTranslator sqlTranslator, TypeSystem typeSystem, ExprDatatypeNorm exprNormalizer) {
		MappingUnion result;

		
		if(groupVars.isEmpty()) {
			
			Generator generator = Generator.create("X");
			Mapping mapping = applyAggregators(m, aggregators, generator, typeSystem, sqlTranslator);
			result = new MappingUnion();
			result.add(mapping);
			
		} else {
			//public static MappingUnion groupBy(Mapping m, VarExprList groupVars, List<ExprAggregator> aggregators, SqlTranslator sqlTranslator, TypeSystem typeSystem, ExprDatatypeNorm exprNormalizer) {

			result = groupByWithExprs(m, groupVars, aggregators, sqlTranslator, typeSystem, exprNormalizer);
		}
		
		return result;
	}


	public static MappingUnion groupByWithExprs(Mapping m, VarExprList groupVars, List<ExprAggregator> aggregators, SqlTranslator sqlTranslator, TypeSystem typeSystem, ExprDatatypeNorm exprNormalizer) {
		MappingUnion result = new MappingUnion();

		List<Var> vars = groupVars.getVars();
		
		/*
		 * Rewrite all involved group by expressions to SQL
		 */
		List<List<SqlExprContext>> contextsLists = new ArrayList<List<SqlExprContext>>();//ArrayListMultimap.create();
		for(Var var : vars) {
			Expr expr = groupVars.getExpr(var);
			if(expr == null) {
				expr = new ExprVar(var);
			}

			List<SqlExprContext> contexts = MappingOpsImpl.createExprSqlRewrites(expr, m, sqlTranslator);

			contextsLists.add(contexts);
		}

		
		/*
		 * Group the SQL rewritten expressions by their RDF term signature
		 * i.e. uri(...), plainLiteral(...), typedLiteral(..., some:type)
		 */
		Multimap<String, List<SqlExprContext>> sigClusters = ArrayListMultimap.create();

		CartesianProduct<SqlExprContext> cart = CartesianProduct.create(contextsLists);
		for(List<SqlExprContext> c : cart) {
			
			// The cartesian product is just a view - but we want copies!
			// TODO This is dangerous, make the view behaviour require a specific flag
			c = new ArrayList<SqlExprContext>(c);
			
			String clusterKey = createClusterKey(c, exprNormalizer);
			sigClusters.put(clusterKey, c);
		}
		

		Set<String> columnNameBlacklist = new HashSet<String>(m.getSqlOp().getSchema().getColumnNames()); //MappingOpsImpl.getReferencedColumnNames(mappings);
		Generator<String> aliasGenUnion = GeneratorBlacklist.create("X", columnNameBlacklist);
		ExprCommonFactor factorizer = new ExprCommonFactor(aliasGenUnion);


		
		
		
		/*
		 * Within each of the groups, we group again by the
		 * involved SQL types
		 * 
		 * Types within a group can be coalesced
		 * e.g. int, byte, short belong to the group int 
		 */
		Map<String, Multimap<String, List<SqlExprContext>>> groups = new HashMap<String, Multimap<String, List<SqlExprContext>>>();
		for(Entry<String, Collection<List<SqlExprContext>>> sigEntry : sigClusters.asMap().entrySet()) {
		
			String sigKey = sigEntry.getKey();
			Collection<List<SqlExprContext>> sigCluster = sigEntry.getValue();
			
			Multimap<String, List<SqlExprContext>> typeGroup = ArrayListMultimap.create();
			groups.put(sigKey, typeGroup);
			
			for(List<SqlExprContext> rewriteEntry : sigCluster) {
				
				String typeKey = "";
				for(SqlExprContext context : rewriteEntry) {
				
					TypeToken type = context.getSqlExpr().getDatatype(); 
					TypeToken groupType = getTypeGroup(type, typeSystem);
					
					typeKey += "| " + groupType;
				}
				
				typeGroup.put(typeKey, rewriteEntry);
			}
		}

		/*
		 * Process the groups.
		 * 
		 */
		for(Multimap<String, List<SqlExprContext>> sigEntry : groups.values()) {
			Collection<List<SqlExprContext>> typeGroup = sigEntry.values();

			// Within a type group, vertically group the expressions
			List<E_RdfTerm> grouped = new ArrayList<E_RdfTerm>();
			for(int i = 0; i < vars.size(); ++i) {
				grouped.add(null);
			}
			
			// Coalesce all the sql exprs
//			List<List<Projection>> sqlExprss = new  ArrayList<List<Projection>>();
//			for(int i = 0; i < vars.size(); ++i) {
//				sqlExprss.add(new ArrayList<Projection>());
//			}

			
			// Vertically iterate each expression
			// First pass: create the E_RdfTerm object
			for(List<SqlExprContext> rewriteEntry : typeGroup) {
				for(int i = 0; i < vars.size(); ++i) {
					SqlExprContext rewrite = rewriteEntry.get(i);
					
					E_RdfTerm gTerm = grouped.get(i);
					E_RdfTerm rdfTerm = rewrite.getRewrite().asConstRdfTerm();
					
					E_RdfTerm tmp;
					if(gTerm == null) {
						tmp = rdfTerm;
					} else {
						tmp = (E_RdfTerm)factorizer.transformMM(gTerm, rdfTerm);
					}
					grouped.set(i, tmp);
				}
			}

//			Table<Integer, Integer, Integer> x;
//			x.r
			// Second pass: Map the Sql expressions
			
			//rowToColsToProj
			// colToRowsToProjs
			List<List<Projection>> sqlExprss = new  ArrayList<List<Projection>>(vars.size());
			for(int i = 0; i < vars.size(); ++i) {
				E_RdfTerm gTerm = grouped.get(i);

				List<Projection> sqlExprs = new ArrayList<Projection>(typeGroup.size());

				for(List<SqlExprContext> rewriteEntry : typeGroup) {
					SqlExprContext rewrite = rewriteEntry.get(i);
					//List<Projection> sqlExprs = sqlExprss.get(i);
					sqlExprss.add(sqlExprs);
				

					
					Projection proj = new Projection();
					List<Expr> args = gTerm.getArgs();
					for(int j = 0; j < args.size(); ++j) {
						Expr arg = args.get(j);
						
						if(arg.isVariable()) {
							Var var = arg.asVar();
							
							ExprSqlRewrite r = rewrite.getRewrite();
							E_RdfTerm t = r.getRdfTermExpr();
							Expr e = t.getArg(j + 1);
							Var v = e.asVar();

							SqlExpr sqlExpr = r.getProjection().getNameToExpr().get(v.getVarName());
							

							proj.put(var.getName(), sqlExpr);
						} else if(arg.isConstant()) {
							// Nothing to do
						} else {
							throw new RuntimeException("Should not happen");
						}
						
						
					}
					sqlExprs.add(proj);					
//					List<Expr> args = tmp.getArgs();
//					for(int j = 0; j gTerm.getArgs())
//					sqlExprss.get(i).add(rewrite.getSqlExpr());
//
//					
//					
//					E_RdfTerm rdfTerm = rewrite.getRewrite().asConstRdfTerm();
				}
			}
			
							
			// TODO We must preserve order in the coalesce expressions:
			// E.g. if we are grouping (?label, ?lang) (?x, ?y), then we
			// need coalesce(?label, ?lang) (?x, ?y) and not e.g. (?y, ?x)
			// so that ?label matches again with ?x
			
			
			Projection proj = new Projection();
			List<String> groupColumnNames = new ArrayList<String>();
			//SqlOpExtend 
			
			//List<SqlExpr> sqlGroup = new ArrayList<SqlExpr>(vars.size());
			Multimap<Var, RestrictedExpr> varDef = HashMultimap.create();
			for(int i = 0; i < vars.size(); ++i) {
				Var var = vars.get(i);
				E_RdfTerm group = grouped.get(i);
				
				List<Projection> sqlExprs = sqlExprss.get(i);
				
				List<Expr> args = group.getArgs();
				List<Expr> newArgs = new ArrayList<Expr>(args.size());
				for(Expr arg : args) {
					Expr newArg;
					if(arg.isConstant()) {
						newArg = arg;
					} else if(arg.isVariable()) {

						String varName = arg.getVarName();
						
						List<SqlExpr> ss = new ArrayList<SqlExpr>();
						for(Projection p : sqlExprs) {
							SqlExpr s = p.getNameToExpr().get(varName);
							ss.add(s);
						}

						SqlExpr sqlCoalesce = S_Coalesce.create(ss);
						//sqlGroup.add(sqlCoalesce);
						String columnName = aliasGenUnion.next();
						groupColumnNames.add(columnName);
						proj.put(columnName, sqlCoalesce);
						
						newArg = new ExprVar(columnName);
						
					} else {
						throw new RuntimeException("Should not happen");
					}
					
					newArgs.add(newArg);
				}
				
				E_RdfTerm groupTerm = new E_RdfTerm(newArgs);				
				varDef.put(var, new RestrictedExpr(groupTerm));
				
				//System.out.println("" + group + " --- " + sqlExprs);
			}

			
			
			
			
			// Get the variables referenced by the aggregators
			Set<Var> varRefs = new HashSet<Var>();
			for(ExprAggregator exprAgg : aggregators) {
				Aggregator agg = exprAgg.getAggregator();
//				Expr expr = agg.getExpr();
//				if(expr == null) {
//					continue;
//				}
//				
//				Set<Var> tmp = expr.getVarsMentioned();
//				varRefs.addAll(tmp);
				for(Expr expr : agg.getExprList()) {
	              Set<Var> tmp = expr.getVarsMentioned();
	              varRefs.addAll(tmp);				    
				}
			}
			
			// Remove the group variables
			varRefs.removeAll(varDef.keySet());
			
			// Add the new variables to the def
			for(Var varRef : varRefs) {
				Collection<RestrictedExpr> tmp = m.getVarDefinition().getDefinitions(varRef);
				varDef.putAll(varRef, tmp);
			}
			
			
			// Deal with the aggregators
			//for()
			SqlOp subOp = m.getSqlOp();

			List<SqlExpr> groupByExprs = new ArrayList<SqlExpr>(groupColumnNames.size());
			for(String columnName : groupColumnNames) {
				SqlExpr sqlExpr = proj.getNameToExpr().get(columnName);
				groupByExprs.add(sqlExpr);
			}
			
			SqlOpGroupBy sqlOp = SqlOpGroupBy.create(subOp, groupByExprs, new ArrayList<SqlAggFunction>());

			
			VarDefinition vd = new VarDefinition(varDef);
			SqlOpExtend sqlOpExtend = SqlOpExtend.create(sqlOp, proj);
			// TODO: Project to only the referenced columns
			
			Mapping mapping = new Mapping(vd, sqlOpExtend);
			
			
			Mapping done = applyAggregators(mapping, aggregators, aliasGenUnion, typeSystem, sqlTranslator);
			
//			
//			Multimap<Var, RestrictedExpr> varDef2 = HashMultimap.create();
//			Projection proj2 = new Projection();
//			for(ExprAggregator exprAgg : aggregators) {
//				Var var = exprAgg.getVar();
//				Aggregator agg = exprAgg.getAggregator();
//				
//				ExprSqlRewrite rewrite = MappingOpsImpl.rewrite(mapping, agg, aliasGenUnion, typeSystem, sqlTranslator);
//				
//				E_RdfTerm rdfTerm = rewrite.getRdfTermExpr();
//				
//				varDef2.put(var, new RestrictedExpr(rdfTerm));
//				proj2.add(rewrite.getProjection());
//			}
//			
//			SqlOpExtend sqlOpExtend2 = SqlOpExtend.create(sqlOpExtend, proj2);
//			VarDefinition vd2 = new VarDefinition(varDef2);
//			Mapping done = new Mapping(vd2, sqlOpExtend2);
			
			
			
//			Map<String, TypeToken> typeMap = u.getSqlOp().getSchema().getTypeMap();
//			List<SqlExpr> columnRefs = new ArrayList<SqlExpr>();
//			for(String columnName : columnNames) {
//				TypeToken type = typeMap.get(columnName);
//				
//				SqlExpr expr = new S_ColumnRef(type, columnName);
//				columnRefs.add(expr);
//			}
//			
//			List<SqlExprAggregator> sqlAggregators = new ArrayList<SqlExprAggregator>();
//			
//			SqlOpGroupBy sqlOpGroupBy = SqlOpGroupBy.create(u.getSqlOp(), columnRefs, sqlAggregators);
//			

			//public ExprSqlRewrite rewrite(Mapping mapping, Aggregator agg, Generator generator) {

			
			
		

			
			// TODO Clone the mapping
			// TODO Project only to the referenced columns
			// TODO Apply aggregators to the mapping
		
			result.add(done);
			//System.out.println("Added group");
		}
	
		return result;
	}		

	public static Mapping applyAggregators(Mapping mapping, List<ExprAggregator> aggregators, Generator generator, TypeSystem typeSystem, SqlTranslator sqlTranslator) {
		
		Multimap<Var, RestrictedExpr> varDef2 = HashMultimap.create();
		varDef2.putAll(mapping.getVarDefinition().getMap());
		
		Projection proj2 = new Projection();
		for(ExprAggregator exprAgg : aggregators) {
			Var var = exprAgg.getVar();
			Aggregator agg = exprAgg.getAggregator();
			
			ExprSqlRewrite rewrite = MappingOpsImpl.rewrite(mapping, agg, generator, typeSystem, sqlTranslator);
			
			E_RdfTerm rdfTerm = rewrite.getRdfTermExpr();
			
			varDef2.put(var, new RestrictedExpr(rdfTerm));
			proj2.add(rewrite.getProjection());
		}
		
		
		
		//SqlOpExtend sqlOpExtend2 = SqlOpExtend.create(sqlOpExtend, proj2);
		SqlOpExtend sqlOpExtend2 = SqlOpExtend.create(mapping.getSqlOp(), proj2);
		VarDefinition vd2 = new VarDefinition(varDef2);
		Mapping result = new Mapping(vd2, sqlOpExtend2);
		return result;
	}
	
//		for(List<SqlExprContext> rewriteEntry : typeGroup) {
//		
//			System.out.println("::: " + rewriteEntry);
//		}
//		System.out.println("---");
		
		/*
		 * 
		 * 
		 */
//		for(Collection<List<SqlExprContext>> cluster : sigClusters.asMap().values()) {
//			
//			List<E_RdfTerm> grouped = new ArrayList<E_RdfTerm>();
//			for(int i = 0; i < vars.size(); ++i) {
//				grouped.add(null);
//			}
//			
//			// Coalesce all the sql exprs
//			List<List<SqlExpr>> sqlExprss = new  ArrayList<List<SqlExpr>>();
//			for(int i = 0; i < vars.size(); ++i) {
//				sqlExprss.add(new ArrayList<SqlExpr>());
//			}
//
//			
//			// Vertically group each expression
//			for(List<SqlExprContext> member : cluster) {
//				for(int i = 0; i < vars.size(); ++i) {
//					SqlExprContext rewrite = member.get(i);
//					
//					E_RdfTerm gTerm = grouped.get(i);
//					E_RdfTerm rdfTerm = rewrite.getRewrite().asConstRdfTerm();
//					
//					E_RdfTerm tmp;
//					if(gTerm == null) {
//						tmp = rdfTerm;
//					} else {						
//						tmp = (E_RdfTerm)factorizer.transformMM(gTerm, rdfTerm);
//					}
//					grouped.set(i, tmp);
//					
//					sqlExprss.get(i).add(rewrite.getSqlExpr());
//				}
//			}
//
//
////			for(int i = 0; i < vars.size(); ++i) {
////				for(List<SqlExprContext> member : cluster) {
////			}
////			
////			
//			// Now we only have 1 grouped expression for each variable
//			for(int i = 0; i < vars.size(); ++i) {
//				Var var = vars.get(i);
//				E_RdfTerm group = grouped.get(i);
//				
//				List<SqlExpr> sqlExprs = sqlExprss.get(i);
//				
//				SqlExpr sqlCoalesce = S_Coalesce.create(sqlExprs);
//				
//				System.out.println(group + " --- " + sqlCoalesce);
//			}
//
//		}
//		
		
//		
//			//List<SqlExprContext> 
//			
//			// For each signature there is now a list of context rewrites		
//			// Cluster the contexts first by the rdfTerm signature
//	
//
//			// For each exprCluster, coalesce the expressions
//			
//			//contextexprCluster.values();
//			
//			List<SqlExpr> sqlExprs = new ArrayList<SqlExpr>(cs.size());
//			for(SqlExprContext context : exprCluster.values()) {
//				sqlExprs.add(context.getSqlExpr());
//
////				sqlCoalesce = S_Coalesce.create(coalesceArgs);			
////				System.out.println(context);
//			}
//			
//			SqlExpr sqlCoalesce = S_Coalesce.create(sqlExprs);
//			System.out.println("COALESCE: " + sqlCoalesce);
//			
//			
//			// Clone the mapping
//			Multimap<Var, RestrictedExpr> newVarDef = HashMultimap.create(m.getVarDefinition().getMap());
//			
//			// Replace the mapping for the grouping variable
//			newVarDef.removeAll(var);
//
//			newVarDef.put(var, new RestrictedExpr(E_RdfTerm.createUri(NodeValue.makeString("test"))));
//			
//			
//			// TODO I think we need to filter the sqlOp to those rows for which the expression applies?
//			Mapping newMapping = new Mapping(new VarDefinition(newVarDef), m.getSqlOp());
//
//			result.add(newMapping);			
//		}
//		
//		
//		System.out.println(result);
//		
//		// Each of the cluster's entries can be coalesced

		
		
		// Important: If we clone the mapping by an expression, we need to make sure we do not duplicate/lose any
		// of the NULL values
		
	

	
	/**
	 * Given a list of mappings, create groups of them with the following properties:
	 * All members of a group must have the same datatypes for their term constructor args
	 * e.g. rdfTerm(int, string, string, string), rdfTerm(int, float, string, string) 
	 * 
	 * 
	 */
	public static ListMultimap<String, Mapping> groupByOld(ExprDatatypeNorm exprNormalizer, List<Mapping> ms, List<Var> vars) {
		
		//Multimap<String, ArgExpr> cluster = HashMultimap.create();
		ListMultimap<String, Mapping> cluster = LinkedListMultimap.create();
		
		for(Mapping m : ms) {

			Map<String, TypeToken> tmpTypeMap = m.getSqlOp().getSchema().getTypeMap();

			List<String> hashes = new ArrayList<String>(vars.size());
			for(Var var : vars) {

				Collection<RestrictedExpr> defs = m.getVarDefinition().getDefinitions(var);
				
				Expr expr;


				if(defs.size() > 1) {
					throw new RuntimeException("Encountered multiple variable definitions during group by. Var: " + var + " vardef: " + m.getVarDefinition()); // + ", Mapping: " );
				}
				else if(defs.isEmpty()) {

					// Example If only name is defined, but we request "GROUP BY name, age"
					// then this is the same as grouping by name only, since age is implicitely NULL

					// TODO Do we have to bind name to NULL?
					
					//expr = 
					//throw new RuntimeException("Cannot group by " + var + " because mapping does not define it. Mapping: " + m);
					expr = null;
				}
				else {
					RestrictedExpr restExpr = defs.iterator().next();
					expr = restExpr.getExpr();					
				}
				
				
				String hash;
				if(expr == null) {
					hash = "null";
				} else {
					Expr datatypeNorm = exprNormalizer.normalize(expr, tmpTypeMap);
					hash = datatypeNorm.toString();
				}
				
				hashes.add(hash);
			}
			
			String key = Joiner.on(",").join(hashes);
			
		
			cluster.put(key, m);
		}

		return cluster;
	}

	
	
	/**
	 * If there are multiple definitions for any of the specified variables,
	 * each definition results in a new mapping.
	 * The result is then a union of these mappings.
	 * 
	 * Note that this should only be used for corner cases where for some reason
	 * the union of mappings got lost, and we have a single mapping with multiple
	 * var definitions.
	 * 
	 * @param m
	 */
	public static List<Mapping> refactorToUnion(Mapping m, List<Var> tmpVars) {
		
				
		//if(true) throw new RuntimeException("test");
		
		
		
		List<Mapping> result = new ArrayList<Mapping>();
		
		VarDefinition varDef = m.getVarDefinition();
		
		List<Var> vars = new ArrayList<Var>(tmpVars.size());
		List<Collection<RestrictedExpr>> c = new ArrayList<Collection<RestrictedExpr>>(tmpVars.size());

		
		if(tmpVars.isEmpty()) {
			result.add(m);
			return result;
		}
		
		for(Var var : tmpVars) {
			
			Collection<RestrictedExpr> defs = varDef.getDefinitions(var);
			if(defs.isEmpty()) {
				continue;
			}
			
			vars.add(var);
			c.add(defs);
		}

		Multimap<Var, RestrictedExpr> baseMap = HashMultimap.create(varDef.getMap());		
		baseMap.keySet().removeAll(vars);

		CartesianProduct<RestrictedExpr> cart = CartesianProduct.create(c);

		
		for(List<RestrictedExpr> item : cart) {
			
	
			Multimap<Var, RestrictedExpr> map = HashMultimap.create(varDef.getMap());
			for(int i = 0; i < vars.size(); ++i) {
				Var var = vars.get(i);
				RestrictedExpr restExpr = item.get(i);
				
				map.put(var, restExpr);
			}
			
			VarDefinition newVd = new VarDefinition(map);
			Mapping newM = new Mapping(newVd, m.getSqlOp());
			
			result.add(newM);
		}
		

		/*
		OpDisjunction result = null;
		return result;
		*/
		return result;
	}
	
	
	
	
//	public static void trySplitUnion(SqlOp sqlOp, Set<String> columnNames) {
//		if(sqlOp instanceof SqlOpUnionN) {
//			splitUnion((SqlOpUnionN)sqlOp, columnNames);
//		}
//	}
//	
//	public static Map<String, SqlExpr> filterNotNull(Map<String, SqlExpr> map) {
//		Map<String, SqlExpr> result = new HashMap<String, SqlExpr>();
//		
//		for(Entry<String, SqlExpr> entry : map.entrySet()) {
//			String name = entry.getKey();
//			SqlExpr sqlExpr = entry.getValue();
//			
//			if(sqlExpr.isConstant()) {
//				Object obj = sqlExpr.asConstant().getValue().getValue();
//				if(obj == null) {
//					continue;
//				}
//			}
//			
//			//sqlExpr instanceof S_Constant.
//			
//			result.put(name, sqlExpr);
//		}
//		
//		return result;
//	}
//	
//	public static void splitUnion(SqlOpUnionN union, Set<String> columnNames) {
//
//		SqlOpSelectBlockCollector collector = new SqlOpSelectBlockCollectorImpl();
//		
//		for(SqlOp member : union.getSubOps()) {
//		
//			SqlOpSelectBlock block = (SqlOpSelectBlock)collector.transform(member);
//			
//			Projection proj = block.getProjection();
//			
//			Map<String, SqlExpr> tmp = proj.getNameToExpr();
//			Map<String, SqlExpr> nameToExpr = filterNotNull(tmp);
//			
//			System.out.println(nameToExpr);
////			SqlOpProject project = (SqlOpProject)member;
////			
////			project.get
//			
//			
//		}
//	}

}
