Sparqlify-master/sparqlify-type-system/src/main/java/org/aksw/sparqlify/type_system/FunctionModelImpl.java
package org.aksw.sparqlify.type_system;

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

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;

/**
 * Return the set of SQL function declarations for a given sparql function id
 * 
 * @author raven
 *
 * @param <T>
 * @param <I>
 */
//interface SparqlSqlFunctionMap<T> {
//	Collection<MethodEntry<T>> getSqlImpls(String id);
//}



/**
 * T is the type
 * I some implementation of a function
 * 
 * @author raven
 *
 * @param <T>
 * @param <I>
 */
public class FunctionModelImpl<T>
	implements FunctionModel<T>
{

	//private static final Logger logger = LoggerFactory.getLogger(FunctionModel.class);
	
	//private Multimap<String, MethodEntry<T>> nameToMethodEntry = ArrayListMultimap.create();
	private Map<String, MethodEntry<T>> idToMethodEntry = new HashMap<String, MethodEntry<T>>();

	private Multimap<String, String> nameToIds = HashMultimap.create();
	
	private DirectSuperTypeProvider<T> typeHierarchyProvider; // = new TypeHierarchyProviderImpl(typeHierarchy);
	
	
	// This is the map for coercion functions
	private Multimap<T, MethodEntry<T>> sourceToTargets = ArrayListMultimap.create();

	
	// This maps symbols to inverse functions
	private Map<String, String> inverses = new HashMap<String, String>(); 
	
	public Map<String, String> getInverses() {
		return inverses;
	}
	
	
	public static <K, V> K getFirstKey(Multimap<K, V> mmap, Object key) {
		K result = null;

		for(Entry<K, V> entry : mmap.entries()) {
			V k = entry.getValue();
			K v = entry.getKey();
			if(key.equals(k)) {
				result = v;
				break;
			}
		}
		
		return result;
		
	}

	@Override
	public String getNameById(String id) {
		// TODO Optimize this lookup!
		String result = getFirstKey(nameToIds, id);
		
		return result;
	}

	
	public Collection<MethodEntry<T>> getMethodEntries() {
		return idToMethodEntry.values();
	}

//	Map<String, Map<String, String>> tags = new HashMap<String, Map<String, String>>();
//	
//	public Map<String, Map<String, String>> getTags() {
//		return tags;
//	}
	
	
	
	public FunctionModelImpl(DirectSuperTypeProvider<T> typeHierarchyProvider) {
		this.typeHierarchyProvider = typeHierarchyProvider;
	}

	@Override
	public Collection<String> getIdsByName(String name) 
	{
		Collection<String> result = nameToIds.get(name);
		return result;
	}

//	public static void main(String[] args) throws IOException {
//		
//		Map<String, String> typeHierarchy = MapReader
//				.readFromResource("/type-hierarchy.default.tsv");
//
//		Map<String, String> typeMap = MapReader
//				.readFromResource("/type-map.h2.tsv");
//
//		// TODO HACK Do not add types programmatically 
//		typeMap.put("INTEGER", "int");
//		
//		typeHierarchy.putAll(typeMap);
//		
//		
//	
//		IBiSetMultimap<TypeToken, TypeToken> h = TypeSystemImpl.createHierarchyMap(typeHierarchy);
//		TypeHierarchyProviderImpl thp = new TypeHierarchyProviderImpl(h);
//
//		FunctionModel<TypeToken> model = new FunctionModelImpl<TypeToken>(thp);
//		
//		model.registerFunction("plus_float", "+", MethodSignature.create(false, TypeToken.Float, TypeToken.Float, TypeToken.Float));
//		//model.registerCoercion("to_int", "to_int", MethodSignature.create(false, TypeToken.Double, TypeToken.Int));
//		model.registerCoercion("to_float", "to_float", MethodSignature.create(false, TypeToken.Float, TypeToken.Int));
//
//		
//		TypeToken Geometry = TypeToken.alloc("geometry");
//		TypeToken Geography = TypeToken.alloc("geography");
//
//		model.registerFunction("st_intersects_geometry", "st_intersects", MethodSignature.create(false, TypeToken.Boolean, Geometry, Geometry));
//		model.registerFunction("st_intersects_geography", "st_intersects", MethodSignature.create(false, TypeToken.Boolean, Geography, Geography));
//
//		{
//			Collection<CandidateMethod<TypeToken>> cands = model.lookupByName("st_intersects", Arrays.asList(Geometry, Geometry));
//			System.out.println("Number of candidates: " + cands.size());
//			System.out.println(cands);
//		}
//
//		{
//			Collection<CandidateMethod<TypeToken>> cands = model.lookupByName("+", Arrays.asList(TypeToken.Int, TypeToken.Int));
//			System.out.println("Number of candidates: " + cands.size());
//			System.out.println(cands);
//		}
//		
//
//		
//	}
	
	
	public void lookupCoercionRec(T argType, T targetType, int depth, Set<CandidateMethod<T>> result) {
		
		
		Collection<MethodEntry<T>> targets = sourceToTargets.get(argType);
		if(targets != null) {
			
			
			boolean found = false;
			for(MethodEntry<T> target : targets) {
				T tt = target.getSignature().getReturnType();
				
				Integer distance = TypeHierarchyUtils.getDistance(targetType, tt, typeHierarchyProvider);
				if(distance != null) {
					
					MethodDistance md = new MethodDistance(distance, depth);
					CandidateMethod<T> candidate = new CandidateMethod<T>(target, null, md);
					
					result.add(candidate);
					found = true;
				}
				
			}
			
			if(found) {
				return;
			}			
		}
		

		Collection<T> superTypes = typeHierarchyProvider.getDirectSuperTypes(argType);
		for(T superType : superTypes) {
			lookupCoercionRec(superType, targetType, depth + 1, result);
		}
		
		
	}
	
	public Set<CandidateMethod<T>> lookupCoercions(T argType, T targetType) {

		Set<CandidateMethod<T>> result = new HashSet<CandidateMethod<T>>();
		

		lookupCoercionRec(argType, targetType, 0, result);

		return result;
	}
	
	public CandidateMethod<T> lookupCoercion(T argType, T targetType) {
		Set<CandidateMethod<T>> tmp = lookupCoercions(argType, targetType);
		
		CandidateMethod<T> result;
		
		if(tmp.size() != 1) {
			result = null;
		} else {
			result = tmp.iterator().next();
		}
		
		return result;
	}
	

	@Deprecated
	public MethodEntry<T> registerFunction(String id, String name, MethodSignature<T> signature) {
		//Collection<MethodEntry<T>> signatures = nameToMethodEntry.get(name);
		
		
		// TODO More thorough checks on the type hierarchy...
//		if(signatures.contains(name)) {
//			throw new RuntimeException("Function " + name + " with signature " + signature + " already registered");
//		}
		
		MethodEntry<T> entry = new MethodEntry<T>(id, MethodDeclaration.create(name, signature));

		idToMethodEntry.put(id,  entry);
		
		//signatures.add(entry);
		
		nameToIds.put(name, id);
		
		return entry;
	}
	
	
	@Deprecated
	public void registerCoercion(String id, String name, MethodSignature<T> signature) {
		List<T> paramTypes = signature.getParameterTypes();
		if(paramTypes.size() != 1) {
			throw new RuntimeException("Coercions must only have 1 paramater");
		}
		
		T sourceType = paramTypes.get(0);
		Collection<MethodEntry<T>> targets = sourceToTargets.get(sourceType);

		MethodEntry<T> entry = new MethodEntry<T>(id, MethodDeclaration.create(name, signature));
		
		targets.add(entry);
		
		idToMethodEntry.put(id, entry);
	}
	
	
	public MethodEntry<T> lookupById(String id) {
		MethodEntry<T> result = idToMethodEntry.get(id);
		return result;
	}
	

	//public Collection<CandidateMethod<T>> lookupByName(String functionName, T ... argType) {
	
	public Collection<MethodEntry<T>> lookupByName(String name) {
		List<MethodEntry<T>> result = new ArrayList<MethodEntry<T>>();
		Collection<String> ids = nameToIds.get(name);
		
		for(String id : ids) {
			MethodEntry<T> method = idToMethodEntry.get(id);
			
			if(method != null) {
				result.add(method);
			}
		}
		
		return result;
	}
	
	public Collection<CandidateMethod<T>> lookupByName(String functionName, List<T> argTypes) {

		
		Collection<MethodEntry<T>> signatures = lookupByName(functionName);

		
		Collection<CandidateMethod<T>> result = lookup(signatures, argTypes);
		
		return result;
	}
	
	public Collection<CandidateMethod<T>> lookup(Collection<MethodEntry<T>> candidates, List<T> argTypes) {
		
		
		// Check if there is an appropriate signature registered
		List<CandidateMethod<T>> result = new ArrayList<CandidateMethod<T>>(); 

		for(MethodEntry<T> candidate : candidates) {	
			 
			MethodSignature<T> signature = candidate.getSignature();
			
			List<T> paramTypes = signature.getParameterTypes(); 
			
			if(paramTypes.size() > argTypes.size()) {
				continue; // Not enough arguments provided
			}
			
			
			if(!signature.isVararg() && paramTypes.size() < argTypes.size()) {
				continue; // Too many arguments provided
			}
			
			int n = argTypes.size(); //Math.min(argTypes.size(), signature.getParameterTypes().size());
			int m = paramTypes.size();
			
			boolean isCandidate = true;
			
			List<ParamDistance> distances = new ArrayList<ParamDistance>(argTypes.size());
			List<CandidateMethod<T>> coercions = new ArrayList<CandidateMethod<T>>();
			
			for(int i = 0; i < n ; ++i) {
				T paramType;
				if(i < m) {
					paramType = paramTypes.get(i);
				} else {
					paramType = signature.getVarArgType();
				}
				
				T argType = argTypes.get(i);
				
				
				boolean usesCoercion = false;
				Integer distance = TypeHierarchyUtils.getDistance(argType, paramType, typeHierarchyProvider);

				
				// Try with coercion instead
				CandidateMethod<T> coercion = null;
				if(distance == null) {
					coercion = lookupCoercion(argType, paramType);
					
					if(coercion == null) {
						isCandidate = false;
					} else {
						distance = coercion.getDistance().getArgTypeDistances().get(0).getDistance();
						usesCoercion = true;
					}
				}

				coercions.add(coercion);
				
				ParamDistance dist = new ParamDistance(distance, usesCoercion);
				distances.add(dist);
				
				if(!isCandidate) {
					break;
				}
			}


			
			if(isCandidate) {

				MethodDistance distance = new MethodDistance(new ParamDistance(0, false), distances);
				CandidateMethod<T> tmp = new CandidateMethod<T>(candidate, coercions, distance);

				tryInsertCandidate(result, tmp);
				//result.add(tmp);
			}
		}
		
		return result;
	}


	/**
	 * Inserts a method candidate if it is not subsumed by another one (based on the distance)
	 * 
	 * @param result
	 * @param candidate
	 */
	public static <T> void tryInsertCandidate(List<CandidateMethod<T>> result, CandidateMethod<T> candidate) {
		
		MethodDistance a = candidate.getDistance();
		
		Iterator<CandidateMethod<T>> it = result.iterator();
		boolean isSubsumed = false;
		while(it.hasNext()) {
			CandidateMethod<T> item = it.next();
			MethodDistance b = item.getDistance();
			
			Integer d = a.compare(b);
			if(d != null) {
				if(d < 0) {
					it.remove();
				} else {
					isSubsumed = true;
				}
			}
		}
		
		if(!isSubsumed) {
			result.add(candidate);
		}
	}

	@Override
	public MethodEntry<T> registerFunction(MethodDeclaration<T> declaration) {
		MethodEntry<T> result = this.registerFunction(declaration.toString(), declaration.getName(), declaration.getSignature());
		return result;
	}

	@Override
	public void registerCoercion(MethodDeclaration<T> declaration) {
		this.registerCoercion(declaration.toString(), declaration.getName(), declaration.getSignature());
	}
	
	
/*
	public ExprFunction pickCandidate(Collection<RegisteredFunction> candidates, List<Expr> args) {
		switch(candidates.size()) {
		case 0: {
			logger.warn("Returning false; although it should be type-error");
			//return new SqlExprValue(false);
			return null;
		}
		case 1: {
			RegisteredFunction regFn = candidates.iterator().next(); 
			ExprFunction result = new E_SqlFunctionRegistered(regFn, args);
			return result;
			//return new SqlStringTransformerRegisteredFunction(resultFn);
			//return new S_Function(this.sqlFunctionName, args, pair.getKey().getReturnType(), pair.getValue());	
		}
		default: {
			logger.warn("Multiple overloads matched: " + candidates);
			logger.warn("Returning false; although it should be type-error");
			//return new SqlExprValue(false);
			return null;
		}
		}
	}
*/
}
