package de.dhbwstuttgart.typeinference.unify.model;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import com.google.common.collect.Ordering;

//PL 18-02-05/18-04-05 Unifier durch Matcher ersetzt
//muss greater noch ersetzt werden ja erledigt 18--04-05
import de.dhbwstuttgart.typeinference.unify.MartelliMontanariUnify;

import de.dhbwstuttgart.typeinference.unify.Match;
import de.dhbwstuttgart.typeinference.unify.TypeUnifyTask;
import de.dhbwstuttgart.typeinference.unify.interfaces.IFiniteClosure;
import de.dhbwstuttgart.typeinference.unify.interfaces.IUnify;

/**
 * The finite closure for the type unification
 * @author Florian Steurer
 */
public class FiniteClosure //extends Ordering<UnifyType> //entfernt PL 2018-12-11
implements IFiniteClosure  {

	Writer logFile;
	static Boolean log = false;
	public void setLogTrue() {
		log = true;
	}
	/**
	 * A map that maps every type to the node in the inheritance graph that contains that type.
	 */
	private HashMap<UnifyType, Node<UnifyType>> inheritanceGraph;
	
	/**
	 * A map that maps every typename to the nodes of the inheritance graph that contain a type with that name.
	 */
	private HashMap<String, Set<Node<UnifyType>>> strInheritanceGraph;


	/**
	 * The initial pairs of that define the inheritance tree
	 */
	private Set<UnifyPair> pairs;
	
	/**
	 * Hastable fuer die greater-Werte, damit sie nicht doppelt berechnet werden muessen
	 */
	Hashtable<hashKeyType, Set<UnifyType>> greaterHash = new Hashtable<>();
	
	/**
	 * Hastable fuer die smaller-Werte, damit sie nicht doppelt berechnet werden muessen
	 */
	Hashtable<hashKeyType, Set<UnifyType>> smallerHash = new Hashtable<>();
	
	/**
	 * Creates a new instance using the inheritance tree defined in the pairs.
	 */
	public FiniteClosure(Set<UnifyPair> pairs, Writer logFile) {
		this.logFile = logFile;
		this.pairs = new HashSet<>(pairs);
		inheritanceGraph = new HashMap<UnifyType, Node<UnifyType>>();
		
		// Build the transitive closure of the inheritance tree
		for(UnifyPair pair : pairs) {
			if(pair.getPairOp() != PairOperator.SMALLER)
				continue;
			
			//VERSUCH PL 18-02-06
			//koennte ggf. die FC reduzieren
			//TO DO: Ueberpruefen, ob das sinnvll und korrekt ist 
			//if (!pair.getLhsType().getTypeParams().arePlaceholders() 
			//		&& !pair.getRhsType().getTypeParams().arePlaceholders())
			//	continue;
;			
			// Add nodes if not already in the graph
			if(!inheritanceGraph.containsKey(pair.getLhsType()))
				inheritanceGraph.put(pair.getLhsType(), new Node<UnifyType>(pair.getLhsType()));
			if(!inheritanceGraph.containsKey(pair.getRhsType()))
				inheritanceGraph.put(pair.getRhsType(), new Node<UnifyType>(pair.getRhsType()));
			
			Node<UnifyType> childNode = inheritanceGraph.get(pair.getLhsType());
			Node<UnifyType> parentNode = inheritanceGraph.get(pair.getRhsType());
			
			// Add edge
			parentNode.addDescendant(childNode);
			
			// Add edges to build the transitive closure
			parentNode.getPredecessors().stream().forEach(x -> x.addDescendant(childNode));
			childNode.getDescendants().stream().forEach(x -> x.addPredecessor(parentNode));
		}

		// Build the alternative representation with strings as keys
		strInheritanceGraph = new HashMap<>();
		for(UnifyType key : inheritanceGraph.keySet()) {
			if(!strInheritanceGraph.containsKey(key.getName()))
				strInheritanceGraph.put(key.getName(), new HashSet<>());
			
			strInheritanceGraph.get(key.getName()).add(inheritanceGraph.get(key));
		}
	}
	
	void testSmaller() {
		UnifyType tq1, tq2, tq3;
		tq1 = new ExtendsType(PlaceholderType.freshPlaceholder());
		List<UnifyType> l1 = new ArrayList<>();
		List<UnifyType> l2 = new ArrayList<>();
		l1.add(tq1);
		tq2 = new ReferenceType("java.util.Vector", new TypeParams(l1));
		l2.add(tq2);
		tq3 = new ReferenceType("java.util.Vector", new TypeParams(l2));
		Set<UnifyType> smaller = smaller(tq3, new HashSet<>()); 
	}

	/**
	 * Returns all types of the finite closure that are subtypes of the argument.
	 * @return The set of subtypes of the argument.
	 */
	@Override
	public Set<UnifyType> smaller(UnifyType type, Set<UnifyType> fBounded) {
		
		Set<UnifyType> ret;
		if ((ret = smallerHash.get(new hashKeyType(type))) != null) {
			//System.out.println(greaterHash);
			return new HashSet<>(ret);
		}
		
		if(type instanceof FunNType)
			return computeSmallerFunN((FunNType) type, fBounded);
		
		Set<Pair<UnifyType,Set<UnifyType>>> ts = new HashSet<>();
		ts.add(new Pair<>(type, fBounded));
		Set<UnifyType> result = computeSmaller(ts);
		smallerHash.put(new hashKeyType(type), result);
		/*
		try { 
			logFile.write("\ntype: " + type + "\nret: " + ret + "\nresult: " + result);//"smallerHash: " + greaterHash.toString());
			logFile.flush();
		}
		catch (IOException e) {
			System.err.println("no LogFile");
		}*/
		return result;
	}
	
	/**
	 * Computes the smaller functions for every type except FunNTypes.
	 */
	private Set<UnifyType> computeSmaller(Set<Pair<UnifyType,Set<UnifyType>>> types) {
		Set<Pair<UnifyType,Set<UnifyType>>> result = new HashSet<>();
		
		//PL 18-02-05 Unifier durch Matcher ersetzt
		//IUnify unify = new MartelliMontanariUnify();
		Match match = new Match();
		
		for(Pair<UnifyType,Set<UnifyType>> pt : types) {
			UnifyType t = pt.getKey();
			Set<UnifyType> fBounded = pt.getValue().get();
			
			// if T = T' then T <* T'
			try {
				result.add(new Pair<>(t, fBounded));
			}
			catch (StackOverflowError e) {
				System.out.println("");
			}
			
			// if C<...> <* C<...> then ... (third case in definition of <*)
			if(t.getTypeParams().size() > 0) {
				ArrayList<Set<UnifyType>> paramCandidates = new ArrayList<>();
				for (int i = 0; i < t.getTypeParams().size(); i++)
					paramCandidates.add(smArg(t.getTypeParams().get(i), fBounded));
				permuteParams(paramCandidates).forEach(x -> result.add(new Pair<>(t.setTypeParams(x), fBounded)));
			}
			
			if(!strInheritanceGraph.containsKey(t.getName()))
				continue;
			
			// if T <* T' then sigma(T) <* sigma(T')
			Set<Node<UnifyType>> candidates = strInheritanceGraph.get(t.getName()); //cadidates= [???Node(java.util.Vector<java.util.Vector<java.lang.Integer>>)???
			                                                                       // , Node(java.util.Vector<gen_hv>)
			                                                                         //]
			for(Node<UnifyType> candidate :  candidates) {
				UnifyType theta2 = candidate.getContent();
				//PL 18-02-05 Unifier durch Matcher ersetzt ANFANG
				ArrayList<UnifyPair> termList= new ArrayList<UnifyPair>();
				termList.add(new UnifyPair(theta2,t, PairOperator.EQUALSDOT));
				Optional<Unifier> optSigma = match.match(termList);
				//PL 18-02-05 Unifier durch Matcher ersetzt ENDE
				if(!optSigma.isPresent())
					continue;
				
				Unifier sigma = optSigma.get();
				sigma.swapPlaceholderSubstitutions(t.getTypeParams());
				
				Set<UnifyType> theta1Set = candidate.getContentOfDescendants();
				
				for(UnifyType theta1 : theta1Set)
					result.add(new Pair<>(theta1.apply(sigma), fBounded));
			}
		}
		
		HashSet<UnifyType> resut = result.stream().map(x -> x.getKey()).collect(Collectors.toCollection(HashSet::new));
		if(resut.equals(types.stream().map(x -> x.getKey()).collect(Collectors.toCollection(HashSet::new))))
			return resut;
		return computeSmaller(result);
	}
	
	/**
	 * Computes the smaller-Function for FunNTypes.
	 */
	private Set<UnifyType> computeSmallerFunN(FunNType type, Set<UnifyType> fBounded) {
		Set<UnifyType> result = new HashSet<>();
		
		// if T = T' then T <=* T'
	    result.add(type); 
	    
	    // Because real function types are implicitly variant
	    // it is enough to permute the params with the values of greater / smaller.
		ArrayList<Set<UnifyType>> paramCandidates = new ArrayList<>();
		paramCandidates.add(smaller(type.getTypeParams().get(0), fBounded));
		for (int i = 1; i < type.getTypeParams().size(); i++)
			paramCandidates.add(greater(type.getTypeParams().get(i), new HashSet<>()));
	
		permuteParams(paramCandidates).forEach(x -> result.add(type.setTypeParams(x)));
		return result;
	}
	
	/**
	 * Returns all types of the finite closure that are supertypes of the argument.
	 * @return The set of supertypes of the argument.
	 */
	@Override
	//Eingefuegt PL 2018-05-24 F-Bounded Problematik
	public Set<UnifyType> greater(UnifyType type, Set<UnifyType> fBounded) {
		
		Set<UnifyType> ret;
		if ((ret = greaterHash.get(new hashKeyType(type))) != null) {
			//System.out.println(greaterHash);
			return new HashSet<>(ret);
		}
	
		
		if(type instanceof FunNType) {
			return computeGreaterFunN((FunNType) type, fBounded); 
		}
		
		Set<UnifyType> result = new HashSet<>();
		Set<Pair<UnifyType,Set<UnifyType>>> PairResultFBounded = new HashSet<>();
		
		Match match = new Match();
		
		
		// if T = T' then T <=* T'
		result.add(type);
		if(!strInheritanceGraph.containsKey(type.getName()))
			return result;
		
		// if T <* T' then sigma(T) <* sigma(T')
		Set<Node<UnifyType>> candidates = strInheritanceGraph.get(type.getName());
		
		/*
		try { 
			if (log) logFile.write(candidates.toString());
			//log = false;
		}
		catch (IOException e) {
			System.err.println("no LogFile");
		}
		*/
		
		for(Node<UnifyType> candidate :  candidates) {
			UnifyType theta1 = candidate.getContent();
			
			//PL 18-04-05 Unifier durch Matcher ersetzt ANFANG
			ArrayList<UnifyPair> termList= new ArrayList<UnifyPair>();
			termList.add(new UnifyPair(theta1,type, PairOperator.EQUALSDOT));
			Optional<Unifier> optSigma = match.match(termList);
			//PL 18-04-05 Unifier durch Matcher ersetzt ENDE
			if(!optSigma.isPresent()) {
				continue;
			}
			Unifier sigma = optSigma.get();
			sigma.swapPlaceholderSubstitutionsReverse(theta1.getTypeParams());
			
			Set<UnifyType> fBoundedNew = new HashSet<>(fBounded);
			fBoundedNew.add(theta1);
			Set<UnifyType> theta2Set = candidate.getContentOfPredecessors();
			//System.out.println("");
			for(UnifyType theta2 : theta2Set) {
				result.add(theta2.apply(sigma));
				PairResultFBounded.add(new Pair<>(theta2.apply(sigma), fBoundedNew));
			}
		}
		/*
		try { 
			if (log) logFile.write(PairResultFBounded.toString());
			log = false;
		}
		catch (IOException e) {
			System.err.println("no LogFile");
		}
		*/
		for(Pair<UnifyType,Set<UnifyType>> pt : PairResultFBounded) {
			UnifyType t = pt.getKey();
			Set<UnifyType> lfBounded = pt.getValue().get();
			
			// if C<...> <* C<...> then ... (third case in definition of <*)	
			//TypeParams typeparams = t.getTypeParams();
			if(t.getTypeParams().size() > 0) {
				ArrayList<Set<UnifyType>> paramCandidates = new ArrayList<>();
				
				for (int i = 0; i < t.getTypeParams().size(); i++) {
					//UnifyType parai = t.getTypeParams().get(i);
					int i_ef = i;
					BiFunction<Boolean,UnifyType,Boolean> f = (x,y) -> 
					{
						ArrayList<UnifyPair> termList = new ArrayList<UnifyPair>();
					    termList.add(new UnifyPair(y,t.getTypeParams().get(i_ef), PairOperator.EQUALSDOT));
					    return ((match.match(termList).isPresent()) || x);
					};
					//if (parai.getName().equals("java.lang.Integer")) {
					//	System.out.println("");
					//}
					BinaryOperator<Boolean> bo = (a,b) -> (a || b);
					if (lfBounded.stream().reduce(false,f,bo)) {
						//F-Bounded Endlosrekursion
						HashSet<UnifyType> res = new HashSet<UnifyType>();
						paramCandidates.add(res); 
					}
					else {
						paramCandidates.add(grArg(t.getTypeParams().get(i), new HashSet<>(fBounded) ));
					}
				}
				permuteParams(paramCandidates).forEach(x -> result.add(t.setTypeParams(x)));
				//System.out.println("");
			}
		}
		
		greaterHash.put(new hashKeyType(type), result);
		/*
		try { 
			logFile.write("\ntype: " + type + "\nret: " + ret + "\nresult: " + result);//"greaterHash: " + greaterHash.toString());
			logFile.flush();
		}
		catch (IOException e) {
			System.err.println("no LogFile");
		}*/
		return result;
	}
	
	/* auskommentiert PL 2018-05-24
	/**
	 * Returns all types of the finite closure that are supertypes of the argument.
	 * @return The set of supertypes of the argument.
	 *
	//@Override
	public Set<UnifyType> oldgreater(UnifyType type, Set<UnifyType> fBounded) {
		if(type instanceof FunNType)
			return computeGreaterFunN((FunNType) type, fBounded); 
		
		Set<Pair<UnifyType,Set<UnifyType>>> ts = new HashSet<>();
		ts.add(new Pair<>(type, fBounded));
		return computeGreater(ts);
	}
 
	/**
	 * Computes the greater function for all types except function types.
	 *
	protected Set<UnifyType> computeGreater(Set<Pair<UnifyType,Set<UnifyType>>> types) {
		Set<Pair<UnifyType,Set<UnifyType>>> result = new HashSet<>();
		
		//PL 18-04-05 Unifier durch Matcher ersetzt
		//IUnify unify = new MartelliMontanariUnify();
		Match match = new Match();
		
		for(Pair<UnifyType,Set<UnifyType>> pt : types) {
			UnifyType t = pt.getKey();
			Set<UnifyType> fBounded = pt.getValue().get();
			// if T = T' then T <=* T'
			result.add(pt);
			
			// if C<...> <* C<...> then ... (third case in definition of <*)			
			if(t.getTypeParams().size() > 0) {
				ArrayList<Set<UnifyType>> paramCandidates = new ArrayList<>();
				for (int i = 0; i < t.getTypeParams().size(); i++) {
					UnifyType parai = t.getTypeParams().get(i);
					int i_ef = i;
					BiFunction<Boolean,UnifyType,Boolean> f = (x,y) -> 
					{
						ArrayList<UnifyPair> termList = new ArrayList<UnifyPair>();
					    termList.add(new UnifyPair(y,t.getTypeParams().get(i_ef), PairOperator.EQUALSDOT));
					    return ((match.match(termList).isPresent()) || x);
					};
					if (parai.getName().equals("java.lang.Integer")) {
						System.out.println("");
					}
					BinaryOperator<Boolean> bo = (a,b) -> (a || b);
					if (fBounded.stream().reduce(false,f,bo)) continue; //F-Bounded Endlosrekursion
					paramCandidates.add(grArg(t.getTypeParams().get(i), new HashSet<>(fBounded) ));
				}
				permuteParams(paramCandidates).forEach(x -> result.add(new Pair<>(t.setTypeParams(x), new HashSet<>(fBounded))));
			}
			
			if(!strInheritanceGraph.containsKey(t.getName()))
				continue;
			
			// if T <* T' then sigma(T) <* sigma(T')
			Set<Node<UnifyType>> candidates = strInheritanceGraph.get(t.getName());
			for(Node<UnifyType> candidate :  candidates) {
				UnifyType theta1 = candidate.getContent();
				
				//PL 18-04-05 Unifier durch Matcher ersetzt ANFANG
				ArrayList<UnifyPair> termList= new ArrayList<UnifyPair>();
				termList.add(new UnifyPair(theta1,t, PairOperator.EQUALSDOT));
				Optional<Unifier> optSigma = match.match(termList);
				//PL 18-04-05 Unifier durch Matcher ersetzt ENDE
				if(!optSigma.isPresent())
					continue;
				
				Unifier sigma = optSigma.get();
				sigma.swapPlaceholderSubstitutionsReverse(theta1.getTypeParams());
				
				Set<UnifyType> fBoundedNew = new HashSet<>(fBounded);
				fBoundedNew.add(theta1);
				Set<UnifyType> theta2Set = candidate.getContentOfPredecessors();
				
				for(UnifyType theta2 : theta2Set)
					result.add(new Pair<>(theta2.apply(sigma), fBoundedNew));
			}
			
		}
		
		HashSet<UnifyType> resut = result.stream().map(x -> x.getKey()).collect(Collectors.toCollection(HashSet::new));
		System.out.println(resut);
		if(resut.equals(types.stream().map(x -> x.getKey()).collect(Collectors.toCollection(HashSet::new))))
			return resut;
		return computeGreater(result);
	}
	*/

	/**
	 * Computes the greater function for FunN-Types
	 */
	protected Set<UnifyType> computeGreaterFunN(FunNType type, Set<UnifyType> fBounded) {
		Set<UnifyType> result = new HashSet<>();
		
		// if T = T' then T <=* T'
	    result.add(type); 
	    
	    // Because real function types are implicitly variant
	    // it is enough to permute the params with the values of greater / smaller.
		ArrayList<Set<UnifyType>> paramCandidates = new ArrayList<>();
		paramCandidates.add(greater(type.getTypeParams().get(0), new HashSet<>()));
		for (int i = 1; i < type.getTypeParams().size(); i++)
				paramCandidates.add(smaller(type.getTypeParams().get(i), fBounded));
		permuteParams(paramCandidates).forEach(x -> result.add(type.setTypeParams(x)));
		return result;
	}

	
	@Override	
	public Set<UnifyType> grArg(UnifyType type, Set<UnifyType> fBounded) {
		return type.grArg(this, fBounded);
	}

	@Override
	public Set<UnifyType> grArg(ReferenceType type, Set<UnifyType> fBounded) {
		Set<UnifyType> result = new HashSet<UnifyType>();
		result.add(type);	
        smaller(type, fBounded).forEach(x -> result.add(new SuperType(x)));
        greater(type,fBounded).forEach(x -> result.add(new ExtendsType(x)));
		return result;
	}
	
	@Override
	public Set<UnifyType> grArg(FunNType type, Set<UnifyType> fBounded) {
		Set<UnifyType> result = new HashSet<UnifyType>();
		result.add(type);	
        smaller(type, fBounded).forEach(x -> result.add(new SuperType(x)));
        greater(type, fBounded).forEach(x -> result.add(new ExtendsType(x)));
		return result;
	}
	
	@Override
	public Set<UnifyType> grArg(ExtendsType type, Set<UnifyType> fBounded) {
		Set<UnifyType> result = new HashSet<UnifyType>();
		result.add(type);
		UnifyType t = type.getExtendedType();
		greater(t, fBounded).forEach(x -> result.add(new ExtendsType(x)));
		return result;
	}

	@Override
	public Set<UnifyType> grArg(SuperType type, Set<UnifyType> fBounded) {
		Set<UnifyType> result = new HashSet<UnifyType>();
		result.add(type);
		UnifyType t = type.getSuperedType();
		smaller(t, fBounded).forEach(x -> result.add(new SuperType(x)));
		return result;
	}
	
	@Override
	public Set<UnifyType> grArg(PlaceholderType type, Set<UnifyType> fBounded) {
		HashSet<UnifyType> result = new HashSet<>();
		result.add(type);
		return result;
	}

	@Override	
	public Set<UnifyType> smArg(UnifyType type, Set<UnifyType> fBounded) {
		return type.smArg(this, fBounded);
	}
	
	@Override
	public Set<UnifyType> smArg(ReferenceType type, Set<UnifyType> fBounded) {
		Set<UnifyType> result = new HashSet<UnifyType>();
		result.add(type);	
		return result;
	}
	
	@Override
	public Set<UnifyType> smArg(FunNType type, Set<UnifyType> fBounded) {
		Set<UnifyType> result = new HashSet<UnifyType>();
		result.add(type);	
		return result;
	}
	
	@Override
	public Set<UnifyType> smArg(ExtendsType type, Set<UnifyType> fBounded) {
		Set<UnifyType> result = new HashSet<UnifyType>();
		result.add(type);
		UnifyType t = type.getExtendedType();
		result.add(t);	
        smaller(t, fBounded).forEach(x -> { 
        	result.add(new ExtendsType(x));
        	result.add(x); 
        });
		return result;
	}
	

	@Override
	public Set<UnifyType> smArg(SuperType type, Set<UnifyType> fBounded) {
		Set<UnifyType> result = new HashSet<UnifyType>();
		result.add(type);
		UnifyType t = type.getSuperedType();
		result.add(t);	
//*** ACHTUNG das koennte FALSCH sein PL 2018-05-23 evtl. HashSet durch smArg durchschleifen	
        greater(t, fBounded).forEach(x -> { 
        	result.add(new SuperType(x));
        	result.add(x); 
        });
		return result;
	}
	
	@Override
	public Set<UnifyType> smArg(PlaceholderType type, Set<UnifyType> fBounded) {
		HashSet<UnifyType> result = new HashSet<>();
		result.add(type);
		return result;
	}
	
	@Override
	public Set<UnifyType> getAllTypesByName(String typeName) {
		if(!strInheritanceGraph.containsKey(typeName))
			return new HashSet<>();
		return strInheritanceGraph.get(typeName).stream().map(x -> x.getContent()).collect(Collectors.toCollection(HashSet::new));
	}
	
	@Override
	public Optional<UnifyType> getLeftHandedType(String typeName) {
		if(!strInheritanceGraph.containsKey(typeName))
			return Optional.empty();
		
		for(UnifyPair pair : pairs)
			if(pair.getLhsType().getName().equals(typeName) && pair.getLhsType().typeParams.arePlaceholders())
					return Optional.of(pair.getLhsType());
		
		return Optional.empty();
	}
	
	@Override
	public Set<UnifyType> getAncestors(UnifyType t) {
		if(!inheritanceGraph.containsKey(t))
			return new HashSet<>();
		Set<UnifyType> result = inheritanceGraph.get(t).getContentOfPredecessors();
		result.add(t);
		return result;
	}
	
	@Override
	public Set<UnifyType> getChildren(UnifyType t) {
		if(!inheritanceGraph.containsKey(t))
			return new HashSet<>();
		Set<UnifyType> result = inheritanceGraph.get(t).getContentOfDescendants();
		result.add(t);
		return result;
	}
	
	/**
	 * Takes a set of candidates for each position and computes all possible permutations.
	 * @param candidates The length of the list determines the number of type params. Each set
	 * contains the candidates for the corresponding position.
	 */
	protected Set<TypeParams> permuteParams(ArrayList<Set<UnifyType>> candidates) {
		Set<TypeParams> result = new HashSet<>();
		permuteParams(candidates, 0, result, new UnifyType[candidates.size()]);
		return result;
	}
	
	/**
	 * Takes a set of candidates for each position and computes all possible permutations.
	 * @param candidates The length of the list determines the number of type params. Each set
	 * contains the candidates for the corresponding position.
	 * @param idx Idx for the current permutatiton.
	 * @param result Set of all permutations found so far
	 * @param current The permutation of type params that is currently explored
	 */
	protected void permuteParams(ArrayList<Set<UnifyType>> candidates, int idx, Set<TypeParams> result, UnifyType[] current) {
		if(candidates.size() == idx) {
			result.add(new TypeParams(Arrays.copyOf(current, current.length)));
			return;
		}
		
		Set<UnifyType> localCandidates = candidates.get(idx);
		
		for(UnifyType t : localCandidates) {
			current[idx] = t;
			permuteParams(candidates, idx+1, result, current);
		}
	}
	
	@Override
	public String toString(){
		return this.inheritanceGraph.toString();
	}
	
	/* entfernt PL 2018-12-11
	public int compare (UnifyType left, UnifyType right) {
		return compare(left, right, PairOperator.SMALLERDOT);
	}
	*/
	
	public int compare (UnifyType left, UnifyType right, PairOperator pairop) {
		if (left.getName().equals("Matrix") || right.getName().equals("Matrix")) 
			System.out.println("");
		/*
		pairop = PairOperator.SMALLERDOTWC;
		List<UnifyType> al = new ArrayList<>();
		PlaceholderType xx =new PlaceholderType("xx");
		al.add(xx);
		left = new ExtendsType(new ReferenceType("Vector", new TypeParams(al)));
		
		List<UnifyType> alr = new ArrayList<>();
		UnifyType exx = new ExtendsType(xx);
		alr.add(exx);
		right = new ExtendsType(new ReferenceType("Vector", new TypeParams(alr)));
		*/
		/*
		List<UnifyType> al = new ArrayList<>();
		PlaceholderType xx =new PlaceholderType("xx");
		al.add(xx);
		left = new ExtendsType(xx);
		right = xx;
		*/
		/*
		List<UnifyType> al = new ArrayList<>();
		PlaceholderType xx =new PlaceholderType("xx");
		PlaceholderType yy =new PlaceholderType("yy");
		al.add(xx);
		left = yy;
		right = new ExtendsType(xx);
		*/
		//Die Faelle abfangen, bei den Variablen verglichen werden PL 2018-12-11
		UnifyType ex;
		if (left instanceof PlaceholderType) { 
				if ((right instanceof WildcardType) 
						&& ((ex = ((WildcardType)right).wildcardedType) instanceof PlaceholderType)
						&& ((PlaceholderType)left).getName().equals(((PlaceholderType)ex).getName())) {// a <.? ? extends a oder a <.? ? super a
					return -1;
				}
				else {
					return 0;
				}
		}
		if (right instanceof PlaceholderType) {//&& (left instanceof WildcardType)) {PL geloescht 2019-01-15 analog zu oben
			if ((left instanceof WildcardType) //PL eingefuegt 2019-01-15 analog zu oben
					&& ((ex = ((WildcardType)left).wildcardedType) instanceof PlaceholderType)
					&& ((PlaceholderType)right).getName().equals(((PlaceholderType)ex).getName())) {// ? extends a <. a oder ? super a <. a
				return 1;
			}
			else {
				return 0;
			}
		}
		UnifyPair up = new UnifyPair(left, right, pairop);
		TypeUnifyTask unifyTask = new TypeUnifyTask();
		HashSet<UnifyPair> hs = new HashSet<>();
		hs.add(up);
		Set<UnifyPair> smallerRes = unifyTask.applyTypeUnificationRules(hs, this);
		if (left.getName().equals("Matrix") || right.getName().equals("Matrix")) 
		{try { 
			logFile.write("\nsmallerRes: " + smallerRes);//"smallerHash: " + greaterHash.toString());
			logFile.flush();
		}
		catch (IOException e) {
			System.err.println("no LogFile");}}
		//Gleichungen der Form a <./=. Theta oder Theta <./=. a oder a <./=. b sind ok.
		Predicate<UnifyPair> delFun = x -> !((x.getLhsType() instanceof PlaceholderType || 
											 x.getRhsType() instanceof PlaceholderType)
								 			 && !((x.getLhsType() instanceof WildcardType) &&   //? extends/super a <.? a 
													 ((WildcardType)x.getLhsType()).getWildcardedType().equals(x.getRhsType()))
											 );
		long smallerLen = smallerRes.stream().filter(delFun).count();
		if (smallerLen == 0) return -1;
		else {
			up = new UnifyPair(right, left, pairop);
			//TypeUnifyTask unifyTask = new TypeUnifyTask();
			hs = new HashSet<>();
			hs.add(up);
			Set<UnifyPair> greaterRes = unifyTask.applyTypeUnificationRules(hs, this);
			if (left.getName().equals("Matrix") || right.getName().equals("Matrix")) 
			{try { 
				logFile.write("\ngreaterRes: " + greaterRes);//"smallerHash: " + greaterHash.toString());
				logFile.flush();
			}
			catch (IOException e) {
				System.err.println("no LogFile");}}
			//Gleichungen der Form a <./=. Theta oder Theta <./=. a oder a <./=. b sind ok. 
			long greaterLen = greaterRes.stream().filter(delFun).count();
			if (greaterLen == 0) return 1;
			else return 0;
		}	
	}
}
