package de.dhbwstuttgart.typeinference.unify;

import de.dhbwstuttgart.util.Logger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;
import java.util.function.Function;
import java.util.stream.Collectors;

import de.dhbwstuttgart.exceptions.DebugException;
import de.dhbwstuttgart.typeinference.unify.interfaces.IFiniteClosure;
import de.dhbwstuttgart.typeinference.unify.interfaces.IRuleSet;
import de.dhbwstuttgart.typeinference.unify.model.*;
import de.dhbwstuttgart.typeinference.constraints.Constraint;

import java.io.IOException;
import java.io.Writer;
import java.io.OutputStreamWriter;

/**
 * Implementation of the type inference rules.
 * @author Florian Steurer
 *
 */
public class RuleSet implements IRuleSet{
	
	Logger logger;
	final PlaceholderRegistry placeholderRegistry;
	
	public RuleSet(PlaceholderRegistry placeholderRegistry) {
		super();
		logger = Logger.NULL_LOGGER;
		this.placeholderRegistry = placeholderRegistry;
	}
	
	RuleSet(Logger logger, PlaceholderRegistry placeholderRegistry) {
		this.logger = logger;
		this.placeholderRegistry = placeholderRegistry;
	}
	
	@Override
	public Optional<UnifyPair> reduceUp(UnifyPair pair) {
		// Check if reduce up is applicable 
		if(pair.getPairOp() != PairOperator.SMALLERDOT)
			return Optional.empty();
		
		UnifyType rhsType = pair.getRhsType();
		if(!(rhsType instanceof SuperType))
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		if(!(lhsType instanceof ReferenceType) && !(lhsType instanceof PlaceholderType))
			return Optional.empty();
		
		// Rule is applicable, unpack the SuperType 
		return Optional.of(new UnifyPair(lhsType, ((SuperType) rhsType).getSuperedType(), PairOperator.SMALLERDOT, pair.getSubstitution(), pair.getBasePair()));
	}

	@Override
	public Optional<UnifyPair> reduceLow(UnifyPair pair) {
		// Check if rule is applicable
		if(pair.getPairOp() != PairOperator.SMALLERDOT)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		if(!(lhsType instanceof ExtendsType))
			return Optional.empty();
		
		UnifyType rhsType = pair.getRhsType();
		if(!(rhsType instanceof ReferenceType) && !(rhsType instanceof PlaceholderType))
			return Optional.empty();

		// Rule is applicable, unpack the ExtendsType
		return Optional.of(new UnifyPair(((ExtendsType) lhsType).getExtendedType(), rhsType, PairOperator.SMALLERDOT, pair.getSubstitution(), pair.getBasePair()));
	}

	@Override
	public Optional<UnifyPair> reduceUpLow(UnifyPair pair) {
		// Check if rule is applicable
		if(pair.getPairOp() != PairOperator.SMALLERDOT)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		if(!(lhsType instanceof ExtendsType))
			return Optional.empty();
		
		UnifyType rhsType = pair.getRhsType();
		if(!(rhsType instanceof SuperType))
			return Optional.empty();
		
		// Rule is applicable, unpack both sides
		return Optional.of(new UnifyPair(((ExtendsType) lhsType).getExtendedType(),((SuperType) rhsType).getSuperedType(), PairOperator.SMALLERDOT, pair.getSubstitution(), pair.getBasePair()));
	}

	@Override
	public Optional<Set<UnifyPair>> reduceExt(UnifyPair pair, IFiniteClosure fc) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return Optional.empty();

		UnifyType x = pair.getLhsType();
		UnifyType sTypeX;
		
		if(x instanceof ReferenceType)
			sTypeX = x;
		else if(x instanceof ExtendsType)
			sTypeX = ((ExtendsType) x).getExtendedType();
		else 
			return Optional.empty();

		UnifyType extY = pair.getRhsType();

		if(!(extY instanceof ExtendsType))
			return Optional.empty();

		if(x.getTypeParams().empty() || extY.getTypeParams().size() != x.getTypeParams().size())
			return Optional.empty();
		
		UnifyType xFromFc = fc.getLeftHandedType(sTypeX.getName()).orElse(null);
		
		if(xFromFc == null || !xFromFc.getTypeParams().arePlaceholders())
			return Optional.empty();
		
		if(x instanceof ExtendsType)
			xFromFc = new ExtendsType(xFromFc);
		
		UnifyType extYFromFc = fc.grArg(xFromFc, new HashSet<>()).stream().filter(t -> t.getName().equals(extY.getName())).filter(t -> t.getTypeParams().arePlaceholders()).findAny().orElse(null);
		
		if(extYFromFc == null || extYFromFc.getTypeParams() != xFromFc.getTypeParams())
			return Optional.empty();

		TypeParams extYParams = extY.getTypeParams();
		TypeParams xParams = x.getTypeParams();
		
		int[] pi = pi(xParams, extYParams);
		
		if(pi.length == 0)
			return Optional.empty();
		
		Set<UnifyPair> result = new HashSet<>();
		
		for(int rhsIdx = 0; rhsIdx < extYParams.size(); rhsIdx++)
			result.add(new UnifyPair(xParams.get(pi[rhsIdx]), extYParams.get(rhsIdx), PairOperator.SMALLERDOTWC, pair.getSubstitution(), pair.getBasePair()));
		
		return Optional.of(result);
	}

	@Override
	public Optional<Set<UnifyPair>> reduceSup(UnifyPair pair, IFiniteClosure fc) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return Optional.empty();
		
		UnifyType x = pair.getLhsType();
		UnifyType sTypeX;
		
		if(x instanceof ReferenceType)
			sTypeX = x;
		else if(x instanceof SuperType)
			sTypeX = ((SuperType) x).getSuperedType();
		else
			return Optional.empty();
		
		UnifyType supY = pair.getRhsType();
		
		if(!(supY instanceof SuperType))
			return Optional.empty();

		if(x.getTypeParams().empty() || supY.getTypeParams().size() != x.getTypeParams().size())
			return Optional.empty();
		
		UnifyType xFromFc = fc.getLeftHandedType(sTypeX.getName()).orElse(null);
		
		if(xFromFc == null || !xFromFc.getTypeParams().arePlaceholders())
			return Optional.empty();
		
		if(x instanceof SuperType)
			xFromFc = new SuperType(xFromFc);
		
		UnifyType supYFromFc = fc.grArg(xFromFc, new HashSet<>()).stream().filter(t -> t.getName().equals(supY.getName())).filter(t -> t.getTypeParams().arePlaceholders()).findAny().orElse(null);
		
		if(supYFromFc == null || supYFromFc.getTypeParams() != xFromFc.getTypeParams())
			return Optional.empty();
		
		TypeParams supYParams = supY.getTypeParams();
		TypeParams xParams = x.getTypeParams();
		Set<UnifyPair> result = new HashSet<>();
		
		int[] pi = pi(xParams, supYParams);
		
		if(pi.length == 0)
			return Optional.empty();
		
		for(int rhsIdx = 0; rhsIdx < supYParams.size(); rhsIdx++)
			result.add(new UnifyPair(supYParams.get(rhsIdx), xParams.get(pi[rhsIdx]), PairOperator.SMALLERDOTWC, pair.getSubstitution(), pair.getBasePair()));
		
		return Optional.of(result);
	}

	@Override
	public Optional<Set<UnifyPair>> reduceEq(UnifyPair pair) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		if(lhsType instanceof PlaceholderType || lhsType.getTypeParams().empty())
			return Optional.empty();
		
		UnifyType rhsType = pair.getRhsType();
		
		if(!rhsType.getName().equals(lhsType.getName()))
			return Optional.empty();
		
		if(rhsType instanceof PlaceholderType || lhsType instanceof PlaceholderType || rhsType.getTypeParams().empty())
			return Optional.empty();
		
		if(rhsType.getTypeParams().size() != lhsType.getTypeParams().size())
			return Optional.empty();
		
		// Keine Permutation wie im Paper nötig
		Set<UnifyPair> result = new HashSet<>();
		TypeParams lhsTypeParams = lhsType.getTypeParams();
		TypeParams rhsTypeParams = rhsType.getTypeParams();
		
		for(int i = 0; i < lhsTypeParams.size(); i++)
			result.add(new UnifyPair(lhsTypeParams.get(i), rhsTypeParams.get(i), PairOperator.EQUALSDOT, pair.getSubstitution(), pair.getBasePair()));
		
		return Optional.of(result);
	}

	@Override
	public Optional<Set<UnifyPair>> reduce1(UnifyPair pair, IFiniteClosure fc) {
		if(pair.getPairOp() != PairOperator.SMALLERDOT)
			return Optional.empty();
		
		UnifyType c = pair.getLhsType();
		if(!(c instanceof ReferenceType))
			return Optional.empty();
		
		UnifyType d = pair.getRhsType();
		if(!(d instanceof ReferenceType))
			return Optional.empty();
		
		ReferenceType lhsSType = (ReferenceType) c;
		ReferenceType rhsSType = (ReferenceType) d;
		
		//try {
		//	logFile.write("PAIR Rules: " + pair + "\n");
		//	logFile.flush();
		//}
		//catch (IOException e) { }
		
		if(lhsSType.getTypeParams().empty() || lhsSType.getTypeParams().size() != rhsSType.getTypeParams().size())
			return Optional.empty();
		
		UnifyType cFromFc = fc.getLeftHandedType(c.getName()).orElse(null);
		//2018-02-23: liefert Vector<Vector<Integer>>: Das kann nicht sein.
		
		//NOCHMAL UEBERPRUEFEN
		//PL 18-02-09 Eingfuegt Anfang
		//C und D koennen auch gleich sein.
		if (c.getName().equals(d.getName())) {
			Set<UnifyPair> result = new HashSet<>();
			TypeParams rhsTypeParams = d.getTypeParams();
			TypeParams lhsTypeParams = c.getTypeParams();
			for(int rhsIdx = 0; rhsIdx < c.getTypeParams().size(); rhsIdx++)
				result.add(new UnifyPair(lhsTypeParams.get(rhsIdx), rhsTypeParams.get(rhsIdx), PairOperator.SMALLERDOTWC, pair.getSubstitution(), pair.getBasePair()));
		
			return Optional.of(result);
		}
		//PL 18-02-09 Eingfuegt ENDE
		
		//try {
		//	logFile.write("cFromFc: " + cFromFc);
		//	logFile.flush();
		//}
		//catch (IOException e) { }
		
		if(cFromFc == null || !cFromFc.getTypeParams().arePlaceholders())
			return Optional.empty();
		
		UnifyType dFromFc = fc.getAncestors(cFromFc).stream().filter(x -> x.getName().equals(d.getName())).findAny().orElse(null);
		
		//try {
		//	logFile.write("cFromFc: " + cFromFc);
		//	logFile.flush();
		//}
		//catch (IOException e) { }
		
		if(dFromFc == null || !dFromFc.getTypeParams().arePlaceholders() || dFromFc.getTypeParams().size() != cFromFc.getTypeParams().size())
			return Optional.empty();
		//context.logger().info("cFromFc: " + cFromFc);
		//context.logger().info("dFromFc: " + dFromFc);
		int[] pi = pi(cFromFc.getTypeParams(), dFromFc.getTypeParams());
		
		if(pi.length == 0)
			return Optional.empty();
		
		TypeParams rhsTypeParams = d.getTypeParams();
		TypeParams lhsTypeParams = c.getTypeParams();
		Set<UnifyPair> result = new HashSet<>();
		
		for(int rhsIdx = 0; rhsIdx < rhsTypeParams.size(); rhsIdx++)
			result.add(new UnifyPair(lhsTypeParams.get(pi[rhsIdx]), rhsTypeParams.get(rhsIdx), PairOperator.SMALLERDOTWC, pair.getSubstitution(), pair.getBasePair()));
		
		return Optional.of(result);
	}

	@Override
	public Optional<Set<UnifyPair>> reduce2(UnifyPair pair) {
		if(pair.getPairOp() != PairOperator.EQUALSDOT)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		ReferenceType lhsSType;
		UnifyType rhsType = pair.getRhsType();
		ReferenceType rhsSType;
		
		if ((lhsType instanceof ReferenceType) && (rhsType instanceof ReferenceType)) {
			lhsSType = (ReferenceType) lhsType;
			rhsSType = (ReferenceType) rhsType;
		}
		else if (((lhsType instanceof ExtendsType) && (rhsType instanceof ExtendsType)) 
				|| ((lhsType instanceof SuperType) && (rhsType instanceof SuperType))) {
			UnifyType lhsSTypeRaw = ((WildcardType) lhsType).getWildcardedType();
			UnifyType rhsSTypeRaw = ((WildcardType) rhsType).getWildcardedType();
			if ((lhsSTypeRaw instanceof ReferenceType) && (rhsSTypeRaw instanceof ReferenceType)) {
				lhsSType = (ReferenceType) lhsSTypeRaw;
				rhsSType = (ReferenceType) rhsSTypeRaw;
			}
			else
				return Optional.empty();
		}
		else
			return Optional.empty();
		
		if(lhsSType.getTypeParams().empty())
			return Optional.empty();

		/* PL 2018-01-22 in obere Teil integriert
		UnifyType rhsType = pair.getRhsType();
		ReferenceType rhsSType;
		
		if(rhsType instanceof ReferenceType)
			rhsSType = (ReferenceType) rhsType;
		else if(rhsType instanceof WildcardType)  {
			UnifyType rhsSTypeRaw = ((WildcardType) rhsType).getWildcardedType();
			if(rhsSTypeRaw instanceof ReferenceType)
				rhsSType = (ReferenceType) rhsSTypeRaw;
			else
				return Optional.empty();
		}
		else
			return Optional.empty();
		*/
		
		if(!rhsSType.getName().equals(lhsSType.getName()))
			return Optional.empty();
		
		if(!(lhsSType.getTypeParams().size()==rhsSType.getTypeParams().size()))throw new DebugException("Fehler in Unifizierung"+ " " + lhsSType.toString() + " " + rhsSType.toString());
		//if(rhsSType.getTypeParams().size() != lhsSType.getTypeParams().size())
		//	return Optional.empty();
		
		Set<UnifyPair> result = new HashSet<>();
		
		TypeParams rhsTypeParams = rhsSType.getTypeParams();
		TypeParams lhsTypeParams = lhsSType.getTypeParams();
		for(int i = 0; i < rhsTypeParams.size(); i++)
			result.add(new UnifyPair(lhsTypeParams.get(i), rhsTypeParams.get(i), PairOperator.EQUALSDOT, pair.getSubstitution(), pair.getBasePair()));
		
		return Optional.of(result);
	}

	@Override
	public boolean erase1(UnifyPair pair, IFiniteClosure fc) {
		if((pair.getPairOp() != PairOperator.SMALLERDOT) && (pair.getPairOp() != PairOperator.SMALLERNEQDOT))
			return false;
		
		UnifyType lhsType = pair.getLhsType();
		UnifyType rhsType = pair.getRhsType();
			
		/*
		 * ty <. ? extends ty' is wrong	
		 */
		if (rhsType instanceof ExtendsType) {
			return false;
		}
	
		/*
		 * ? super ty <. ty' is wrong
		 * except Ty' = Object or ty' = ? super Object	
		 */
		if ((lhsType instanceof SuperType) && 
				(!(rhsType.equals(new ReferenceType("java.lang.Object", false)))) &&
				!(rhsType.equals(new SuperType (new ReferenceType("java.lang.Object", false))))) {
			return false;
		}
		
		/*
		 * ? extends ty <. ty' is equivalent to ty < ty'
		 */
		if (lhsType instanceof ExtendsType) {
				lhsType = ((WildcardType)lhsType).getWildcardedType();
		}
		
		/*
		 * ty <. ? super ty' ist equivalent to ty <. ty'
		 */
		if (rhsType instanceof SuperType) {
			rhsType = ((WildcardType)rhsType).getWildcardedType();
		}
		
		/*
		 * SMALLERNEQDOT => type must not be equal
		 */
		if (pair.getPairOp() == PairOperator.SMALLERNEQDOT && lhsType.equals(rhsType)){
			return false; 
		}
		
		if(!(lhsType instanceof ReferenceType) && !(lhsType instanceof PlaceholderType))
			return false;
		
		
		if(!(rhsType instanceof ReferenceType) && !(rhsType instanceof PlaceholderType))
			return false;
		
		return fc.greater(lhsType, new HashSet<>(), pair.getLocation()).contains(rhsType);
	}

	@Override
	public boolean erase2(UnifyPair pair, IFiniteClosure fc) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return false;
		
		UnifyType lhsType = pair.getLhsType();		
		UnifyType rhsType = pair.getRhsType();
		
		return fc.grArg(lhsType, new HashSet<>()).contains(rhsType);
	}

	@Override
	public boolean erase3(UnifyPair pair) {
		if(pair.getPairOp() != PairOperator.EQUALSDOT)
			return false;
		
		return pair.getLhsType().equals(pair.getRhsType());
	}

	@Override
	public Optional<UnifyPair> swap(UnifyPair pair) {
		if(pair.getPairOp() != PairOperator.EQUALSDOT)
			return Optional.empty();
		
		if(pair.getLhsType() instanceof PlaceholderType)
			return Optional.empty();
		
		if(!(pair.getRhsType() instanceof PlaceholderType))
			return Optional.empty();
		
		return Optional.of(new UnifyPair(pair.getRhsType(), pair.getLhsType(), PairOperator.EQUALSDOT, pair.getSubstitution(), pair.getBasePair()));
	}

	@Override
	public Optional<UnifyPair> adapt(UnifyPair pair, IFiniteClosure fc) {
		if(pair.getPairOp() != PairOperator.SMALLERDOT)
			return Optional.empty();
		
		UnifyType typeD = pair.getLhsType();
		if(!(typeD instanceof ReferenceType))
			return Optional.empty();
		
		UnifyType typeDs = pair.getRhsType();
		if(!(typeDs instanceof ReferenceType))
			return Optional.empty();
		
		/*if(typeD.getTypeParams().size() == 0 || typeDs.getTypeParams().size() == 0)
			return Optional.empty();*/
		
		if(typeD.getName().equals(typeDs.getName()))
			return Optional.empty();
		

		Optional<UnifyType> opt = fc.getLeftHandedType(typeD.getName());
		if(!opt.isPresent())
			return Optional.empty();
		
		// The generic Version of Type D (D<a1, a2, a3, ... >)
		UnifyType typeDgen = opt.get();
	
		// Actually greater+ because the types are ensured to have different names
		Set<UnifyType> greater = fc.getAncestors(typeDgen);
		opt = greater.stream().filter(x -> x.getName().equals(typeDs.getName())).findAny();

		if(!opt.isPresent())
			return Optional.empty();
		
		UnifyType newLhs = opt.get();
		
		TypeParams typeDParams = typeD.getTypeParams();
		TypeParams typeDgenParams = typeDgen.getTypeParams();
		
		//context.logger().info("Pair: " +pair);
		//context.logger().info("typeD: " +typeD);
		//context.logger().info("typeDParams: " +typeDParams);
		//context.logger().info("typeDgen: " +typeD);
		//context.logger().info("typeDgenParams: " +typeDgenParams);
		Unifier unif = Unifier.identity();
		for(int i = 0; i < typeDParams.size(); i++) { 
			//context.logger().info("ADAPT" +typeDgenParams);
			if (typeDgenParams.get(i) instanceof PlaceholderType) 
				unif.add((PlaceholderType) typeDgenParams.get(i), typeDParams.get(i));
			else logger.exception(new Exception("ERROR in adapt rule: cannot add non placeholder type"));
		}
		return Optional.of(new UnifyPair(unif.apply(newLhs), typeDs, PairOperator.SMALLERDOT, pair.getSubstitution(), pair.getBasePair()));
	}

	@Override
	public Optional<UnifyPair> adaptExt(UnifyPair pair, IFiniteClosure fc) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return Optional.empty();
		
		UnifyType typeD = pair.getLhsType();
		if(!(typeD instanceof ReferenceType) && !(typeD instanceof ExtendsType))
			return Optional.empty();
		
		UnifyType typeExtDs = pair.getRhsType();
		if(!(typeExtDs instanceof ExtendsType))
			return Optional.empty();
		
		if(typeD.getTypeParams().size() == 0 || typeExtDs.getTypeParams().size() == 0)
			return Optional.empty();
		
		UnifyType typeDgen;
		if(typeD instanceof ReferenceType)
			typeDgen = fc.getLeftHandedType(typeD.getName()).orElse(null);
		else {
			Optional<UnifyType> opt = fc.getLeftHandedType(((ExtendsType) typeD).getExtendedType().getName());
			typeDgen = opt.isPresent() ? new ExtendsType(opt.get()) : null;
		}

		if(typeDgen == null)
			return Optional.empty();
		
		Set<UnifyType> grArg = fc.grArg(typeDgen, new HashSet<>());
		
		Optional<UnifyType> opt = grArg.stream().filter(x -> x.getName().equals(typeExtDs.getName())).findAny();

		if(!opt.isPresent())
			return Optional.empty();
		
		UnifyType newLhs = ((ExtendsType) opt.get()).getExtendedType();
		
		TypeParams typeDParams = typeD.getTypeParams();
		TypeParams typeDgenParams = typeDgen.getTypeParams();
		
		Unifier unif = new Unifier((PlaceholderType) typeDgenParams.get(0), typeDParams.get(0));
		for(int i = 1; i < typeDParams.size(); i++) 
			unif.add((PlaceholderType) typeDgenParams.get(i), typeDParams.get(i));
		
		return Optional.of(new UnifyPair(unif.apply(newLhs), typeExtDs, PairOperator.SMALLERDOTWC, pair.getSubstitution(), pair.getBasePair()));
	}

	@Override
	public Optional<UnifyPair> adaptSup(UnifyPair pair, IFiniteClosure fc) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return Optional.empty();
		
		UnifyType typeDs = pair.getLhsType();
		if(!(typeDs instanceof ReferenceType) && !(typeDs instanceof SuperType))
			return Optional.empty();
		
		UnifyType typeSupD = pair.getRhsType();
		if(!(typeSupD instanceof SuperType))
			return Optional.empty();
		
		if(typeDs.getTypeParams().size() == 0 || typeSupD.getTypeParams().size() == 0)
			return Optional.empty();
		
		
		Optional<UnifyType> opt = fc.getLeftHandedType(((SuperType) typeSupD).getSuperedType().getName());
		
		if(!opt.isPresent()) 
			return Optional.empty();
		
		UnifyType typeDgen = opt.get();
		UnifyType typeSupDgen = new SuperType(typeDgen);

		// Use of smArg instead of grArg because
		// a in grArg(b) => b in smArg(a)
		Set<UnifyType> smArg = fc.smArg(typeSupDgen, new HashSet<>());
		opt = smArg.stream().filter(x -> x.getName().equals(typeDs.getName())).findAny();

		if(!opt.isPresent())
			return Optional.empty();
		
		// New RHS
		UnifyType newRhs = null;
		if(typeDs instanceof ReferenceType)
			newRhs = new ExtendsType(typeDs);
		else
			newRhs = new ExtendsType(((SuperType) typeDs).getSuperedType());
		
		// New LHS
		UnifyType newLhs = opt.get();
		TypeParams typeDParams = typeSupD.getTypeParams();
		TypeParams typeSupDsgenParams = typeSupDgen.getTypeParams();
		
		Unifier unif = new Unifier((PlaceholderType) typeSupDsgenParams.get(0), typeDParams.get(0));
		for(int i = 1; i < typeDParams.size(); i++) 
			unif.add((PlaceholderType) typeSupDsgenParams.get(i), typeDParams.get(i));
		
		return Optional.of(new UnifyPair(unif.apply(newLhs), newRhs, PairOperator.SMALLERDOTWC, pair.getSubstitution(), pair.getBasePair()));
	}

	/**
	 * Finds the permutation pi of the type arguments of two types based on the finite closure
	 * @param cArgs The type which arguments are permuted
	 * @param dArgs The other type
	 * @return An array containing the values of pi for every type argument of C or an empty array if the search failed.
	 */
	private int[] pi(TypeParams cArgs, TypeParams dArgs) {
		if(!(cArgs.size()==dArgs.size()))throw new DebugException("Fehler in Unifizierung");
	
		int[] permutation = new int[dArgs.size()];
		
		boolean succ = true;
		for (int dArgIdx = 0; dArgIdx < dArgs.size() && succ; dArgIdx++) {
			UnifyType dArg = dArgs.get(dArgIdx);
			succ = false;
			for (int pi = 0; pi < cArgs.size(); pi++)
				if (cArgs.get(pi).getName().equals(dArg.getName())) {
					permutation[dArgIdx] = pi;
					succ = true;
					break;
				}
		}

		return succ ? permutation : new int[0];
	}

	public Optional<Set<UnifyPair>> subst(Set<UnifyPair> pairs) {
		return subst(pairs, new ArrayList<>());
	}
	
	@Override
	public Optional<Set<UnifyPair>> subst(Set<UnifyPair> pairs, List<Set<Constraint<UnifyPair>>> oderConstraints) {
		// Statistically, typeMap will fill up quickly and resize multiple times. To reduce this, we start with a higher capacity
		HashMap<UnifyType, Integer> typeMap = new HashMap<>(200);
		
		Stack<UnifyType> occuringTypes = new Stack<>();
		occuringTypes.ensureCapacity(pairs.size() * 3);

		for(UnifyPair pair : pairs) {
			occuringTypes.push(pair.getLhsType());
			occuringTypes.push(pair.getRhsType());
		}

		while(!occuringTypes.isEmpty()) {
			UnifyType t1 = occuringTypes.pop();
			if(!typeMap.containsKey(t1))
				typeMap.put(t1, 0);
			typeMap.put(t1, typeMap.get(t1)+1);
			
			if(t1 instanceof ExtendsType)
				occuringTypes.push(((ExtendsType) t1).getExtendedType());
			if(t1 instanceof SuperType)
				occuringTypes.push(((SuperType) t1).getSuperedType());
			else
				t1.getTypeParams().forEach(occuringTypes::push);
		}
		LinkedList<UnifyPair> result1 = new LinkedList<UnifyPair>(pairs);
		ArrayList<UnifyPair> result = new ArrayList<UnifyPair>();
		boolean applied = false;

		while(!result1.isEmpty()) {
			UnifyPair pair = result1.poll();
			PlaceholderType lhsType = null;
			UnifyType rhsType;
			
			if(pair.getPairOp() == PairOperator.EQUALSDOT
					&& pair.getLhsType() instanceof PlaceholderType)
				lhsType = (PlaceholderType) pair.getLhsType();
				rhsType = pair.getRhsType(); //PL eingefuegt 2017-09-29 statt !((rhsType = pair.getRhsType()) instanceof PlaceholderType)
			
			if(lhsType != null 
					//&& !((rhsType = pair.getRhsType()) instanceof PlaceholderType) //PL geloescht am 2017-09-29 Begründung: auch Typvariablen muessen ersetzt werden.
					&& typeMap.get(lhsType) > 1  // The type occurs in more pairs in the set than just the recent pair.
					&& !rhsType.getTypeParams().occurs(lhsType) 
					&& !((rhsType instanceof WildcardType) && ((WildcardType)rhsType).getWildcardedType().equals(lhsType))) //PL eigefuegt 2018-02-18
			{
				Unifier uni = new Unifier(lhsType,  rhsType);
				// apply unifier to result and result1 in place
				result.replaceAll(p -> uni.apply(pair, p));
				ListIterator<UnifyPair> result1Iterator = result1.listIterator();
				while (result1Iterator.hasNext()) {
					UnifyPair x = result1Iterator.next();
					result1Iterator.set(uni.apply(pair, x));
				}

				Function<? super Constraint<UnifyPair>,? extends Constraint<UnifyPair>> applyUni = b -> b.stream().map(
						x -> uni.apply(pair,x)).collect(Collectors.toCollection((b.getExtendConstraint() != null)
								? () -> new Constraint<UnifyPair>(
										  b.isInherited(),
										  b.isImplemented(),
										  b.getExtendConstraint().createdMapped(x -> uni.apply(pair,x)),
										  b.getmethodSignatureConstraint().stream().map(x -> uni.apply(pair,x)).collect(Collectors.toCollection(HashSet::new)))
								: () -> new Constraint<UnifyPair>(b.isInherited(), b.isImplemented())
									));
				oderConstraints.replaceAll(oc -> {
						HashSet<Constraint<UnifyPair>> mapped = new HashSet<>(oc.size());
						for (var element : oc) {
							mapped.add(applyUni.apply(element));
						}
						return mapped;
				});
				/*
						oderConstraints = oderConstraints.stream().map(
						a -> a.stream().map(applyUni
								//b -> b.stream().map(
								//		x -> uni.apply(pair,x)).collect(Collectors.toCollection(HashSet::new) )
								).collect(Collectors.toCollection(HashSet::new))
						).collect(Collectors.toList(ArrayList::new));
				}
				*/
				applied = true;
			}
			result.add(pair);
		}
		
		return applied ? Optional.of(new HashSet<>(result)) : Optional.empty();
	}

	@Override
	public Optional<UnifyPair> reduceWildcardLow(UnifyPair pair) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		UnifyType rhsType = pair.getRhsType();
		if(!(lhsType instanceof ExtendsType) || !(rhsType instanceof ExtendsType))
			return Optional.empty();
		
		return Optional.of(new UnifyPair(((ExtendsType) lhsType).getExtendedType(), ((ExtendsType) rhsType).getExtendedType(), PairOperator.SMALLERDOT, pair.getSubstitution(), pair.getBasePair())); 
	}

	@Override
	public Optional<UnifyPair> reduceWildcardLowRight(UnifyPair pair) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		UnifyType rhsType = pair.getRhsType();
		if((!(lhsType instanceof ReferenceType) && !(lhsType instanceof FunNType)) || !(rhsType instanceof ExtendsType))
			return Optional.empty();
		
		return Optional.of(new UnifyPair(lhsType, ((ExtendsType) rhsType).getExtendedType(), PairOperator.SMALLERDOT, pair.getSubstitution(), pair.getBasePair()));
	}

	@Override
	public Optional<UnifyPair> reduceWildcardUp(UnifyPair pair) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		UnifyType rhsType = pair.getRhsType();
		if(!(lhsType instanceof SuperType) || !(rhsType instanceof SuperType))
			return Optional.empty();
		
		return Optional.of(new UnifyPair(((SuperType) rhsType).getSuperedType(), ((SuperType) lhsType).getSuperedType(), PairOperator.SMALLERDOT, pair.getSubstitution(), pair.getBasePair()));
	}

	@Override
	public Optional<UnifyPair> reduceWildcardUpRight(UnifyPair pair) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		UnifyType rhsType = pair.getRhsType();
		if((!(lhsType instanceof ReferenceType) && !(lhsType instanceof FunNType)) || !(rhsType instanceof SuperType))
			return Optional.empty();
		
		return Optional.of(new UnifyPair(((SuperType) rhsType).getSuperedType(), lhsType, PairOperator.SMALLERDOTWC, pair.getSubstitution(), pair.getBasePair()));
	}

	/* PL 2018-03-06 auskommentiert sind mutmaßlich falsch
	 * vgl. JAVA_BSP/Wildcard6.java
	@Override
	public Optional<UnifyPair> reduceWildcardLowUp(UnifyPair pair) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		UnifyType rhsType = pair.getRhsType();
		if(!(lhsType instanceof ExtendsType) || !(rhsType instanceof SuperType))
			return Optional.empty();
		
		return Optional.of(new UnifyPair(((ExtendsType) lhsType).getExtendedType(), ((SuperType) rhsType).getSuperedType(), PairOperator.EQUALSDOT));
	}

	@Override
	public Optional<UnifyPair> reduceWildcardUpLow(UnifyPair pair) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		UnifyType rhsType = pair.getRhsType();
		if(!(lhsType instanceof SuperType) || !(rhsType instanceof ExtendsType))
			return Optional.empty();
		
		return Optional.of(new UnifyPair(((SuperType) lhsType).getSuperedType(), ((ExtendsType) rhsType).getExtendedType(), PairOperator.EQUALSDOT));
	}

	
	@Override
	public Optional<UnifyPair> reduceWildcardLeft(UnifyPair pair) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return Optional.empty();
		
		UnifyType rhsType = pair.getRhsType();
		if(!(rhsType instanceof ReferenceType))
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		
		if(lhsType instanceof WildcardType)
			return Optional.of(new UnifyPair(((WildcardType) lhsType).getWildcardedType(), rhsType, PairOperator.EQUALSDOT));
		
		return Optional.empty();
	}
	*/
	@Override
	public Optional<Set<UnifyPair>> reduceFunN(UnifyPair pair) {
		if((pair.getPairOp() != PairOperator.SMALLERDOT)  
		  && (pair.getPairOp() != PairOperator.EQUALSDOT))     //PL 2017-10-03 hinzugefuegt  
		                                                       //da Regel auch fuer EQUALSDOT anwendbar 
			                                                   //TODO: fuer allen anderen Relationen noch pruefen
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		UnifyType rhsType = pair.getRhsType();
		
		if(!(lhsType instanceof FunNType) || !(rhsType instanceof FunNType))
			return Optional.empty();

		FunNType funNLhsType = (FunNType) lhsType;
		FunNType funNRhsType = (FunNType) rhsType;
		
		if(funNLhsType.getN() != funNRhsType.getN())
			return Optional.empty();
		
		Set<UnifyPair> result = new HashSet<UnifyPair>();
		if (pair.getPairOp() == PairOperator.SMALLERDOT) { 
			result.add(new UnifyPair(funNLhsType.getTypeParams().get(funNLhsType.getTypeParams().size()-1), funNRhsType.getTypeParams().get(funNRhsType.getTypeParams().size()-1), PairOperator.SMALLERDOT, pair.getSubstitution(), pair.getBasePair()));
			for(int i = 0; i < funNLhsType.getTypeParams().size()-1; i++) {
				result.add(new UnifyPair(funNRhsType.getTypeParams().get(i), funNLhsType.getTypeParams().get(i), PairOperator.SMALLERDOT, pair.getSubstitution(), pair.getBasePair()));
			}
		}
		else {// pair.getPairOp() == PairOperator.EQUALDOT
			result.add(new UnifyPair(funNLhsType.getTypeParams().get(funNLhsType.getTypeParams().size()-1), funNRhsType.getTypeParams().get(funNRhsType.getTypeParams().size()-1), PairOperator.EQUALSDOT, pair.getSubstitution(), pair.getBasePair()));
			for(int i = 0; i < funNLhsType.getTypeParams().size()-1; i++) {
				result.add(new UnifyPair(funNRhsType.getTypeParams().get(i), funNLhsType.getTypeParams().get(i), PairOperator.EQUALSDOT, pair.getSubstitution(), pair.getBasePair()));
			}
		}
		result.stream().forEach(x -> { UnifyType l = x.getLhsType();
        								   if (l instanceof PlaceholderType) { ((PlaceholderType)l).disableWildcardtable(); } 
        								   UnifyType r = x.getRhsType();
        								   if (r instanceof PlaceholderType) { ((PlaceholderType)r).disableWildcardtable(); }
								     } );

		logger.debug(() -> "FUNgreater: " + pair);
		logger.debug(() -> "FUNred: " + result);


		return Optional.of(result);
	}
	
	
	@Override
	public Optional<Set<UnifyPair>> greaterFunN(UnifyPair pair, IFiniteClosure fc) {
		if(pair.getPairOp() != PairOperator.SMALLERDOT)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		UnifyType rhsType = pair.getRhsType();
		
		if(!(lhsType instanceof FunNType)) 
			return Optional.empty();
		
		//FunN$$<...> <. FunctinalInterface<...> wird umgewandelt in FunN$$<...> <. FunN$$<... args aus FuntionalInterface ...>
		if (rhsType instanceof ReferenceType) {
			UnifyType typeFI = pair.getRhsType();
			
			Optional<UnifyType> opt = fc.getRightHandedFunctionalInterfaceType(typeFI.getName());
			if(!opt.isPresent())
				return Optional.empty();
			
			if (!(typeFI instanceof ReferenceType refType) || !(refType instanceof FunInterfaceType intf))
				return Optional.empty();

			var fiArgs = intf.getFunctionalInterfaceTypeArguments(refType);
			var retType = fiArgs.getFirst();
			var lhsArgs = intf.getFunctionalInterfaceTypeArguments(lhsType); //FALSCHRUM????
			var lhsRet = lhsArgs.getFirst();

			Set<UnifyPair> result = new HashSet<>();
			if (retType instanceof ExtendsType) {
				result.add(new UnifyPair(lhsRet, retType, PairOperator.SMALLERDOTWC));
			} else if (retType instanceof SuperType) {
				return Optional.empty();
			} else {
				result.add(new UnifyPair(lhsRet, retType, PairOperator.EQUALSDOT));
			}

			for (var i = 1; i < fiArgs.size(); i++) {
				var lh = lhsArgs.get(i);
				var rh = fiArgs.get(i);

				if (rh instanceof SuperType) {
					result.add(new UnifyPair(lh, rh, PairOperator.SMALLERDOTWC));
				} else if (rh instanceof ExtendsType) {
					return Optional.empty();
				} else {
					result.add(new UnifyPair(lh, rh, PairOperator.EQUALSDOT));
				}
			}

			return Optional.of(result);
		}

		else {
			if(!(rhsType instanceof PlaceholderType))
				return Optional.empty();
		}

		FunNType funNLhsType = (FunNType) lhsType;

		Set<UnifyPair> result = new HashSet<UnifyPair>();
		
		int variance = ((PlaceholderType)rhsType).getVariance();
		int inversVariance = distributeVariance.inverseVariance(variance);
		
		UnifyType[] freshPlaceholders = new UnifyType[funNLhsType.getTypeParams().size()];
		for(int i = 0; i < freshPlaceholders.length-1; i++) {
			freshPlaceholders[i] = PlaceholderType.freshPlaceholder(placeholderRegistry);
			((PlaceholderType)freshPlaceholders[i]).setVariance(inversVariance);
		}
		freshPlaceholders[freshPlaceholders.length-1] = PlaceholderType.freshPlaceholder(placeholderRegistry);
		((PlaceholderType)freshPlaceholders[freshPlaceholders.length-1]).setVariance(variance);
		result.add(new UnifyPair(funNLhsType.getTypeParams().get(funNLhsType.getTypeParams().size()-1), freshPlaceholders[funNLhsType.getTypeParams().size()-1], PairOperator.SMALLERDOT, pair.getSubstitution(), pair.getBasePair()));
		
		for(int i = 0; i < funNLhsType.getTypeParams().size()-1; i++) {
			result.add(new UnifyPair(freshPlaceholders[i], funNLhsType.getTypeParams().get(i), PairOperator.SMALLERDOT, pair.getSubstitution(), pair.getBasePair()));
		}
		
		result.add(new UnifyPair(rhsType, funNLhsType.setTypeParams(new TypeParams(freshPlaceholders)), PairOperator.EQUALSDOT, pair.getSubstitution(), pair.getBasePair()));
		
		result.stream().forEach(x -> { UnifyType l = x.getLhsType();
									   if (l instanceof PlaceholderType) { ((PlaceholderType)l).disableWildcardtable(); }
									   UnifyType r = x.getRhsType();
									   if (r instanceof PlaceholderType) { ((PlaceholderType)r).disableWildcardtable(); }
									 } );

		logger.debug(() -> "FUNgreater: " + pair);
		logger.debug(() -> "FUNgreater: " + result);

		return Optional.of(result);
	}

	@Override
	public Optional<Set<UnifyPair>> smallerFunN(UnifyPair pair, IFiniteClosure fc) {
		if(pair.getPairOp() != PairOperator.SMALLERDOT)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		UnifyType rhsType = pair.getRhsType();
		
		if(!(rhsType instanceof FunNType))
			return Optional.empty();
		
		/* muss nach angepasst werden */
		//FunN$$<...> <. FunctinalInterface<...> wird umgewandelt in FunN$$<...> <. FunN$$<... args aus FuntionalInterface ...>
		if (lhsType instanceof ReferenceType) {
			UnifyType typeFI = pair.getLhsType();
					
			Optional<UnifyType> opt = fc.getRightHandedFunctionalInterfaceType(typeFI.getName());
			if(!opt.isPresent())
				return Optional.empty();
					
			if (!(typeFI instanceof ReferenceType refType) || !(refType instanceof FunInterfaceType intf))
				return Optional.empty();

			var fiArgs = intf.getFunctionalInterfaceTypeArguments(refType);
			var retType = fiArgs.getFirst();
			var rhsArgs = intf.getFunctionalInterfaceTypeArguments(rhsType);
			var rhsRet = rhsArgs.getFirst();

			Set<UnifyPair> result = new HashSet<>();
			//if (retType instanceof ExtendsType) {
				result.add(new UnifyPair(retType, rhsRet, PairOperator.SMALLER));
			//} else if (retType instanceof SuperType) {
			//	return Optional.empty();
			//} else {
			//	result.add(new UnifyPair(lhsRet, retType, PairOperator.EQUALSDOT));
			//}

			for (var i = 1; i < fiArgs.size(); i++) {
				var rh = rhsArgs.get(i);
				var lh = fiArgs.get(i);

				//if (rh instanceof SuperType) {
					result.add(new UnifyPair(rh, lh, PairOperator.SMALLER));
				//} else if (rh instanceof ExtendsType) {
				//			return Optional.empty();
				//} else {
				//	result.add(new UnifyPair(lh, rh, PairOperator.EQUALSDOT));
				//}
			}

			return Optional.of(result);
		}

		else {
			if(!(lhsType instanceof PlaceholderType))
				return Optional.empty();
		}
		
		
		

		FunNType funNRhsType = (FunNType) rhsType;

		Set<UnifyPair> result = new HashSet<UnifyPair>();
		
		int variance = ((PlaceholderType)lhsType).getVariance();
		int inversVariance = distributeVariance.inverseVariance(variance);
		
		UnifyType[] freshPlaceholders = new UnifyType[funNRhsType.getTypeParams().size()];
		for(int i = 0; i < freshPlaceholders.length-1; i++) {
			freshPlaceholders[i] = PlaceholderType.freshPlaceholder(placeholderRegistry);
			((PlaceholderType)freshPlaceholders[i]).setVariance(inversVariance);
		}
		freshPlaceholders[freshPlaceholders.length-1] = PlaceholderType.freshPlaceholder(placeholderRegistry);
		((PlaceholderType)freshPlaceholders[freshPlaceholders.length-1]).setVariance(variance);
		
		result.add(new UnifyPair(freshPlaceholders[funNRhsType.getTypeParams().size()-1], funNRhsType.getTypeParams().get(funNRhsType.getTypeParams().size()-1), PairOperator.SMALLERDOT, pair.getSubstitution(), pair.getBasePair()));
		
		for(int i = 0; i < funNRhsType.getTypeParams().size()-1; i++) {
			result.add(new UnifyPair(funNRhsType.getTypeParams().get(i), freshPlaceholders[i], PairOperator.SMALLERDOT, pair.getSubstitution(), pair.getBasePair()));
		}
		
		result.add(new UnifyPair(lhsType, funNRhsType.setTypeParams(new TypeParams(freshPlaceholders)), PairOperator.EQUALSDOT, pair.getSubstitution(), pair.getBasePair()));
		
		result.stream().forEach(x -> { UnifyType l = x.getLhsType();
		   							   if (l instanceof PlaceholderType) { ((PlaceholderType)l).disableWildcardtable(); }
		   							   UnifyType r = x.getRhsType();
		   							   if (r instanceof PlaceholderType) { ((PlaceholderType)r).disableWildcardtable(); }
									 } );


		logger.debug(() -> "FUNgreater: " + pair);
		logger.debug(() -> "FUNsmaller: " + result);

		return Optional.of(result);
	}
	
	@Override
	public Optional<Set<UnifyPair>> reduceFIFunN(UnifyPair pair, IFiniteClosure fc) {
		if(pair.getPairOp() != PairOperator.EQUALSDOT)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		UnifyType rhsType = pair.getRhsType();
		
		if(!(rhsType instanceof FunNType))
			return Optional.empty();
		
		/* muss nach angepasst werden */
		//FunN$$<...> <. FunctinalInterface<...> wird umgewandelt in FunN$$<...> <. FunN$$<... args aus FuntionalInterface ...>
		if (lhsType instanceof ReferenceType) {
			UnifyType typeFI = pair.getLhsType();
					
			Optional<UnifyType> opt = fc.getRightHandedFunctionalInterfaceType(typeFI.getName());
			if(!opt.isPresent())
				return Optional.empty();
					
			if (!(typeFI instanceof ReferenceType refType) || !(refType instanceof FunInterfaceType intf))
				return Optional.empty();

			var fiArgs = intf.getFunctionalInterfaceTypeArguments(refType);
			var retType = fiArgs.getFirst();
			var rhsArgs = intf.getFunctionalInterfaceTypeArguments(rhsType);
			var rhsRet = rhsArgs.getFirst();

			Set<UnifyPair> result = new HashSet<>();
			//if (retType instanceof ExtendsType) {
				result.add(new UnifyPair(retType, rhsRet, PairOperator.EQUALSDOT));
			//} else if (retType instanceof SuperType) {
			//	return Optional.empty();
			//} else {
			//	result.add(new UnifyPair(lhsRet, retType, PairOperator.EQUALSDOT));
			//}

			for (var i = 1; i < fiArgs.size(); i++) {
				var rh = rhsArgs.get(i);
				var lh = fiArgs.get(i);

				//if (rh instanceof SuperType) {
					result.add(new UnifyPair(rh, lh, PairOperator.EQUALSDOT));
				//} else if (rh instanceof ExtendsType) {
				//			return Optional.empty();
				//} else {
				//	result.add(new UnifyPair(lh, rh, PairOperator.EQUALSDOT));
				//}
			}

			return Optional.of(result);
		}
		else {
			return Optional.empty();
		}
	}
	
	@Override
	public Optional<Set<UnifyPair>> reduceFunNFi(UnifyPair pair, IFiniteClosure fc) {
		if(pair.getPairOp() != PairOperator.EQUALSDOT)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		UnifyType rhsType = pair.getRhsType();
		
		if(!(lhsType instanceof FunNType)) 
			return Optional.empty();
		
		//FunN$$<...> <. FunctinalInterface<...> wird umgewandelt in FunN$$<...> <. FunN$$<... args aus FuntionalInterface ...>
		if (rhsType instanceof ReferenceType) {
			UnifyType typeFI = pair.getRhsType();
			
			Optional<UnifyType> opt = fc.getRightHandedFunctionalInterfaceType(typeFI.getName());
			if(!opt.isPresent())
				return Optional.empty();
			
			if (!(typeFI instanceof ReferenceType refType) || !(refType instanceof FunInterfaceType intf))
				return Optional.empty();

			var fiArgs = intf.getFunctionalInterfaceTypeArguments(refType);
			var retType = fiArgs.getFirst();
			var lhsArgs = intf.getFunctionalInterfaceTypeArguments(lhsType);
			var lhsRet = lhsArgs.getFirst();

			Set<UnifyPair> result = new HashSet<>();
			//if (retType instanceof ExtendsType) {
			//	result.add(new UnifyPair(lhsRet, retType, PairOperator.SMALLERDOTWC));
			//} else if (retType instanceof SuperType) {
			//	return Optional.empty();
			//} else {
				result.add(new UnifyPair(lhsRet, retType, PairOperator.EQUALSDOT));
			//}

			for (var i = 1; i < fiArgs.size(); i++) {
				var lh = lhsArgs.get(i);
				var rh = fiArgs.get(i);

				//if (rh instanceof SuperType) {
				//	result.add(new UnifyPair(lh, rh, PairOperator.SMALLERDOTWC));
				//} else if (rh instanceof ExtendsType) {
				//	return Optional.empty();
				//} else {
					result.add(new UnifyPair(lh, rh, PairOperator.EQUALSDOT));
				//}
			}

			return Optional.of(result);
		}

		else {
				return Optional.empty();
		}
	}

	
	@Override
	public Optional<UnifyPair> reduceTph(UnifyPair pair) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		UnifyType rhsType = pair.getRhsType();
		if(!(lhsType instanceof PlaceholderType) || (!(rhsType instanceof ReferenceType) && !(rhsType instanceof FunNType)))
			return Optional.empty();
		
		return Optional.of(new UnifyPair(lhsType, rhsType, PairOperator.EQUALSDOT, pair.getSubstitution(), pair.getBasePair()));
	}

	@Override
	public Optional<Set<UnifyPair>> reduceTphExt(UnifyPair pair) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		UnifyType rhsType = pair.getRhsType();
		if(!(lhsType instanceof ExtendsType) || !(rhsType instanceof PlaceholderType))
			return Optional.empty();
		
		UnifyType extendedType = ((ExtendsType)lhsType).getExtendedType();
		
		if (extendedType.equals(rhsType)) return Optional.empty(); //PL 2019-02-18 eingefügt ? extends a <.? a
		
		boolean isGen = extendedType instanceof PlaceholderType && !((PlaceholderType) extendedType).isGenerated();
		
		Set<UnifyPair> result = new HashSet<>();
		if(isGen)
			result.add(new UnifyPair(rhsType, lhsType, PairOperator.EQUALSDOT, pair.getSubstitution(), pair.getBasePair()));
		else {
			UnifyType freshTph = PlaceholderType.freshPlaceholder(placeholderRegistry);
			result.add(new UnifyPair(rhsType, new ExtendsType(freshTph), PairOperator.EQUALSDOT, pair.getSubstitution(), pair.getBasePair()));
			result.add(new UnifyPair(extendedType, freshTph, PairOperator.SMALLERDOT, pair.getSubstitution(), pair.getBasePair()));
		}
		
		return Optional.of(result);
	}

	@Override
	public Optional<Set<UnifyPair>> reduceTphSup(UnifyPair pair) {
		if(pair.getPairOp() != PairOperator.SMALLERDOTWC)
			return Optional.empty();
		
		UnifyType lhsType = pair.getLhsType();
		UnifyType rhsType = pair.getRhsType();
		if(!(lhsType instanceof SuperType) || !(rhsType instanceof PlaceholderType))
			return Optional.empty();
		
		UnifyType superedType = ((SuperType)lhsType).getSuperedType();
		
		if (superedType.equals(rhsType)) return Optional.empty(); //PL 2019-02-18 eingefügt ? super a <.? a
		
		boolean isGen = superedType instanceof PlaceholderType && !((PlaceholderType) superedType).isGenerated();
		
		Set<UnifyPair> result = new HashSet<>();
		if(isGen)
			result.add(new UnifyPair(rhsType, lhsType, PairOperator.EQUALSDOT, pair.getSubstitution(), pair.getBasePair()));
		else {
			UnifyType freshTph = PlaceholderType.freshPlaceholder(placeholderRegistry);
			result.add(new UnifyPair(rhsType, new SuperType(freshTph), PairOperator.EQUALSDOT, pair.getSubstitution(), pair.getBasePair()));
			Set<UnifyType> fBounded = pair.getfBounded();
			fBounded.add(lhsType);
			result.add(new UnifyPair(freshTph, superedType, PairOperator.SMALLERDOT, pair.getSubstitution(), pair.getBasePair(), fBounded));
		}
		
		return Optional.of(result);
	}
}
