//PL 2018-12-19: Merge checken
package de.dhbwstuttgart.typeinference.unify;

import de.dhbwstuttgart.core.ConsoleInterface;
import de.dhbwstuttgart.exceptions.TypeinferenceException;
import de.dhbwstuttgart.parser.NullToken;
import de.dhbwstuttgart.server.ServerTaskLogger;
import de.dhbwstuttgart.typeinference.constraints.Constraint;
import de.dhbwstuttgart.typeinference.unify.cartesianproduct.VarianceCase;
import de.dhbwstuttgart.typeinference.unify.interfaces.IFiniteClosure;
import de.dhbwstuttgart.typeinference.unify.interfaces.IMatch;
import de.dhbwstuttgart.typeinference.unify.interfaces.IRuleSet;
import de.dhbwstuttgart.typeinference.unify.interfaces.ISetOperations;
import de.dhbwstuttgart.typeinference.unify.interfaces.IUnify;
import de.dhbwstuttgart.typeinference.unify.model.ExtendsType;
import de.dhbwstuttgart.typeinference.unify.model.FunNType;
import de.dhbwstuttgart.typeinference.unify.model.OrderingExtend;
import de.dhbwstuttgart.typeinference.unify.model.OrderingUnifyPair;
import de.dhbwstuttgart.typeinference.unify.model.PairOperator;
import de.dhbwstuttgart.typeinference.unify.model.PlaceholderType;
import de.dhbwstuttgart.typeinference.unify.model.ReferenceType;
import de.dhbwstuttgart.typeinference.unify.model.SuperType;
import de.dhbwstuttgart.typeinference.unify.model.TypeParams;
import de.dhbwstuttgart.typeinference.unify.model.Unifier;
import de.dhbwstuttgart.typeinference.unify.model.UnifyPair;
import de.dhbwstuttgart.typeinference.unify.model.UnifyType;
import de.dhbwstuttgart.typeinference.unify.model.WildcardType;
import de.dhbwstuttgart.util.Logger;
import de.dhbwstuttgart.util.Pair;
import de.dhbwstuttgart.util.Tuple;
import java.io.Serial;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RecursiveTask;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;


/**
 * Implementation of the type unification algorithm
 *
 * @author Florian Steurer
 */
public class TypeUnifyTask extends CancellableTask<CompletableFuture<Set<Set<UnifyPair>>>> {

  @Serial
  private static final long serialVersionUID = 1L;
  private static int i = 0;
  private final boolean printtag = false;

  public final UnifyContext context;

  /**
   * Element, das aus dem nextSet den Gleichunen dieses Threads hinzugefuegt wurde
   */
  Set<UnifyPair> nextSetElement;

  /**
   * Fuer die Threads
   */
  protected boolean one = false;

  public static final String rootDirectory = System.getProperty("user.dir") + "/test/logFiles/";

  /**
   * The implementation of setOps that will be used during the unification
   */
  protected ISetOperations setOps = new GuavaSetOperations();

  /**
   * The implementation of the standard unify that will be used during the unification
   */
  protected IUnify stdUnify = new MartelliMontanariUnify();

  /**
   * The implementation of the rules that will be used during the unification.
   */
  protected IRuleSet rules;

  protected Set<UnifyPair> eq; //und-constraints

  protected List<Set<Constraint<UnifyPair>>> oderConstraintsField;

  protected IFiniteClosure fc;

  public OrderingExtend<Set<UnifyPair>> oup;

  //Gives if unify is not called from checkA
  private boolean finalresult = true;

  int rekTiefeField;

  int nOfUnify = 0;

  int noUndefPair = 0;

  int noAllErasedElements = 0;

  // some statistics for local output (they will not make sense when executed on the server)
  public static int noBacktracking;
  public static int noShortendElements;
  public static int noou = 0;

  public TypeUnifyTask(UnifyContext context) {
    this.context = context.newWithLogger(Logger.NULL_LOGGER);
    rules = new RuleSet(context.placeholderRegistry());
  }

	/*
	public TypeUnifyTask(Set<UnifyPair> eq, IFiniteClosure fc, boolean parallel, FileWriter logFile, Boolean log) {
		this.eq = eq;
		this.fc = fc;
		this.oup = new OrderingUnifyPair(fc);
		this.parallel = parallel;
		this.logFile = logFile;
		this.log = log;
		rules = new RuleSet(logFile);
		noOfThread++;
		thNo = noOfThread;
	}
	*/


  public TypeUnifyTask(Set<UnifyPair> eq, List<Set<Constraint<UnifyPair>>> oderConstraints, IFiniteClosure fc, UnifyContext context, int rekTiefe) {
    this.eq = eq;
    //this.oderConstraints = oderConstraints.stream().map(x -> x.stream().map(y -> new HashSet<>(y)).collect(Collectors.toSet(HashSet::new))).collect(Collectors.toList(ArrayList::new));
    this.oderConstraintsField = oderConstraints; /*.stream().map(x -> {
    Set<Set<UnifyPair>> ret = new HashSet<>();
    for (Constraint<UnifyPair> y : x) {
      ret.add(new HashSet<>(y));
    }
    return ret;
    }).collect(Collectors.toCollection(ArrayList::new));
     */

    //x.stream().map(y -> new HashSet<>(y)).collect(Collectors.toSet(HashSet::new))).collect(Collectors.toList(ArrayList::new));
    this.fc = fc;
    this.oup = new OrderingUnifyPair(fc, context);

    if (!ConsoleInterface.writeLogFiles || context.logger() instanceof ServerTaskLogger) this.context = context;
    else {
      var baseFolder = Path.of(System.getProperty("user.dir"), "logFiles");
      this.context = context.newWithLogger(
        Logger.forFile(
          baseFolder.resolve("Thread_" + Thread.currentThread().hashCode() + ".txt").toString(), "Unify"
        )
      );
    }

    /*Abbruchtest
    if (thNo > 10) {
      context.logger().info("cancel");
      usedTasks.cancel();
      writeLog(nOfUnify.toString() + "cancel");
      context.logger().info("cancel");
      try {
        logFile.write("Abbruch");
      }
      catch (IOException e) {
        System.err.println("log-File nicht vorhanden");
      }
    }
    */
    rules = new RuleSet(context.logger(), context.placeholderRegistry());
    this.rekTiefeField = rekTiefe;
    context.usedTasks().add(this);
  }

  /*
   * Vererbt alle Variancen
   * @param eq The set of constraints
   */
	/* PL 2018-05- 17 verschoben nach JavaTXCompiler
	private void varianceInheritance(Set<UnifyPair> eq) {
		Set<PlaceholderType> usedTPH = new HashSet<>();
		Set<PlaceholderType> phSet = eq.stream().map(x -> { 
			Set<PlaceholderType> pair = new HashSet<>();
			if (x.getLhsType() instanceof PlaceholderType) pair.add((PlaceholderType)x.getLhsType());
			if (x.getRhsType() instanceof PlaceholderType) pair.add((PlaceholderType)x.getRhsType());
			return pair;
		}).reduce(new HashSet<>(), (a,b) -> { a.addAll(b); return a;} , (c,d) -> { c.addAll(d); return c;});
		
		ArrayList<PlaceholderType> phSetVariance =  new ArrayList<>(phSet);
		phSetVariance.removeIf(x -> (x.getVariance() == 0));
		while(!phSetVariance.isEmpty()) {
			PlaceholderType a = phSetVariance.remove(0);
			usedTPH.add(a);
			//HashMap<PlaceholderType,Integer> ht = new HashMap<>();
			//ht.put(a, a.getVariance());
			Set<UnifyPair> eq1 = new HashSet<>(eq); 
			eq1.removeIf(x -> !(x.getLhsType() instanceof PlaceholderType && ((PlaceholderType)x.getLhsType()).equals(a)));
			eq1.stream().forEach(x -> { x.getRhsType().accept(new distributeVariance(), a.getVariance());});
			eq1 = new HashSet<>(eq); 
			eq1.removeIf(x -> !(x.getRhsType() instanceof PlaceholderType && ((PlaceholderType)x.getRhsType()).equals(a)));
			eq1.stream().forEach(x -> { x.getLhsType().accept(new distributeVariance(), a.getVariance());});
			phSetVariance =  new ArrayList<>(phSet);
			phSetVariance.removeIf(x -> (x.getVariance() == 0 || usedTPH.contains(x)));
		}		
}
*/

  public CompletableFuture<Set<Set<UnifyPair>>> compute() {
    if (one) {
      context.logger().info("two");
    }
    one = true;
    Set<UnifyPair> neweq = new HashSet<>(eq);
    /* 1-elementige Oder-Constraints werden in und-Constraints umgewandelt */
    Set<UnifyPair> methodSignatureConstraint = new HashSet<>();
    oderConstraintsField.stream()
      .filter(x -> x.size() == 1)
      .map(y -> y.stream().findFirst().get()).forEach(x -> {
        neweq.addAll(x);
        methodSignatureConstraint.addAll(x.getmethodSignatureConstraint());
      });
    ArrayList<Set<Constraint<UnifyPair>>> remainingOderconstraints = oderConstraintsField.stream()
      .filter(x -> x.size() > 1)
      .collect(Collectors.toCollection(ArrayList::new));
    var unifyFuture = unify(neweq, remainingOderconstraints, fc, context.parallel(), rekTiefeField, methodSignatureConstraint);
    return unifyFuture.thenApply(res -> {
      context.logger().close();
      if (isUndefinedPairSetSet(res)) {
        //fuer debug-Zwecke
        ArrayList<ArrayList<UnifyPair>> al = res.stream()
          .map(ArrayList::new)
          .collect(Collectors.toCollection(ArrayList::new));
        throw new TypeinferenceException("Unresolved constraints: " + res, new NullToken()); //return new HashSet<>();
      }

      if (this.isExecutionCancelled()) {
        return new HashSet<>();
      }

      return res;
    });
  }

/*	
	@Override
	protected Set<Set<UnifyPair>> compute() {
		Set<Set<UnifyPair>> fstElems = new HashSet<>();
		fstElems.add(eq);
		Set<Set<UnifyPair>> res = computeCartesianRecursiveOderConstraints(fstElems, oderConstraints, fc, parallel);
		if (isUndefinedPairSetSet(res)) { return new HashSet<>(); }
		else return res;
	}
*/


  /**
   * Computes all principal type unifiers for a set of constraints.
   *
   * @param eq The set of constraints
   * @param fc The finite closure
   * @return The set of all principal type unifiers
   */
  protected CompletableFuture<Set<Set<UnifyPair>>> unify(final Set<UnifyPair> eq, List<Set<Constraint<UnifyPair>>> oderConstraints, IFiniteClosure fc, boolean parallel, int rekTiefe, Set<UnifyPair> methodSignatureConstraint) {
    //Set<UnifyPair> aas = eq.stream().filter(x -> x.getLhsType().getName().equals("AA") //&& x.getPairOp().equals(PairOperator.SMALLERDOT)
    //		).collect(Collectors.toCollection(HashSet::new));
    //writeLog(nOfUnify.toString() + " AA: " + aas.toString());
    //if (aas.isEmpty()) {
    //	context.logger().info("");
    //}

    //.collect(Collectors.toCollection(HashSet::new)));

    if (this.isExecutionCancelled()) {
      return CompletableFuture.completedFuture(new HashSet<>());
    }

    rekTiefe++;
    nOfUnify++;
    context.logger().debug(() -> nOfUnify + " Unifikation: " + eq.toString());
    context.logger().debug(() -> nOfUnify + " Oderconstraints: " + oderConstraints.toString());

    /*
     * Variancen auf alle Gleichungen vererben
     */
    //PL 2018-05-17 nach JavaTXCompiler verschoben
    //varianceInheritance(eq);

    /*
     * ? extends ? extends Theta rausfiltern
     */
    Set<UnifyPair> doubleExt = eq.stream().filter(UnifyPair::wrongWildcard).peek(UnifyPair::setUndefinedPair)
      .collect(Collectors.toCollection(HashSet::new));
    if (!doubleExt.isEmpty()) {
      Set<Set<UnifyPair>> ret = new HashSet<>();
      ret.add(doubleExt);
      return CompletableFuture.completedFuture(ret);
    }

    /*
     * Occurs-Check durchfuehren
     */
    Set<UnifyPair> ocurrPairs = TypeUnifyTaskHelper.occursCheck(eq);

    Set<UnifyPair> finalOcurrPairs = ocurrPairs;
    context.logger().debug(() -> "ocurrPairs: " + finalOcurrPairs);
    if (!ocurrPairs.isEmpty()) {
      Set<Set<UnifyPair>> ret = new HashSet<>();
      ret.add(ocurrPairs);
      return CompletableFuture.completedFuture(ret);
    }



    /*
     * Step 1: Repeated application of reduce, adapt, erase, swap
     */
    Set<UnifyPair> eq0;
    Set<UnifyPair> eq0Prime;
    Optional<Set<UnifyPair>> eqSubst = Optional.of(eq);
    do {
      eq0Prime = eqSubst.get();
      eq0 = applyTypeUnificationRules(eq0Prime, fc);
      /* In commit dfd91b5f8b7fca1cb5f302eec4b0ba3330271c9b eingefuegt ANFANG */
      Set<UnifyPair> occurcheck = new HashSet<>(eq0);
      occurcheck.removeAll(eq0Prime);
      ocurrPairs = TypeUnifyTaskHelper.occursCheck(occurcheck);
      Set<UnifyPair> finalOcurrPairs1 = ocurrPairs;
      context.logger().debug(() -> "ocurrPairs: " + finalOcurrPairs1);
      if (!ocurrPairs.isEmpty()) {
        Set<Set<UnifyPair>> ret = new HashSet<>();
        ret.add(ocurrPairs);
        return CompletableFuture.completedFuture(ret);
      }
      /* In commit dfd91b5f8b7fca1cb5f302eec4b0ba3330271c9b eingefuegt ENDE */
      eqSubst = rules.subst(eq0, oderConstraints);
    } while (eqSubst.isPresent());

    eq0.forEach(UnifyPair::disableCondWildcards);

    context.logger().debug(() ->nOfUnify + " Unifikation nach applyTypeUnificationRules: " + eq.toString());
    context.logger().debug(() -> nOfUnify + " Oderconstraints nach applyTypeUnificationRules: " + oderConstraints.toString());

    /*
     * Step 2 and 3: Create a subset eq1s of pairs where both sides are TPH and eq2s of the other pairs
     */
    Set<UnifyPair> eq1s = new HashSet<>();
    Set<UnifyPair> eq2s = new HashSet<>();
    splitEq(eq0, eq1s, eq2s);

    /*
     * Step 4: Create possible typings
     *
     * "Manche Autoren identifizieren die Paare (a, (b,c)) und ((a,b),c)
     * mit dem geordneten Tripel (a,b,c), wodurch das kartesische Produkt auch assoziativ wird." - Wikipedia
     */

    // There are up to 10 toplevel set. 8 of 10 are the result of the
    // cartesian product of the sets created by pattern matching.
    List<Set<? extends Set<UnifyPair>>> topLevelSets = new ArrayList<>();

    //context.logger().info(eq2s);

    if (!eq1s.isEmpty()) { // Do not add empty sets or the cartesian product will always be empty.
      Set<Set<UnifyPair>> wrap = new HashSet<>();
      wrap.add(eq1s);
      topLevelSets.add(wrap); // Add Eq1'
    }

    // Add the set of [a =. Theta | (a=. Theta) in Eq2']
    //TODO: Occurscheck anwenden als Fehler identifizieren
    Set<UnifyPair> bufferSet = eq2s.stream()
      .filter(x -> x.getPairOp() == PairOperator.EQUALSDOT && x.getLhsType() instanceof PlaceholderType)
      .collect(Collectors.toSet());

    if (!bufferSet.isEmpty()) { // Do not add empty sets or the cartesian product will always be empty.
      Set<Set<UnifyPair>> wrap = new HashSet<>();
      wrap.add(bufferSet);
      topLevelSets.add(wrap);
      eq2s.removeAll(bufferSet);
    }

    // Sets that originate from pair pattern matching
    // Sets of the "second level"
    Set<UnifyPair> undefinedPairs = new HashSet<>();
    if (printtag) context.logger().info("eq2s " + eq2s);
    //writeLog("BufferSet: " + bufferSet.toString()+"\n");
    List<Set<Constraint<UnifyPair>>> oderConstraintsOutput = new ArrayList<>();//new ArrayList<>(oderConstraints);
    Set<Set<Set<? extends Set<UnifyPair>>>> secondLevelSets = calculatePairSets(eq2s, oderConstraints, fc, undefinedPairs, oderConstraintsOutput);
    //PL 2017-09-20: Im calculatePairSets wird möglicherweise O .< java.lang.Integer
    //nicht ausgewertet Faculty Beispiel im 1. Schritt
    //PL 2017-10-03 geloest, muesste noch mit FCs mit kleineren
    //Typen getestet werden.
    context.logger().debug(() -> nOfUnify + " Oderconstraints2: " + oderConstraintsOutput.toString());
    if (printtag) context.logger().info("secondLevelSets:" + secondLevelSets);
    // If pairs occured that did not match one of the cartesian product cases,
    // those pairs are contradictory and the unification is impossible.
    if (!undefinedPairs.isEmpty()) {
      noUndefPair++;
      for (UnifyPair up : undefinedPairs) {
        context.logger().debug(() -> noUndefPair + " UndefinedPairs; " + up);
        context.logger().debug(() -> "BasePair; " + up.getBasePair());
      }
      Set<Set<UnifyPair>> error = new HashSet<>();
      undefinedPairs = undefinedPairs.stream().peek(UnifyPair::setUndefinedPair)
        .collect(Collectors.toCollection(HashSet::new));
      error.add(undefinedPairs);
      undefinedPairs.forEach(x -> context.logger().debug(() -> "AllSubst: " + x.getAllSubstitutions().toString()));
      return CompletableFuture.completedFuture(error);
    }

    /* Up to here, no cartesian products are calculated.
     * filters for pairs and sets can be applied here */

    // Alternative: Sub cartesian products of the second level (pattern matched) sets
    // "the big (x)"
		/* for(Set<Set<Set<UnifyPair>>> secondLevelSet : secondLevelSets) {
			//context.logger().info("secondLevelSet "+secondLevelSet.size());
			List<Set<Set<UnifyPair>>> secondLevelSetList = new ArrayList<>(secondLevelSet);
			Set<List<Set<UnifyPair>>> cartResult = setOps.cartesianProduct(secondLevelSetList);
			//context.logger().info("CardResult: "+cartResult.size());
			// Flatten and add to top level sets
			Set<Set<UnifyPair>> flat = new HashSet<>();
			int j = 0;
			for(List<Set<UnifyPair>> s : cartResult) {
				j++;
				//context.logger().info("s from CardResult: "+cartResult.size() + " " + j);
				Set<UnifyPair> flat1 = new HashSet<>();
				for(Set<UnifyPair> s1 : s)
					flat1.addAll(s1);
				flat.add(flat1);
			}
			//topLevelSets.add(flat);
		}
		*/

    //Alternative KEIN KARTESISCHES PRODUKT der secondlevel Ebene bilden
    for (Set<Set<? extends Set<UnifyPair>>> secondLevelSet : secondLevelSets) {
      topLevelSets.addAll(secondLevelSet);
    }
    //context.logger().info(topLevelSets);
    //context.logger().info();


    //Aufruf von computeCartesianRecursive ANFANG
    //writeLog("topLevelSets: " + topLevelSets.toString());
    return computeCartesianRecursive(new ArrayList<>(topLevelSets), eq, oderConstraintsOutput, fc, parallel, rekTiefe, methodSignatureConstraint);
  }


  public CompletableFuture<Set<Set<UnifyPair>>> unify2(Set<Set<UnifyPair>> setToFlatten, Set<UnifyPair> eq, List<Set<Constraint<UnifyPair>>> oderConstraints, IFiniteClosure fc, boolean parallel, int rekTiefe, Set<UnifyPair> methodSignatureConstraint) {
    //Aufruf von computeCartesianRecursive ENDE

    //keine Ahnung woher das kommt
    //Set<Set<UnifyPair>> setToFlatten = topLevelSets.stream().map(x -> x.iterator().next()).collect(Collectors.toCollection(HashSet::new));

    //Muss auskommentiert werden, wenn computeCartesianRecursive ANFANG
    // Cartesian product over all (up to 10) top level sets
    //Set<Set<Set<UnifyPair>>> eqPrimeSet = setOps.cartesianProduct(topLevelSets)
    //		.stream().map(x -> new HashSet<>(x))
    //		.collect(Collectors.toCollection(HashSet::new));
    //Muss auskommentiert werden, wenn computeCartesianRecursive ENDE

    if (this.isExecutionCancelled()) {
      return CompletableFuture.completedFuture(new HashSet<>());
    }

    CompletableFuture<Set<Set<UnifyPair>>> eqPrimePrimeSetFuture = CompletableFuture.completedFuture(new HashSet<>());

    Set<TypeUnifyTask> forks = new HashSet<>();

    //Muss auskommentiert werden, wenn computeCartesianRecursive ANFANG
    //for(Set<Set<UnifyPair>> setToFlatten : eqPrimeSet) {
    // Flatten the cartesian product
    //Muss auskommentiert werden, wenn computeCartesianRecursive ENDE
    Set<UnifyPair> eqPrime = new HashSet<>();
    setToFlatten.forEach(eqPrime::addAll);

    /*
     * Step 5: Substitution
     */
    //writeLog("vor Subst: " + eqPrime);
    context.logger().debug(() -> "vor Subst: " + oderConstraints);
    String ocString = oderConstraints.toString();
    List<Set<Constraint<UnifyPair>>> newOderConstraints = new ArrayList<>(oderConstraints);
    Optional<Set<UnifyPair>> eqPrimePrime = rules.subst(eqPrime, newOderConstraints);
    Set<Set<UnifyPair>> unifyres1 = null;
    Set<Set<UnifyPair>> unifyres2 = null;
    if (!ocString.equals(newOderConstraints.toString()))
      context.logger().debug(() -> "nach Subst: " + newOderConstraints);


    {// sequentiell (Step 6b is included)
      if (printtag) context.logger().info("nextStep: " + eqPrimePrime);
      if (eqPrime.equals(eq) && eqPrimePrime.isEmpty()
        && oderConstraints.isEmpty()) {                    //PL 2017-09-29 //(!eqPrimePrime.isPresent()) auskommentiert und durch
        //PL 2017-09-29 dies ersetzt //(!eqPrimePrime.isPresent())
        //PL 2018-05-18 beide Bedingungen muessen gelten, da eqPrime Veränderungen in allem ausser subst
        //eqPrimePrime Veraenderungen in subst repraesentieren.
        //try {
        //if (isSolvedForm(eqPrime)) {
        //	writeLog("eqPrime:" + eqPrime.toString()+"\n");
        //}
        //}
        //catch (IOException e) {
        //	System.err.println("log-File nicht vorhanden");
        //}
        eqPrimePrimeSetFuture = eqPrimePrimeSetFuture.thenApply(eqPrimePrimeSet -> {
          eqPrimePrimeSet.add(eqPrime);
          return eqPrimePrimeSet;
        });
        if (finalresult && isSolvedForm(eqPrime)) {
          context.logger().debug(() -> "eqPrime:" + eqPrime.toString() + "\n");

          /* methodconstraintsets werden zum Ergebnis hinzugefuegt
           * Anfang
           */
          //context.logger().info("methodSignatureConstraint Return: " + methodSignatureConstraint + "\n");
          eqPrimePrimeSetFuture = eqPrimePrimeSetFuture.thenApply(eqPrimePrimeSet -> {
            eqPrimePrimeSet.forEach(x -> x.addAll(methodSignatureConstraint));

            context.resultModel().notify(eqPrimePrimeSet, context);
            return eqPrimePrimeSet;
          });

          //Substitutionen in methodcontraintsets werdne ausgeführt
						/* PL auskommentiert 2024-05-02
						eqPrimePrimeSet = eqPrimePrimeSet.stream().map(
									x -> { Optional<Set<UnifyPair>> help = rules.subst(x); 
										   return help.isPresent() ? 
												   help.get(): 
													   x; }).collect(Collectors.toSet());
						*/
        }
      } else if (eqPrimePrime.isPresent()) {
        CompletableFuture<Set<Set<UnifyPair>>> unifyResFuture = unify(eqPrimePrime.get(), newOderConstraints, fc, parallel, rekTiefe, methodSignatureConstraint);
        eqPrimePrimeSetFuture = eqPrimePrimeSetFuture.thenCombine(unifyResFuture, (eqPrimePrimeSet, unifyRes) -> {
          eqPrimePrimeSet.addAll(unifyRes);
          return eqPrimePrimeSet;
        });
      } else {
        CompletableFuture<Set<Set<UnifyPair>>> unifyResFuture = unify(eqPrime, newOderConstraints, fc, parallel, rekTiefe, methodSignatureConstraint);
        eqPrimePrimeSetFuture = eqPrimePrimeSetFuture.thenCombine(unifyResFuture, (eqPrimePrimeSet, unifyRes) -> {
          eqPrimePrimeSet.addAll(unifyRes);
          return eqPrimePrimeSet;
        });
      }
    }
    //Muss auskommentiert werden, wenn computeCartesianRecursive ANFANG
    //}
    //Muss auskommentiert werden, wenn computeCartesianRecursive ENDE

    /*
     * 	Step 6 b) Build the union over everything.
     */
		/*
		 * PL 2019-01-22: geloescht
		
		if(parallel)
			for(TypeUnifyTask fork : forks) 
				eqPrimePrimeSet.addAll(fork.join());
		*/
    /*
     * Step 7: Filter empty sets;
     */
    return eqPrimePrimeSetFuture.thenApply(eqPrimePrimeSet -> {
      eqPrimePrimeSet = eqPrimePrimeSet.stream().filter(x -> isSolvedForm(x) || this.isUndefinedPairSet(x)).collect(Collectors.toCollection(HashSet::new));
      if (!eqPrimePrimeSet.isEmpty() && !isUndefinedPairSetSet(eqPrimePrimeSet)) {
        Set<Set<UnifyPair>> finalEqPrimePrimeSet = eqPrimePrimeSet;
        context.logger().debug(() -> "Result1 " + finalEqPrimePrimeSet);
      }
      return eqPrimePrimeSet;
    });
  }


  /**
   * Computes the cartesian product of topLevelSets step by step.
   *
   * @param topLevelSets    List of Sets of Sets, where a cartesian product have to be built
   *                        Ex.: [{{a =. Integer}, {a = Object}}, {{a = Vector<b>, b =. Integer}, {a = Vector<b>, b =. Object}}]
   * @param eq              Original set of equations which should be unified
   * @param oderConstraints Remaining or-constraints
   * @param fc              The finite closure
   * @param parallel        If the algorithm should be parallelized run
   * @param rekTiefe        Deep of recursive calls
   * @return The set of all principal type unifiers
   */
  CompletableFuture<Set<Set<UnifyPair>>> computeCartesianRecursive(ArrayList<Set<? extends Set<UnifyPair>>> topLevelSets, Set<UnifyPair> eq, List<Set<Constraint<UnifyPair>>> oderConstraints, IFiniteClosure fc, boolean parallel, int rekTiefe, Set<UnifyPair> methodSignatureConstraint) {

    Set<Set<UnifyPair>> singleElementSets = TypeUnifyTaskHelper.getSingleElementSets(topLevelSets);
    singleElementSets.forEach(x -> {
      if (x instanceof Constraint)
        methodSignatureConstraint.addAll(((Constraint<UnifyPair>) x).getmethodSignatureConstraint());
    });

    /*
     * @var optionalAnyMultiElementSet Eine mehrelementige Menge, wenn vorhanden
     */
    Optional<Set<? extends Set<UnifyPair>>> optionalAnyMultiElementSet = topLevelSets.stream().filter(x -> x.size() > 1).findAny();
    if (optionalAnyMultiElementSet.isEmpty()) {
      // Alle Elemente sind 1-elementig
      return unify2(singleElementSets, eq, oderConstraints, fc, parallel, rekTiefe, methodSignatureConstraint);
    }

    /*
     *	@var nextSet is any set with multiple elements
     */
    Set<? extends Set<UnifyPair>> nextSet = optionalAnyMultiElementSet.get();
    //writeLog("nextSet: " + nextSet.toString());
    List<Set<UnifyPair>> nextSetAsList = new ArrayList<>(nextSet);
		/*
		try {
		//List<Set<UnifyPair>>
			//nextSetasList = oup.sortedCopy(nextSet);//new ArrayList<>(nextSet);
		}
		catch (java.lang.IllegalArgumentException e) {
			System.out.print("");
		}
		*/

    // check if all elements of nextSet have the same base
    boolean hasSameBase = TypeUnifyTaskHelper.doesFirstNextSetHasSameBase(nextSetAsList);


    /*
     * oderConstraint = true => entweder existiert kein Basepair oder unterschiedliche Basepairs
     */
    boolean oderConstraint = !hasSameBase;

    /* Varianzbestimmung
     * Varianz = 1 => Argumentvariable
     * Varianz = -1 => Rückgabevariable
     * Varianz = 0 => unklar
     * Varianz = 2 => Operatoren oderConstraints */
    int variance;
    if (!oderConstraint) {
      variance = TypeUnifyTaskHelper.calculateVariance(nextSetAsList);
    } else {
      //Varianz-Bestimmung Oder-Constraints
      if (printtag) {
        context.logger().info("nextSetasList " + nextSetAsList);
      }
      variance = TypeUnifyTaskHelper.calculateOderConstraintVariance(nextSetAsList);
    }


    //writeLog("nextSetasList: " + nextSetasList.toString());
    Set<UnifyPair> nextSetElement = nextSetAsList.getFirst();
    //writeLog("BasePair1: " + nextSetElem + " " + nextSetElem.iterator().next().getBasePair());


    /* sameEqSet-Bestimmung: Wenn a = ty \in  nextSet dann enthaelt sameEqSet
     * alle Paare a < ty1 oder ty2 < a aus oneElems
     */
    Set<UnifyPair> sameEqSet = new HashSet<>();
    //optOrigPair enthaelt ggf. das Paar a = ty \in  nextSet
    Optional<UnifyPair> optOrigPair;
    if (!oderConstraint) {
      optOrigPair = TypeUnifyTaskHelper.findEqualityConstrainedUnifyPair(nextSetElement);
      context.logger().debug(() -> "optOrigPair: " + optOrigPair);

      if (optOrigPair.isPresent()) {
        sameEqSet = TypeUnifyTaskHelper.findConstraintsWithSameTVAssociation(optOrigPair.get(), singleElementSets);
      }
    } else {
      optOrigPair = Optional.empty();
    }


    CompletableFuture<Set<Set<UnifyPair>>> resultFuture =
      this.innerCartesianLoop(variance, rekTiefe, oderConstraint, parallel, new HashSet<>(), null, nextSet,
        nextSetAsList, optOrigPair, methodSignatureConstraint, singleElementSets, sameEqSet, oderConstraints);

    return resultFuture.thenApply(result -> {
      //2020-02-02: if (variance ==2) Hier Aufruf von filterOverriding einfuegen
      context.logger().debug(() ->"Return computeCR: " + result.toString());
      return result;
    });
  }

  private CompletableFuture<Set<Set<UnifyPair>>> innerCartesianLoop(
    int variance,
    int rekTiefe,
    boolean oderConstraint,
    boolean parallel,
    final Set<Set<UnifyPair>> prevResult,
    Set<UnifyPair> a_last,
    Set<? extends Set<UnifyPair>> nextSet,
    final List<Set<UnifyPair>> prevNextSetAsList,
    Optional<UnifyPair> optOrigPair,
    Set<UnifyPair> methodSignatureConstraint,
    Set<Set<UnifyPair>> singleElementSets,
    Set<UnifyPair> sameEqSet,
    List<Set<Constraint<UnifyPair>>> oderConstraints
  ) {
    if (prevNextSetAsList.isEmpty()) {
      return CompletableFuture.completedFuture(prevResult);
    }


    VarianceCase varianceCase = VarianceCase.createFromVariance(variance, oderConstraint, this, context);

    context.logger().debug(() -> "nextSet: " + nextSet.toString());
    context.logger().debug(() -> "nextSetasList: " + prevNextSetAsList.toString());

    varianceCase.selectNextData(this, prevNextSetAsList, optOrigPair);

    if (oderConstraint) {//Methodconstraints werden abgespeichert für die Bytecodegenerierung von Methodenaufrufen
      methodSignatureConstraint.addAll(((Constraint<UnifyPair>) varianceCase.a).getmethodSignatureConstraint());
      context.logger().debug(() -> "ERSTELLUNG methodSignatureConstraint: " + methodSignatureConstraint);
      //context.logger().info("ERSTELLUNG methodSignatureConstraint: " +noOfThread+" "+methodSignatureConstraint);
      //context.logger().info("a: " +a);
      //context.logger().info("eq: " +eq);
      //context.logger().info();
    }

    i++;
    Set<Set<UnifyPair>> elems = new HashSet<>(singleElementSets);
    context.logger().debug(() -> "a1: " + rekTiefe + "  " + "variance:  " + variance + " " + varianceCase.a.toString() + "\n");

    Set<Set<UnifyPair>> aParDef = new HashSet<>();

    /* Wenn bei (a \in theta) \in a zu Widerspruch in oneElems wird
     * a verworfen und zu nächstem Element von nextSetasList gegangen
     */
    if (!oderConstraint && !sameEqSet.isEmpty() && !checkNoContradiction(varianceCase.a, sameEqSet, prevResult)) {
      noShortendElements++;
      // continue
      return this.innerCartesianLoop(variance, rekTiefe, false, parallel, prevResult, null, nextSet,
        prevNextSetAsList, optOrigPair, methodSignatureConstraint, singleElementSets, sameEqSet, oderConstraints);
    }

    /* Wenn parallel gearbeitet wird, wird je nach Varianz ein neuer Thread
     * gestartet, der parallel weiterarbeitet.
     */

    // Ergebnisvariable für die parallele Verabeitung: Tupel aus
    // - forkOrig result : currentThreadResult (frueher "res")
    // - fork results : forkResults (frueher "add_res")
    CompletableFuture<VarianceCase.ComputationResults> parallelResultDataFuture;


    if (parallel) {
      parallelResultDataFuture = varianceCase.computeParallel(
        elems, eq, oderConstraints, fc, rekTiefe, methodSignatureConstraint,
        prevNextSetAsList, sameEqSet, prevResult, aParDef
      );
    } else {
      // same as variance = 0
      elems.add(varianceCase.a); //PL 2019-01-16 muss das wirklich hin steht schon in Zeile 859 ja braucht man siehe Zeile 859
      parallelResultDataFuture =  this.unify2(elems, eq, oderConstraints, fc, false, rekTiefe, new HashSet<>(methodSignatureConstraint))
        .thenApply(VarianceCase.ComputationResults::new);
    }

    if (this.isExecutionCancelled()) {
      return CompletableFuture.completedFuture(new HashSet<>());
    }

    return parallelResultDataFuture.thenCompose(parallelResultData -> {
      Set<Set<UnifyPair>> currentThreadResult = parallelResultData.mainResult;
      Set<Set<Set<UnifyPair>>> forkResults = parallelResultData.forkResults;

      Set<Set<UnifyPair>> result = prevResult;
      List<Set<UnifyPair>> nextSetAsList = prevNextSetAsList;


      //Ab hier alle parallele Berechnungen wieder zusammengeführt.
      if (oderConstraint) {//Wenn weiteres Element nextSetasList genommen wird, muss die vorherige methodsignatur geloescht werden
        methodSignatureConstraint.removeAll(((Constraint<UnifyPair>) varianceCase.a).getmethodSignatureConstraint());
        //context.logger().info("REMOVE: " +methodSignatureConstraint);
      }
      if (!isUndefinedPairSetSet(currentThreadResult) && isUndefinedPairSetSet(result)) {
        //wenn korrektes Ergebnis gefunden alle Fehlerfaelle loeschen
        result = currentThreadResult;
      } else {
        if ((isUndefinedPairSetSet(currentThreadResult) && isUndefinedPairSetSet(result))
          || (!isUndefinedPairSetSet(currentThreadResult) && !isUndefinedPairSetSet(result))
          || result.isEmpty()) {

          if ((!result.isEmpty() && !currentThreadResult.isEmpty() && !isUndefinedPairSetSet(currentThreadResult) && !isUndefinedPairSetSet(result)) //korrekte Loesungen aus und-constraints
            && (varianceCase.a.stream().map(x -> (x.getBasePair() != null)).reduce(true, (x, y) -> (x && y)))) //bei oder-Constraints nicht ausfuehren
          {
            //TODO: PL 2019-01-15: Bug 129: Im Moment wird nur das Maximum und das Minimum des aktuellen Elements betrachtet.
            //Die zu vereinigenden Mengen können mehrere Elemente enthalten. Das ist bisher nicht berücksichtigt

            //Alle Variablen bestimmen die nicht hinzugefügt wurden in a
            //PL 2018-12-28: Hier gab es eine ClassCastException, war nicht reproduzierbar
            // context.logger().info("");
            List<PlaceholderType> vars_a = TypeUnifyTaskHelper.extractMatchingPlaceholderTypes(varianceCase.a);
            Set<UnifyPair> fstElemRes = currentThreadResult.iterator().next();
            Set<UnifyPair> compRes = fstElemRes.stream().filter(x -> vars_a.contains(((PlaceholderType) x.getLhsType()))).collect(Collectors.toCollection(HashSet::new));

            //Alle Variablen bestimmen die nicht hinzugefügt wurden in a_last
            //context.logger().info(a_last);

            try {//PL eingefuegt 2019-03-06 da bei map mmer wieder Nullpointer kamen
              a_last.forEach(x -> context.logger().debug(() -> "a_last_elem:" + x + " basepair: " + x.getBasePair()));//PL 2019-05-13 ins try hinzugefuegt Nullpointer-Exception ist in der Zeile aufgetaucht.
              List<PlaceholderType> varsLast_a = TypeUnifyTaskHelper.extractMatchingPlaceholderTypes(a_last);
              //[(java.util.Vector<java.lang.Integer> <. gen_aq, , 1), (CEK =. ? extends gen_aq, 1)] KANN VORKOMMEN
              //erstes Element genügt, da vars immer auf die gleichen Elemente zugeordnet werden muessen
              Set<UnifyPair> fstElemResult = result.iterator().next();
              Set<UnifyPair> compResult = fstElemResult.stream().filter(x -> varsLast_a.contains(((PlaceholderType) x.getLhsType()))).collect(Collectors.toCollection(HashSet::new));

              varianceCase.applyComputedResults(result, currentThreadResult, compResult, compRes);
            } catch (NullPointerException e) {
              context.logger().debug(() -> "NullPointerException: " + a_last.toString());
            }
          } else {
            //alle Fehlerfaelle und alle korrekten Ergebnis jeweils adden
            Set<Set<UnifyPair>> finalResult = result;
            context.logger().debug(() -> "RES Fst: result: " + finalResult.toString() + " currentThreadResult: " + currentThreadResult.toString());
            result.addAll(currentThreadResult);
          }
        }
        //else {
        //wenn Korrekte Ergebnisse da und Feherfälle dazukommen Fehlerfälle ignorieren
        //	if (isUndefinedPairSetSet(currentThreadResult) && !isUndefinedPairSetSet(result)) {
        //		result = result;
        //	}
        //}
      }

      if (parallel) {
        for (Set<Set<UnifyPair>> par_res : forkResults) {
          if (!isUndefinedPairSetSet(par_res) && isUndefinedPairSetSet(result)) {
            //wenn korrektes Ergebnis gefunden alle Fehlerfaelle loeschen
            result = par_res;
            if (!par_res.isEmpty() && par_res.iterator().next() instanceof WildcardType) {
              // context.logger().info();
            }
          } else {
            if ((isUndefinedPairSetSet(par_res) && isUndefinedPairSetSet(result))
              || (!isUndefinedPairSetSet(par_res) && !isUndefinedPairSetSet(result))
              || result.isEmpty()) {
              //alle Fehlerfaelle und alle korrekten Ergebnis jeweils adden
              Set<Set<UnifyPair>> finalResult1 = result;
              context.logger().debug(() ->"RES var1 ADD:" + finalResult1.toString() + " " + par_res.toString());
              result.addAll(par_res);
            }
          }
        }
        //break;
      }

      /* auskommentiert um alle Max und min Betrachtung auszuschalten ANFANG */
      if (!result.isEmpty() && (!isUndefinedPairSetSet(currentThreadResult) || !aParDef.isEmpty())) {
        /*
        if (nextSetAsList.iterator().hasNext()
          && nextSetAsList.getFirst().stream().anyMatch(x -> x.getLhsType().getName().equals("B"))
          && nextSetAsList.size() > 1) {
          // System.out.print("");
        }
         */

        // Iterator<Set<UnifyPair>> nextSetasListIt = new ArrayList<>(nextSetAsList).iterator();
        boolean shouldBreak = varianceCase.eraseInvalidSets(rekTiefe, aParDef, nextSetAsList);
        if (shouldBreak) {
          // this.cancelYoungerSiblingTasks();
          return CompletableFuture.completedFuture(result);
        }

        context.logger().debug(() -> "a: " + rekTiefe + " variance: " + variance + varianceCase.a.toString());
      }
      /* auskommentiert um alle Max und min Betrachtung auszuschalten ENDE */

      if (isUndefinedPairSetSet(currentThreadResult) && aParDef.isEmpty()) {
        Set<UnifyPair> abhSubst = TypeUnifyTaskHelper.collectFromThreadResult(currentThreadResult, UnifyPair::getAllSubstitutions);
        abhSubst.addAll(
          TypeUnifyTaskHelper.collectFromThreadResult(currentThreadResult, UnifyPair::getThisAndAllBases)
        );

        Set<UnifyPair> durchschnitt = abhSubst.stream()
          .filter(varianceCase.a::contains)
          //.filter(y -> abhSubst.contains(y))
          .collect(Collectors.toCollection(HashSet::new));

        //Set<PlaceholderType> vars = durchschnitt.stream().map(x -> (PlaceholderType)x.getLhsType()).collect(Collectors.toCollection(HashSet::new));
        int len = nextSetAsList.size();
        /*
        Set<UnifyPair> undefRes = currentThreadResult.stream().reduce((y, z) -> {
          y.addAll(z);
          return y;
        }).get(); //flatten aller undef results

        Set<Pair<Set<UnifyPair>, UnifyPair>> reducedUndefResSubstGroundedBasePair = undefRes.stream()
          .map(x -> {
            Set<UnifyPair> su = x.getAllSubstitutions(); //alle benutzten Substitutionen
            su.add(x.getGroundBasePair());  // urspruengliches Paar
            su.removeAll(durchschnitt); //alle aktuell genänderten Paare entfernen
            return new Pair<>(su, x.getGroundBasePair());
          })
          .collect(Collectors.toCollection(HashSet::new));
        */

        if (currentThreadResult.size() > 1) {
          // context.logger().info();
        }
        List<Set<UnifyPair>> finalNextSetAsList = nextSetAsList;
        context.logger().debug(() -> "nextSetasList vor filter-Aufruf: " + finalNextSetAsList);
        if (!oderConstraint) {//PL 2023-02-08 eingefuegt: Bei oderconstraints sind Subststitutionen nicht als Substitutionen in idesem Sinne zu sehen
          nextSetAsList = nextSetAsList.stream().filter(x -> {
              //Boolean ret = false;
              //for (PlaceholderType var : vars) {
              //	ret = ret || x.stream().map(b -> b.getLhsType().equals(var)).reduce((c,d) -> c || d).get();
              //}
              return (!x.containsAll(durchschnitt));
            })//.filter(y -> couldBecorrect(reducedUndefResSubstGroundedBasePair, y)) //fuer testzwecke auskommentiert um nofstred zu bestimmen PL 2018-10-10
            .collect(Collectors.toCollection(ArrayList::new));
        }

        if (context.logger().isLogLevelActive(Logger.LogLevel.DEBUG)) {
          List<Set<UnifyPair>> finalNextSetAsList1 = nextSetAsList;
          context.logger().debug(() -> "nextSetasList nach filter-Aufruf: " + finalNextSetAsList1);
          int nofstred = nextSetAsList.size();
          //NOCH NICHT korrekt PL 2018-10-12
          //nextSetasList = nextSetasList.stream().filter(y -> couldBecorrect(reducedUndefResSubstGroundedBasePair, y))
          //			.collect(Collectors.toCollection(ArrayList::new));
          context.logger().debug("currentThreadResult (undef): " + currentThreadResult.toString());
          context.logger().debug("abhSubst: " + abhSubst.toString());
          context.logger().debug("a2: " + rekTiefe + " " + varianceCase.a.toString());
          context.logger().debug("Durchschnitt: " + durchschnitt.toString());
          context.logger().debug("nextSet: " + nextSet.toString());
          context.logger().debug("nextSetasList: " + nextSetAsList.toString());
          context.logger().debug("Number first erased Elements (undef): " + (len - nofstred));
          context.logger().debug("Number second erased Elements (undef): " + (nofstred - nextSetAsList.size()));
          context.logger().debug("Number erased Elements (undef): " + (len - nextSetAsList.size()));
          noAllErasedElements += (len - nextSetAsList.size());
          context.logger().debug("Number of all erased Elements (undef): " + noAllErasedElements);
          context.logger().debug("Number of Backtracking: " + noBacktracking++);
        }

        // context.logger().info("");
      }
      //if (nextSetasList.size() == 0 && isUndefinedPairSetSet(result) && nextSet.size() > 1) {
      //	return result;
      //}
      //else {
      //	result.removeIf(y -> isUndefinedPairSet(y));
      //}
      //else result.stream().filter(y -> !isUndefinedPairSet(y));
      context.logger().debug(() -> "currentThreadResult: " + currentThreadResult.toString());

      return this.innerCartesianLoop(variance, rekTiefe, oderConstraint, parallel, result, varianceCase.a, nextSet,
        nextSetAsList, optOrigPair, methodSignatureConstraint, singleElementSets, sameEqSet, oderConstraints);
    });
  }


  /**
   * checks if there is for (a = ty) \in a in sameEqSet a constradiction
   *
   * @param a         Set of actual element of constraints with a =. ty \in a
   * @param sameEqSet Set of constraints where a <. ty' and ty' <. a
   * @param result    set of results which contains correct solution s and the
   *                  the error constraints. Error constraints are added
   * @result contradiction of (a = ty) in sameEqSet
   */
  public boolean checkNoContradiction(Set<UnifyPair> a, Set<UnifyPair> sameEqSet, Set<Set<UnifyPair>> result) {

    //optAPair enthaelt ggf. das Paar a = ty' \in a
    //unterscheidet sich von optOrigPair, da dort a = ty
    Optional<UnifyPair> optAPair =
      a.stream()
        .filter(x ->
          x.getPairOp().equals(PairOperator.EQUALSDOT)
            && ( //Sicherstellen, dass bei a = ty a auch wirklich die gesuchte Typvariable ist
            x.getLhsType().equals(x.getBasePair().getLhsType())
              || x.getLhsType().equals(x.getBasePair().getRhsType())
          )
        )
        .findFirst();

    if (optAPair.isPresent()) {
      //basepair ist entweder a <. Ty oder ty <. a
      UnifyPair aPair = optAPair.get();
      //writeLog("optOrigPair: " + optOrigPair + " " + "aPair: " + aPair+ " " + "aPair.basePair(): " + aPair.getBasePair());

      context.logger().debug(() ->"checkA: " + aPair + "sameEqSet: " + sameEqSet);
      for (UnifyPair sameEq : sameEqSet) {
        if (sameEq.getLhsType() instanceof PlaceholderType) {
          Set<UnifyPair> localEq = new HashSet<>();
          Set<UnifyPair> unitedSubst = new HashSet<>(aPair.getAllSubstitutions());
          unitedSubst.addAll(aPair.getAllBases());
          unitedSubst.addAll(sameEq.getAllSubstitutions());
          unitedSubst.addAll(sameEq.getAllBases());
          localEq.add(new UnifyPair(aPair.getRhsType(), sameEq.getRhsType(), sameEq.getPairOp(), unitedSubst, null));
          finalresult = false;
          Set<Set<UnifyPair>> localRes;
          try {
            localRes = unify(localEq, new ArrayList<>(), fc, false, 0, new HashSet<>()).get();
          } catch (ExecutionException | InterruptedException exception) {
            throw new RuntimeException(exception);
          }
          finalresult = true;
          if (isUndefinedPairSetSet(localRes)) {
            if (result.isEmpty() || isUndefinedPairSetSet(result)) {
              result.addAll(localRes);
            }
            context.logger().debug(() ->"FALSE: " + aPair + "sameEqSet: " + sameEqSet);
            return false;
          }
        } else {
          Set<UnifyPair> localEq = new HashSet<>();
          Set<UnifyPair> unitedSubst = new HashSet<>(aPair.getAllSubstitutions());
          unitedSubst.addAll(aPair.getAllBases());
          unitedSubst.addAll(sameEq.getAllSubstitutions());
          unitedSubst.addAll(sameEq.getAllBases());
          localEq.add(new UnifyPair(sameEq.getLhsType(), aPair.getRhsType(), sameEq.getPairOp(), unitedSubst, null));
          finalresult = false;
          Set<Set<UnifyPair>> localRes;
          try {
            localRes = unify(localEq, new ArrayList<>(), fc, false, 0, new HashSet<>()).get();
          } catch (ExecutionException | InterruptedException exception) {
            throw new RuntimeException(exception);
          }
          finalresult = true;
          if (isUndefinedPairSetSet(localRes)) {
            if (result.isEmpty() || isUndefinedPairSetSet(result)) {
              result.addAll(localRes);
            }
            context.logger().debug(() ->"FALSE: " + aPair + "sameEqSet: " + sameEqSet);
            return false;
          }
        }
      }
      context.logger().debug(() ->"TRUE: " + aPair + "sameEqSet: " + sameEqSet);
      return true;
    }
    return true;
  }


  protected boolean couldBeCorrect(Set<Pair<Set<UnifyPair>, UnifyPair>> reducedUndefResSubstGroundedBasePair, Set<UnifyPair> nextElem) {
    return reducedUndefResSubstGroundedBasePair.stream()
      .map(pair -> {
        Set<UnifyPair> reducedAbhSubst = pair.getKey();
        reducedAbhSubst.addAll(nextElem);
        Optional<Set<UnifyPair>> substRes = rules.subst(reducedAbhSubst);
        if (substRes.isEmpty()) {
          return true;
        }
        //PL 2018-10-12
        //Evtl. zurest applyTypeUnification aufrufen
        //evtl auch unify aufrufen
        else {
          UnifyPair checkPair = substRes.get().stream()
            .filter(x -> x.getGroundBasePair().equals(pair.getValue().get())).findFirst().get();
          if (((checkPair.getLhsType() instanceof PlaceholderType) || (checkPair.getRhsType() instanceof PlaceholderType))
            && (checkPair.getPairOp() == PairOperator.SMALLERDOT || checkPair.getPairOp() == PairOperator.SMALLERDOTWC)) {
					/*
					Set<UnifyPair> setCheckPair = new HashSet<>();
					setCheckPair.add(checkPair);
					Set<UnifyPair> setReturnCheckPair = applyTypeUnificationRules(setCheckPair, fc);
					UnifyPair checkPair1 = setReturnCheckPair.iterator().next();
					Set<UnifyPair> up = new HashSet<>();
					up.add(checkPair1);
					Set<UnifyPair> undef = new HashSet<>();
					 */
            PairOperator pairOp = checkPair.getPairOp();
            UnifyType lhsType = checkPair.getLhsType();
            UnifyType rhsType = checkPair.getRhsType();

            ///* Case 1: (a <. Theta')
            if ((((pairOp == PairOperator.SMALLERDOT) || (pairOp == PairOperator.SMALLERNEQDOT)) && lhsType instanceof PlaceholderType)
              // Case 2: (a <.? ? ext Theta')
              || (pairOp == PairOperator.SMALLERDOTWC && lhsType instanceof PlaceholderType && rhsType instanceof ExtendsType)
              // Case 3: (a <.? ? sup Theta')
              || (pairOp == PairOperator.SMALLERDOTWC && lhsType instanceof PlaceholderType && rhsType instanceof SuperType)

              // Case 4 was replaced by an inference rule
              // Case 4: (a <.? Theta')
              || (pairOp == PairOperator.SMALLERDOTWC && lhsType instanceof PlaceholderType)
              // Case 5: (Theta <. a)
              || ((pairOp == PairOperator.SMALLERDOT) && rhsType instanceof PlaceholderType)
              // Case 6 was replaced by an inference rule.
              // Case 6: (? ext Theta <.? a)
              || (pairOp == PairOperator.SMALLERDOTWC && lhsType instanceof ExtendsType && rhsType instanceof PlaceholderType)
              // Case 7 was replaced by an inference rule
              // Case 7: (? sup Theta <.? a)
              || (pairOp == PairOperator.SMALLERDOTWC && lhsType instanceof SuperType && rhsType instanceof PlaceholderType)
              // Case 8: (Theta <.? a)
              || (pairOp == PairOperator.SMALLERDOTWC && rhsType instanceof PlaceholderType)
              //reduceWildcardLow
              || (pairOp == PairOperator.SMALLERDOTWC && (lhsType instanceof ExtendsType) && (rhsType instanceof ExtendsType))
              //reduceWildcardLowRight
              || ((pairOp == PairOperator.SMALLERDOTWC) && (lhsType instanceof ReferenceType) && (rhsType instanceof ExtendsType))
              //reduceWildcardUp
              || ((pairOp == PairOperator.SMALLERDOTWC) && (lhsType instanceof SuperType) && (rhsType instanceof SuperType))
              //reduceWildcardUpRight
              || ((pairOp == PairOperator.SMALLERDOTWC) && (lhsType instanceof ReferenceType) && (rhsType instanceof SuperType))
              //reduceFunN
              || (((pairOp == PairOperator.SMALLERDOT) || (pairOp == PairOperator.EQUALSDOT))
              //PL 2017-10-03 hinzugefuegt
              //da Regel auch fuer EQUALSDOT anwendbar
              && (lhsType instanceof FunNType) && (rhsType instanceof FunNType))
              //greaterFunN
              || ((pairOp == PairOperator.SMALLERDOT) && (lhsType instanceof FunNType) && (rhsType instanceof PlaceholderType))
              //smallerFunN
              || ((pairOp == PairOperator.SMALLERDOT) && (lhsType instanceof PlaceholderType && rhsType instanceof FunNType))
              //reduceTph
              || ((pairOp == PairOperator.SMALLERDOTWC) && (lhsType instanceof PlaceholderType && rhsType instanceof ReferenceType))
              //reduceTphExt
              || ((pairOp == PairOperator.SMALLERDOTWC) && (lhsType instanceof ExtendsType) && rhsType instanceof PlaceholderType)
              //reduceTphSup
              || ((pairOp == PairOperator.SMALLERDOTWC) && (lhsType instanceof SuperType) && rhsType instanceof PlaceholderType)) {
              return true;
            }
            // Case unknown: If a pair fits no other case, then the type unification has failed.
            // Through application of the rules, every pair should have one of the above forms.
            // Pairs that do not have one of the aboves form are contradictory.
            else {
              context.logger().debug(() ->"Second erase:" + checkPair);
              return false;
            }
            //*/
          } else {
            //Pair type <. ? extends ? extends type betrachten TODO PL 2018-10-09
          }
        }
        return true;
      }).reduce((xx, yy) -> xx || yy).get();
  }

  public boolean isUndefinedPairSet(Set<UnifyPair> s) {
    if (s.isEmpty()) {
      return false;
    } else {
      return s.stream().map(UnifyPair::isUndefinedPair).reduce(true, (x, y) -> (x && y));
    }
  }

  public boolean isUndefinedPairSetSet(Set<Set<UnifyPair>> s) {
    if (s.isEmpty()) {
      return false;
    }
    return s.stream().map(this::isUndefinedPairSet).reduce(true, (x, y) -> (x && y));
  }

  /**
   * Checks whether a set of pairs is in solved form.
   *
   * @param eqPrimePrime The set of pair
   * @return True if in solved form,  false otherwise.
   */
  protected boolean isSolvedForm(Set<UnifyPair> eqPrimePrime) {
    for (UnifyPair pair : eqPrimePrime) {
      UnifyType lhsType = pair.getLhsType();
      UnifyType rhsType = pair.getRhsType();

      if (!(lhsType instanceof PlaceholderType))
        return false;

      // If operator is not equals, both sides must be placeholders
      if (pair.getPairOp() != PairOperator.EQUALSDOT && !(rhsType instanceof PlaceholderType))
        return false;
    }
    return true;
  }

  /**
   * Repeatedly applies type unification rules to a set of equations.
   * This is step one of the unification algorithm.
   *
   * @return The set of pairs that results from repeated application of the inference rules.
   */
  public Set<UnifyPair> applyTypeUnificationRules(Set<UnifyPair> eq, IFiniteClosure fc) {

    /*
     * Rule Application Strategy:
     *
     * 1. 	Swap all pairs and erase all erasable pairs
     * 2. 	Apply all possible rules to a single pair, then move it to the result set.
     *    	Iterating over pairs first, then iterating over rules prevents the application
     *    	of rules to a "finished" pair over and over.
     * 2.1 	Apply all rules repeatedly except for erase rules. If
     * 		the application of a rule creates new pairs, check immediately
     * 		against the erase rules.
     */


    LinkedHashSet<UnifyPair> targetSet = new LinkedHashSet<>();
    LinkedList<UnifyPair> eqQueue = new LinkedList<>();

    /*
     * Swap all pairs and erase all erasable pairs
     */
    eq.forEach(x -> swapAddOrErase(x, fc, eqQueue));

    /*
     * Apply rules until the queue is empty
     */
    while (!eqQueue.isEmpty()) {
      UnifyPair pair = eqQueue.pollFirst();

      // ReduceUp, ReduceLow, ReduceUpLow
      Optional<UnifyPair> opt = rules.reduceUpLow(pair);
      opt = opt.isPresent() ? opt : rules.reduceLow(pair);
      opt = opt.isPresent() ? opt : rules.reduceUp(pair);
      opt = opt.isPresent() ? opt : rules.reduceWildcardLow(pair);
      opt = opt.isPresent() ? opt : rules.reduceWildcardLowRight(pair);
      opt = opt.isPresent() ? opt : rules.reduceWildcardUp(pair);
      opt = opt.isPresent() ? opt : rules.reduceWildcardUpRight(pair);
      //PL 2018-03-06 auskommentiert muesste falsch sein vgl. JAVA_BSP/Wildcard6.java
      //opt = opt.isPresent() ? opt : rules.reduceWildcardLowUp(pair);
      //opt = opt.isPresent() ? opt : rules.reduceWildcardUpLow(pair);
      //opt = opt.isPresent() ? opt : rules.reduceWildcardLeft(pair);

      // Reduce TPH
      opt = opt.isPresent() ? opt : rules.reduceTph(pair);

      // One of the rules has been applied
      if (opt.isPresent()) {
        swapAddOrErase(opt.get(), fc, eqQueue);
        continue;
      }

      // Reduce1, Reduce2, ReduceExt, ReduceSup, ReduceEq
      //try {
      //	logFile.write("PAIR1 " + pair + "\n");
      //	logFile.flush();
      //}
      //catch (IOException e) { }

      Optional<Set<UnifyPair>> optSet = rules.reduce1(pair, fc);
      optSet = optSet.isPresent() ? optSet : rules.reduce2(pair);
      optSet = optSet.isPresent() ? optSet : rules.reduceExt(pair, fc);
      optSet = optSet.isPresent() ? optSet : rules.reduceSup(pair, fc);
      optSet = optSet.isPresent() ? optSet : rules.reduceEq(pair);

      // ReduceTphExt, ReduceTphSup
      optSet = optSet.isPresent() ? optSet : rules.reduceTphExt(pair);
      optSet = optSet.isPresent() ? optSet : rules.reduceTphSup(pair);


      // FunN Rules
      optSet = optSet.isPresent() ? optSet : rules.reduceFunN(pair);
      optSet = optSet.isPresent() ? optSet : rules.greaterFunN(pair, fc);
      optSet = optSet.isPresent() ? optSet : rules.smallerFunN(pair, fc);

      // One of the rules has been applied
      if (optSet.isPresent()) {
        optSet.get().forEach(x -> swapAddOrErase(x, fc, eqQueue));
        continue;
      }

      // Adapt, AdaptExt, AdaptSup
      //try {
      //	logFile.write("PAIR2 " + pair + "\n");
      //	logFile.flush();
      //}
      //catch (IOException e) { }
      opt = rules.adapt(pair, fc);
      opt = opt.isPresent() ? opt : rules.adaptExt(pair, fc);
      opt = opt.isPresent() ? opt : rules.adaptSup(pair, fc);

      // One of the rules has been applied
      if (opt.isPresent()) {
        swapAddOrErase(opt.get(), fc, eqQueue);
        continue;
      }

      // None of the rules has been applied
      targetSet.add(pair);
    }

    return targetSet;
  }

  /**
   * Applies the rule swap to a pair if possible. Then adds the pair to the set if no erase rule applies.
   * If an erase rule applies, the pair is not added (erased).
   *
   * @param pair       The pair to swap and add or erase.
   * @param collection The collection to which the pairs are added.
   */
  protected void swapAddOrErase(UnifyPair pair, IFiniteClosure fc, Collection<UnifyPair> collection) {
    Optional<UnifyPair> opt = rules.swap(pair);
    UnifyPair pair2 = opt.orElse(pair);

    if (rules.erase1(pair2, fc) || rules.erase3(pair2) || rules.erase2(pair2, fc))
      return;

    collection.add(pair2);
  }

  /**
   * Splits the equation eq into a set eq1s where both terms are type variables,
   * and a set eq2s where one of both terms is not a type variable.
   *
   * @param eq   Set of pairs to be splitted.
   * @param eq1s Subset of eq where both terms are type variables.
   * @param eq2s eq/eq1s.
   */
  protected void splitEq(Set<UnifyPair> eq, Set<UnifyPair> eq1s, Set<UnifyPair> eq2s) {
    for (UnifyPair pair : eq)
      if (pair.getLhsType() instanceof PlaceholderType && pair.getRhsType() instanceof PlaceholderType)
        eq1s.add(pair);
      else
        eq2s.add(pair);
  }

  /**
   * Creates sets of pairs specified in the fourth step. Does not calculate cartesian products.
   *
   * @param undefined All pairs that did not match one of the 8 cases are added to this set.
   * @return The set of the eight cases (without empty sets). Each case is a set, containing sets generated
   * from the pairs that matched the case. Each generated set contains singleton sets or sets with few elements
   * (as in case 1 where sigma is added to the innermost set).
   */
  protected Set<Set<Set<? extends Set<UnifyPair>>>> calculatePairSets(Set<UnifyPair> eq2s, List<Set<Constraint<UnifyPair>>> oderConstraintsInput, IFiniteClosure fc, Set<UnifyPair> undefined, List<Set<Constraint<UnifyPair>>> oderConstraintsOutput) {
    context.logger().debug(() ->"eq2s: " + eq2s.toString());
    oderConstraintsOutput.addAll(oderConstraintsInput);
    List<Set<Set<? extends Set<UnifyPair>>>> result = new ArrayList<>(9);

    // Init all 8 cases + 9. Case: oderConstraints
    for (int i = 0; i < 9; i++)
      result.add(new HashSet<>());

    ArrayList<UnifyPair> eq2sprime = new ArrayList<>(eq2s);
    Iterator<UnifyPair> eq2sprimeit = eq2sprime.iterator();
    ArrayList<UnifyPair> eq2sAsListFst = new ArrayList<>();
    ArrayList<UnifyPair> eq2sAsListSnd = new ArrayList<>();
    ArrayList<UnifyPair> eq2sAsListThird = new ArrayList<>();
    ArrayList<UnifyPair> eq2sAsListFourth = new ArrayList<>();
    ArrayList<UnifyPair> eq2sAsListBack = new ArrayList<>();
    ArrayList<UnifyPair> eq2sAsList = new ArrayList<>();
    boolean first = true;
    while (eq2sprimeit.hasNext()) {// alle mit Variance != 0 nach vorne schieben
      UnifyPair up = eq2sprimeit.next();
      if ((up.getLhsType() instanceof PlaceholderType &&
        ((PlaceholderType) up.getLhsType()).getVariance() == 1 &&
        !((PlaceholderType) up.getLhsType()).isInnerType()) ||
        (up.getRhsType() instanceof PlaceholderType &&
          ((PlaceholderType) up.getRhsType()).getVariance() == -1) &&
          !((PlaceholderType) up.getRhsType()).isInnerType()) {
        eq2sAsListFst.add(up);
        eq2s.remove(up);
      } else if ((up.getLhsType() instanceof PlaceholderType && ((PlaceholderType) up.getLhsType()).getVariance() == 1 && ((PlaceholderType) up.getLhsType()).isInnerType())
        || (up.getRhsType() instanceof PlaceholderType && ((PlaceholderType) up.getRhsType()).getVariance() == -1) && ((PlaceholderType) up.getRhsType()).isInnerType()) {
        eq2sAsListSnd.add(up);
        eq2s.remove(up);
      } else if ((up.getLhsType() instanceof PlaceholderType &&
        ((PlaceholderType) up.getLhsType()).getVariance() == -1 &&
        !((PlaceholderType) up.getLhsType()).isInnerType()) ||
        (up.getRhsType() instanceof PlaceholderType &&
          ((PlaceholderType) up.getRhsType()).getVariance() == -1) &&
          !((PlaceholderType) up.getRhsType()).isInnerType()) {
        eq2sAsListThird.add(up);
        eq2s.remove(up);
      } else if ((up.getLhsType() instanceof PlaceholderType && ((PlaceholderType) up.getLhsType()).getVariance() == -1 && ((PlaceholderType) up.getLhsType()).isInnerType())
        || (up.getRhsType() instanceof PlaceholderType && ((PlaceholderType) up.getRhsType()).getVariance() == 1) && ((PlaceholderType) up.getRhsType()).isInnerType()) {
        eq2sAsListFourth.add(up);
        eq2s.remove(up);
      } else if ((up.getLhsType() instanceof PlaceholderType && ((PlaceholderType) up.getLhsType()).isInnerType())
        || (up.getRhsType() instanceof PlaceholderType && ((PlaceholderType) up.getRhsType()).isInnerType())) {
        eq2sAsListBack.add(up);
        eq2s.remove(up);
      }
    }
    //if (eq2sAsListFst.isEmpty())
    {
      List<Set<Constraint<UnifyPair>>> oderConstraintsVariance = oderConstraintsOutput.stream() //Alle Elemente rauswerfen, die Variance 0 haben oder keine TPH in LHS oder RHS sind
        .filter(x -> x.stream()
          .filter(y ->
            y.stream().filter(z -> ((z.getLhsType() instanceof PlaceholderType)
              && (((PlaceholderType) (z.getLhsType())).getVariance() != 0))
              || ((z.getRhsType() instanceof PlaceholderType)
              && (((PlaceholderType) (z.getRhsType())).getVariance() != 0))
            ).findFirst().isPresent()
          ).findFirst().isPresent()).collect(Collectors.toList());
      if (!oderConstraintsVariance.isEmpty()) {
        Set<Constraint<UnifyPair>> ret = oderConstraintsVariance.getFirst();
        oderConstraintsOutput.remove(ret);
        //Set<UnifyPair> retFlat = new HashSet<>();
        //ret.stream().forEach(x -> retFlat.addAll(x));

        //Alle wildcard Faelle rausfiltern bei not wildcardable
        ret = ret.stream().filter(x -> {
            Optional<UnifyPair> optElem;
            return !((optElem = x.stream().filter(y -> (y.getLhsType()) instanceof PlaceholderType
                && !((PlaceholderType) y.getLhsType()).isWildcardable()
                && y.getPairOp() == PairOperator.EQUALSDOT
                && !(y.getRhsType() instanceof PlaceholderType))
              .findAny()).isPresent()
              && optElem.get().getRhsType() instanceof ExtendsType);
          })
          .collect(Collectors.toSet());
        ret.forEach(x -> x.forEach(y -> {
          Set<UnifyPair> x_new = new HashSet<>(x); //PL 2020-03-18: y selbst darf nicht in die Substitutionen
          x_new.remove(y);
          y.addSubstitutions(x_new);
        }));
        result.get(8).add(ret);
        first = false;
      }
    }

    if (context.logger().isLogLevelActive(Logger.LogLevel.DEBUG)) {
      context.logger().debug("eq2s: " + eq2s);
      context.logger().debug("eq2sAsListFst: " + eq2sAsListFst);
      context.logger().debug("eq2sAsListSnd: " + eq2sAsListSnd);
      context.logger().debug("eq2sAsListBack: " + eq2sAsListBack);
    }

    eq2sAsList.addAll(eq2sAsListFst);
    eq2sAsList.addAll(eq2sAsListSnd);
    eq2sAsList.addAll(eq2sAsListThird);
    eq2sAsList.addAll(eq2sAsListFourth);
    eq2sAsList.addAll(eq2s);
    eq2sAsList.addAll(eq2sAsListBack);

    if (eq2sAsList.isEmpty() && first) {//Alle eq2s sind empty und alle oderConstraints mit Variance != 0 sind bearbeitet
      if (!oderConstraintsOutput.isEmpty()) {
        Set<Constraint<UnifyPair>> ret = oderConstraintsOutput.removeFirst();
        //if (ret.iterator().next().iterator().next().getLhsType().getName().equals("M"))
        //	context.logger().info("M");
        //Set<UnifyPair> retFlat = new HashSet<>();
        //ret.stream().forEach(x -> retFlat.addAll(x));

        //Alle wildcard Faelle rausfiltern bei not wildcardable
        ret = ret.stream().filter(x -> {
            Optional<UnifyPair> optElem;
            return !((optElem = x.stream().filter(y -> (y.getLhsType()) instanceof PlaceholderType
                && !((PlaceholderType) y.getLhsType()).isWildcardable()
                && y.getPairOp() == PairOperator.EQUALSDOT
                && !(y.getRhsType() instanceof PlaceholderType))
              .findAny()).isPresent()
              && optElem.get().getRhsType() instanceof ExtendsType);
          })
          .collect(Collectors.toSet());

        ret.forEach(x -> x.forEach(y -> {
          Set<UnifyPair> x_new = new HashSet<>(x); //PL 2020-03-18: y selbst darf nicht in die Substitutionen
          x_new.remove(y);
          y.addSubstitutions(x_new);
        }));
        result.get(8).add(ret);
        first = false;
      }
    }
		/*
		Bei allen die Abhaengigkeit der Elemente aus eq2sAsList als evtl. als Substitution 
		  hinzufuegen 
		*/
    Set<UnifyPair> consideredElements = new HashSet<>();
    for (UnifyPair pair : eq2sAsList) {
      if (consideredElements.contains(pair)) {
        continue;
      }
      PairOperator pairOp = pair.getPairOp();
      UnifyType lhsType = pair.getLhsType();
      UnifyType rhsType = pair.getRhsType();

      // Case 1: (a <. Theta')
      if (((pairOp == PairOperator.SMALLERDOT) || (pairOp == PairOperator.SMALLERNEQDOT)) && lhsType instanceof PlaceholderType) {
        //context.logger().info(pair);
        if (first) { //writeLog(pair.toString()+"\n");
          Set<Set<UnifyPair>> x1 = new HashSet<>();
          if (pair.getRhsType().getName().equals("void")) {
            Set<UnifyPair> resultOne = new HashSet<>();
            resultOne.add(new UnifyPair(pair.getLhsType(), pair.getRhsType(), PairOperator.EQUALSDOT, pair.getSubstitution(), pair));
            x1.add(resultOne);
          } else {
            x1 = unifyCase1(pair, fc);
          }
          if (pairOp == PairOperator.SMALLERNEQDOT) {
            Set<UnifyPair> remElem = new HashSet<>();
            remElem.add(new UnifyPair(pair.getLhsType(), pair.getRhsType(), PairOperator.EQUALSDOT));
            x1.remove(remElem);
            remElem = new HashSet<>();
            remElem.add(new UnifyPair(pair.getLhsType(), new ExtendsType(pair.getRhsType()), PairOperator.EQUALSDOT));
            x1.remove(remElem);
            remElem = new HashSet<>();
            remElem.add(new UnifyPair(pair.getLhsType(), new SuperType(pair.getRhsType()), PairOperator.EQUALSDOT));
            x1.remove(remElem);
          }
					/* ZU LOESCHEN ANFANG
				//context.logger().info(x1);
					Set<UnifyPair> sameEqSet = eq2sAsList.stream()
							  .filter(x -> ((x.getLhsType().equals(lhsType) || x.getRhsType().equals(lhsType)) && !x.equals(pair)))
							  .collect(Collectors.toCollection(HashSet::new));
					consideredElements.addAll(sameEqSet);
					Set<Set<UnifyPair>> x2 = x1;
					Set<Set<UnifyPair>> x1Res = new HashSet<>(); 
					writeLog("pair:\n" + pair.toString());
					writeLog("x1 Start:\n" + x1.toString());
					writeLog("sameEqSet:\n" + sameEqSet.toString());
					for (UnifyPair sameEq : sameEqSet) {
						writeLog("x1 Original:\n" + x1.toString());
						if (sameEq.getLhsType() instanceof PlaceholderType) {
							x1 = x1.stream().filter(y -> {
								UnifyPair type = y.stream().filter(z -> z.getLhsType().equals(lhsType)).findFirst().get();
								Set<UnifyPair> localEq = new HashSet<>(); 
								Set<UnifyPair> unitedSubst = new HashSet<>(type.getSubstitution());
								unitedSubst.addAll(sameEq.getSubstitution());
								localEq.add(new UnifyPair(type.getRhsType(), sameEq.getRhsType(), sameEq.getPairOp(), unitedSubst, null));
								Set<Set<UnifyPair>> localRes = unify(localEq, new ArrayList<>(), fc, false, 0, false);
								Boolean localCorr = !isUndefinedPairSetSet(localRes);
								if (!localCorr) {
									collectErr.addAll(localRes);
								}
								else {
									localRes.forEach(z -> z.addAll(y));
									x1Res.addAll(localRes);
								}
								return localCorr;
							}
							).collect(Collectors.toCollection(HashSet::new));
						}
						else {
							x1 = x1.stream().filter(y -> {
								UnifyPair type = y.stream().filter(z -> z.getLhsType().equals(lhsType)).findFirst().get();
								Set<UnifyPair> localEq = new HashSet<>(); 
								Set<UnifyPair> unitedSubst = new HashSet<>(type.getSubstitution());
								unitedSubst.addAll(sameEq.getSubstitution());
								localEq.add(new UnifyPair(sameEq.getLhsType(), type.getRhsType(), sameEq.getPairOp(), unitedSubst, null));
								Set<Set<UnifyPair>> localRes = unify(localEq, new ArrayList<>(), fc, false, 0, false);
								Boolean localCorr = !isUndefinedPairSetSet(localRes);
								if (!localCorr) {
									collectErr.addAll(localRes);
								}
								else {
									localRes.forEach(z -> z.addAll(y));
									x1Res.addAll(localRes);
								}
								return localCorr;
							}
							).collect(Collectors.toCollection(HashSet::new));
						}
						writeLog("x1 nach Loeschung von " + sameEq.toString()+" :\n" + x1.toString());
					}
					Set<Set<UnifyPair>> x1ResPrime;
					if (sameEqSet.isEmpty()) {
						x1ResPrime = x1;
					}
					else {
						x1ResPrime = x1Res;
					}
					result.get(0).add(x1ResPrime);
					ZU LOESCHEN ENDE */
          result.getFirst().add(x1);
          if (x1.isEmpty()) {
            undefined.add(pair); //Theta ist nicht im FC => Abbruch
          }
        } else {
          Set<UnifyPair> s1 = new HashSet<>();
          s1.add(pair);
          Set<Set<UnifyPair>> s2 = new HashSet<>();
          s2.add(s1);
          result.getFirst().add(s2);
        }

      }
      // Case 2: (a <.? ? ext Theta')
      else if (pairOp == PairOperator.SMALLERDOTWC && lhsType instanceof PlaceholderType && rhsType instanceof ExtendsType)
        if (first) { //writeLog(pair.toString()+"\n");
          Set<Set<UnifyPair>> x1 = unifyCase2(pair, fc);
          result.get(1).add(x1);
          if (x1.isEmpty()) {
            undefined.add(pair); //Theta ist nicht im FC
          }
        } else {
          Set<UnifyPair> s1 = new HashSet<>();
          s1.add(pair);
          Set<Set<UnifyPair>> s2 = new HashSet<>();
          s2.add(s1);
          result.get(1).add(s2);
        }

        // Case 3: (a <.? ? sup Theta')
      else if (pairOp == PairOperator.SMALLERDOTWC && lhsType instanceof PlaceholderType && rhsType instanceof SuperType)
        if (first) { //writeLog(pair.toString()+"\n");
          Set<Set<UnifyPair>> x1 = unifyCase3(pair, fc);
          result.get(2).add(x1);
          if (x1.isEmpty()) {
            undefined.add(pair); //Theta ist nicht im FC
          }
        } else {
          Set<UnifyPair> s1 = new HashSet<>();
          s1.add(pair);
          Set<Set<UnifyPair>> s2 = new HashSet<>();
          s2.add(s1);
          result.get(2).add(s2);
        }

        // Case 4 was replaced by an inference rule
        // Case 4: (a <.? Theta')
        //else if(pairOp == PairOperator.SMALLERDOTWC && lhsType instanceof PlaceholderType)
        //	result.get(3).add(unifyCase4((PlaceholderType) lhsType, rhsType, fc));

        // Case 5: (Theta <. a)
      else if ((pairOp == PairOperator.SMALLERDOT) && rhsType instanceof PlaceholderType)
        if (first) { //writeLog(pair.toString()+"\n");
          Set<Set<UnifyPair>> x1 = new HashSet<>();
          if (pair.getLhsType().getName().equals("void")) {
            Set<UnifyPair> resultOne = new HashSet<>();
            resultOne.add(new UnifyPair(pair.getRhsType(), pair.getLhsType(), PairOperator.EQUALSDOT, pair.getSubstitution(), pair));
            x1.add(resultOne);
          } else {
            x1 = unifyCase5(pair, fc);
          }
          result.get(4).add(x1);
          if (x1.isEmpty()) {
            undefined.add(pair); //Theta ist nicht im FC
          }
        } else {
          Set<UnifyPair> s1 = new HashSet<>();
          s1.add(pair);
          Set<Set<UnifyPair>> s2 = new HashSet<>();
          s2.add(s1);
          result.get(4).add(s2);
        }

        // Case 6 was replaced by an inference rule.
        // Case 6: (? ext Theta <.? a)
        //else if(pairOp == PairOperator.SMALLERDOTWC && lhsType instanceof ExtendsType && rhsType instanceof PlaceholderType)
        //	result.get(5).add(unifyCase6((ExtendsType) lhsType, (PlaceholderType) rhsType, fc));

        // Case 7 was replaced by an inference rule
        // Case 7: (? sup Theta <.? a)
        //else if(pairOp == PairOperator.SMALLERDOTWC && lhsType instanceof SuperType && rhsType instanceof PlaceholderType)
        //	result.get(6).add(unifyCase7((SuperType) lhsType, (PlaceholderType) rhsType, fc));

        // Case 8: (Theta <.? a)
      else if (pairOp == PairOperator.SMALLERDOTWC && rhsType instanceof PlaceholderType)
        if (first) { //writeLog(pair.toString()+"\n");
          Set<Set<UnifyPair>> x1 = unifyCase8(pair, fc);
          result.get(7).add(x1);
          if (x1.isEmpty()) {
            undefined.add(pair); //Theta ist nicht im FC
          }
        } else {
          Set<UnifyPair> s1 = new HashSet<>();
          s1.add(pair);
          Set<Set<UnifyPair>> s2 = new HashSet<>();
          s2.add(s1);
          result.get(7).add(s2);
        }
        // Case unknown: If a pair fits no other case, then the type unification has failed.
        // Through application of the rules, every pair should have one of the above forms.
        // Pairs that do not have one of the aboves form are contradictory.
      else {
        // If a pair is not defined, the unificiation will fail, so the loop can be stopped here.
        undefined.add(pair);
        break;
      }
      first = false;
    }

    // Filter empty sets or sets that only contain an empty set.
    return result.stream()
      .map(x ->
        x.stream()
          .filter(y -> !y.isEmpty())
          .collect(Collectors.toCollection(HashSet::new)))
      .filter(x -> !x.isEmpty())
      .collect(Collectors.toCollection(HashSet::new));
  }

  //TODO: Wenn Theta' nicht im FC muss ein Fehler produziert werden PL 18-04-20

  /**
   * Cartesian product Case 1: (a <. Theta')
   */
  protected Set<Set<UnifyPair>> unifyCase1(UnifyPair pair, IFiniteClosure fc) {
    PlaceholderType a = (PlaceholderType) pair.getLhsType();
    UnifyType thetaPrime = pair.getRhsType();

    if (thetaPrime instanceof ExtendsType) {
      thetaPrime = ((ExtendsType) thetaPrime).getExtendedType();
    }

    if (thetaPrime instanceof SuperType) {
      //HIER MUSS NOCH WAS UEBERLEGT WERDEN
    }

    Set<Set<UnifyPair>> result = new HashSet<>();

    if (thetaPrime instanceof ReferenceType && ((ReferenceType) thetaPrime).isGenTypeVar()) {
      Set<UnifyPair> resultOne = new HashSet<>();
      resultOne.add(new UnifyPair(a, thetaPrime, PairOperator.EQUALSDOT, pair.getSubstitution(), pair));
      result.add(resultOne);
    }

    boolean allGen = thetaPrime.getTypeParams().size() > 0;
    for (UnifyType t : thetaPrime.getTypeParams())
      if (!(t instanceof PlaceholderType) || !((PlaceholderType) t).isGenerated()) {
        allGen = false;
        break;
      }
    //if (thetaPrime.getName().equals("java.util.Vector") //Fuer Bug 127
    //		&& thetaPrime instanceof ReferenceType
    //		&& ((ReferenceType)thetaPrime).getTypeParams().iterator().next() instanceof PlaceholderType) //.getName().equals("java.util.Vector"))
    //		&& ((ReferenceType)((ReferenceType)thetaPrime).getTypeParams().iterator().next()).getTypeParams().iterator().next().getName().equals("java.lang.Integer")) {
    //		{
    //		context.logger().info("");
    //}
    Set<UnifyType> cs = fc.getAllTypesByName(thetaPrime.getName());//cs= [java.util.Vector<NP>, java.util.Vector<java.util.Vector<java.lang.Integer>>, ????java.util.Vector<gen_hv>???]

    context.logger().debug(() ->"cs: " + cs.toString());
    //PL 18-02-06 entfernt, kommt durch unify wieder rein
    //cs.add(thetaPrime);
    //PL 18-02-06 entfernt

    //cs muessen fresh Typvars gesetzt werden PL 2018-03-18
    Set<UnifyType> csPHRenamed = cs.stream().map(x -> {
      BinaryOperator<HashMap<PlaceholderType, PlaceholderType>> combiner = (aa, b) -> {
        aa.putAll(b);
        return aa;
      };
      var involvedPlaceholderTypes = x.getInvolvedPlaceholderTypes();
      HashMap<PlaceholderType, PlaceholderType> hm = involvedPlaceholderTypes.stream()
        .reduce(TypeUnifyTaskHelper.getPresizedHashMap(involvedPlaceholderTypes.size()),
          (aa, b) -> {
            aa.put(b, PlaceholderType.freshPlaceholder(context.placeholderRegistry()));
            return aa;
          }, combiner);
      return x.accept(new freshPlaceholder(), hm);
    }).collect(Collectors.toCollection(HashSet::new));

    IMatch match = new Match();
    for (UnifyType c : csPHRenamed) {
      //PL 18-02-05 getChildren durch smaller ersetzt in getChildren werden die Varianlen nicht ersetzt.
      Set<UnifyType> thetaQs = new HashSet<>();
      //TODO smaller wieder reinnehmen?
      //thetaQs.add(c);//
      thetaQs = fc.smaller(c, new HashSet<>()).stream().collect(Collectors.toCollection(HashSet::new));
      ArrayList<UnifyPair> ml = new ArrayList<>();
      ml.add(new UnifyPair(c, thetaPrime, PairOperator.EQUALSDOT));
      if ((match.match(ml)).isEmpty()) {
        thetaQs.remove(c);
      }
      Set<UnifyType> finalThetaQs = thetaQs;
      context.logger().debug(() ->"thetaQs von " + c + ": " + finalThetaQs.toString());
      //Set<UnifyType> thetaQs = fc.getChildren(c).stream().collect(Collectors.toCollection(HashSet::new));
      //thetaQs.add(thetaPrime); //PL 18-02-05 wieder geloescht
      //PL 2017-10-03: War auskommentiert habe ich wieder einkommentiert,
      //da children offensichtlich ein echtes kleiner und kein kleinergleich ist

      //PL 18-02-06: eingefuegt, thetaQs der Form V<V<...>> <. V'<V<...>> werden entfernt
      //TODO PL 19-01-14 wieder reinnehmen kurzfristig auskommentiert
      //thetaQs = thetaQs.stream().filter(ut -> ut.getTypeParams().arePlaceholders()).collect(Collectors.toCollection(HashSet::new));
      //PL 18-02-06: eingefuegt

      Set<UnifyType> thetaQPrimes = new HashSet<>();
      TypeParams cParams = c.getTypeParams();
      if (cParams.size() == 0)
        thetaQPrimes.add(c);
      else {
        ArrayList<Set<UnifyType>> candidateParams = new ArrayList<>();

        for (UnifyType param : cParams)
          candidateParams.add(fc.grArg(param, new HashSet<>()));

        for (TypeParams tp : permuteParams(candidateParams))
          thetaQPrimes.add(c.setTypeParams(tp));
      }
      context.logger().debug(() ->"thetaQPrimes von " + c + ": " + thetaQPrimes.toString());
      for (UnifyType tqp : thetaQPrimes) {//PL 2020-03-08 umbauen in der Schleife wird nur unifizierbarer Typ gesucht break am Ende
        Collection<PlaceholderType> tphs = tqp.getInvolvedPlaceholderTypes();
        Optional<Unifier> opt = stdUnify.unify(tqp, thetaPrime);
        if (opt.isEmpty()) {
          continue;
        }
        Unifier unifier = opt.get();
        unifier.swapPlaceholderSubstitutions(thetaPrime.getTypeParams());
        Set<UnifyPair> substitutionSet = new HashSet<>();
        for (Entry<PlaceholderType, UnifyType> sigma : unifier) {
          if (!tphs.contains(sigma.getKey())) {//eingefuegt PL 2019-02-02 Bug 127
            substitutionSet.add(new UnifyPair(sigma.getKey(), sigma.getValue(), PairOperator.EQUALSDOT,
              //TODO: nochmals ueberlegen ob hier pair.getSubstitution() korrekt ist, oder ob leere Menge hin müsste
              //alle folgenden New UnifyPair ebenfalls ueberpruefen PL 2018-04-19
              pair.getSubstitution(), pair));
          }
        }
        //List<UnifyType> freshTphs = new ArrayList<>(); PL 18-02-06 in die For-Schleife verschoben
        for (UnifyType tq : thetaQs) {
          //geaendert PL 20-03-07
          Set<UnifyType> smaller = new HashSet<>();
          smaller.add(unifier.apply(tq));
          //Set<UnifyType> smaller = fc.smaller(unifier.apply(tq), new HashSet<>());
          //eingefuegt PL 2018-03-29 Anfang ? ext. theta hinzufuegen
          if (a.isWildcardable()) {
            Set<UnifyType> smaller_ext = smaller.stream().filter(x -> !(x instanceof ExtendsType) && !(x instanceof SuperType))
              .map(x -> {
                //BinaryOperator<HashMap<PlaceholderType,PlaceholderType>> combiner = (aa,b) -> { aa.putAll(b); return aa;}; //Variablenumbenennung rausgenommen
                //HashMap<PlaceholderType,PlaceholderType> hm = x.getInvolvedPlaceholderTypes().stream()                   //Variablen muessen wahrscheinlich erhalten bleiben
                //		.reduce(new HashMap<PlaceholderType,PlaceholderType>(),
                //				(aa, b)-> { aa.put(b,PlaceholderType.freshPlaceholder()); return aa; }, combiner);
                return new ExtendsType(x);
              })//.accept(new freshPlaceholder(), hm));}
              .collect(Collectors.toCollection(HashSet::new));
            smaller.addAll(smaller_ext);
          }
          //eingefuegt PL 2018-03-29 Ende ? ext. theta hinzufuegen
          for (UnifyType theta : smaller) {
            List<UnifyType> freshTphs = new ArrayList<>();
            Set<UnifyPair> resultPrime = new HashSet<>();

            for (int i = 0; !allGen && i < theta.getTypeParams().size(); i++) {
              if (freshTphs.size() - 1 < i)//IST DAS RICHTIG??? PL 2018-12-12
                freshTphs.add(PlaceholderType.freshPlaceholder(context.placeholderRegistry()));
              freshTphs.forEach(x -> ((PlaceholderType) x).setInnerType(true));
              resultPrime.add(new UnifyPair(freshTphs.get(i), theta.getTypeParams().get(i), PairOperator.SMALLERDOTWC, pair.getSubstitution(), pair));
            }

            UnifyPair up;
            if (allGen) {
              up = new UnifyPair(a, theta, PairOperator.EQUALSDOT, pair.getSubstitution(), pair);
              //TODO PL 2019-01-24: upit.next() ist nicht unbedingt ein PlaceholderType -> Visitor erledigt
              for (UnifyType unifyType : up.getRhsType().getTypeParams())
                unifyType.accept(new distributeVariance(), a.getVariance());//((PlaceholderType)upit.next()).setVariance(a.getVariance());
            } else {
              up = new UnifyPair(a, theta.setTypeParams(new TypeParams(freshTphs.toArray(new UnifyType[0]))), PairOperator.EQUALSDOT, pair.getSubstitution(), pair);
              Iterator<UnifyType> upit = up.getRhsType().getTypeParams().iterator();
              distributeVariance dv = new distributeVariance();
              //TODO PL 2019-01-24: upit.next() ist nicht unbedingt ein PlaceholderType -> Visitor erledigt
              while (upit.hasNext())
                upit.next().accept(new distributeVariance(), a.getVariance()); //((PlaceholderType)upit.next()).setVariance(a.getVariance());
            }
            resultPrime.add(up);
            resultPrime.addAll(substitutionSet);
            //writeLog("Substitution: " + substitutionSet.toString());
            result.add(resultPrime);
            //writeLog("Result: " + resultPrime.toString());
            //writeLog("MAX: " + oup.max(resultPrime.iterator()).toString());
          }
        }
      }
    }
    context.logger().debug(() ->"result von " + pair + ": " + result);
    return result;
  }

  /**
   * Cartesian Product Case 2: (a <.? ? ext Theta')
   */
  private Set<Set<UnifyPair>> unifyCase2(UnifyPair pair, IFiniteClosure fc) {
    PlaceholderType a = (PlaceholderType) pair.getLhsType();
    ExtendsType extThetaPrime = (ExtendsType) pair.getRhsType();
    Set<Set<UnifyPair>> result = new HashSet<>();

    PlaceholderType aPrime = PlaceholderType.freshPlaceholder(context.placeholderRegistry());
    aPrime.setVariance(a.getVariance());
    aPrime.disableWildcardtable();
    UnifyType extAPrime = new ExtendsType(aPrime);
    UnifyType thetaPrime = extThetaPrime.getExtendedType();
    Set<UnifyPair> resultPrime = new HashSet<>();
    resultPrime.add(new UnifyPair(a, thetaPrime, PairOperator.SMALLERDOT, pair.getSubstitution(), pair));
    result.add(resultPrime);

    resultPrime = new HashSet<>();
    resultPrime.add(new UnifyPair(a, extAPrime, PairOperator.EQUALSDOT, pair.getSubstitution(), pair));
    resultPrime.add(new UnifyPair(aPrime, thetaPrime, PairOperator.SMALLERDOT, pair.getSubstitution(), pair));
    result.add(resultPrime);
    //writeLog("Result: " + resultPrime.toString());
    return result;
  }

  /**
   * Cartesian Product Case 3: (a <.? ? sup Theta')
   */
  private Set<Set<UnifyPair>> unifyCase3(UnifyPair pair, IFiniteClosure fc) {
    PlaceholderType a = (PlaceholderType) pair.getLhsType();
    a.reversVariance();
    SuperType subThetaPrime = (SuperType) pair.getRhsType();
    Set<Set<UnifyPair>> result = new HashSet<>();

    UnifyType aPrime = PlaceholderType.freshPlaceholder(context.placeholderRegistry());
    ((PlaceholderType) aPrime).setVariance(((PlaceholderType) a).getVariance());
    ((PlaceholderType) aPrime).disableWildcardtable();
    UnifyType supAPrime = new SuperType(aPrime);
    UnifyType thetaPrime = subThetaPrime.getSuperedType();
    Set<UnifyPair> resultPrime = new HashSet<>();
    resultPrime.add(new UnifyPair(thetaPrime, a, PairOperator.SMALLERDOT, pair.getSubstitution(), pair, pair.getfBounded()));
    result.add(resultPrime);
    //writeLog(resultPrime.toString());

    resultPrime = new HashSet<>();
    resultPrime.add(new UnifyPair(a, supAPrime, PairOperator.EQUALSDOT, pair.getSubstitution(), pair));
    resultPrime.add(new UnifyPair(thetaPrime, aPrime, PairOperator.SMALLERDOT, pair.getSubstitution(), pair));
    result.add(resultPrime);
    //writeLog(resultPrime.toString());

    return result;
  }

  /**
   * Cartesian Product Case 5: (Theta <. a)
   */
  private Set<Set<UnifyPair>> unifyCase5(UnifyPair pair, IFiniteClosure fc) {
    UnifyType theta = pair.getLhsType();
    PlaceholderType a = (PlaceholderType) pair.getRhsType();
    Set<Set<UnifyPair>> result = new HashSet<>();

    boolean allGen = theta.getTypeParams().size() > 0;
    for (UnifyType t : theta.getTypeParams())
      if (!(t instanceof PlaceholderType) || !((PlaceholderType) t).isGenerated()) {
        allGen = false;
        break;
      }

    //eingefuegt PL 2019-01-03 ANFANG
    //fc.setLogTrue();
    //writeLog("FBOUNDED: " + pair.getfBounded());
    //writeLog("Pair: " + pair);
    Set<UnifyType> greater = fc.greater(theta, pair.getfBounded());
    //writeLog("GREATER: " + greater + pair + "THETA: " + theta + "FBOUNDED: " + pair.getfBounded() + " ");
    if (a.isWildcardable()) {
      Set<UnifyType> greater_ext = greater.stream().filter(x -> !(x instanceof ExtendsType) && !(x instanceof SuperType))
        .map(x -> {
          //BinaryOperator<HashMap<PlaceholderType,PlaceholderType>> combiner = (aa,b) -> { aa.putAll(b); return aa;}; //Variablenumbenennung rausgenommen
          //HashMap<PlaceholderType,PlaceholderType> hm = x.getInvolvedPlaceholderTypes().stream()                   //Variablen muessen wahrscheinlich erhalten bleiben
          //		.reduce(new HashMap<PlaceholderType,PlaceholderType>(),
          //				(aa, b)-> { aa.put(b,PlaceholderType.freshPlaceholder()); return aa; }, combiner);
          return new SuperType(x);
        })//.accept(new freshPlaceholder(), hm));}
        .collect(Collectors.toCollection(HashSet::new));
      greater.addAll(greater_ext);
    }
    //eingefuegt PL 2019-01-03 ENDE

    //for(UnifyType thetaS : fc.greater(theta, pair.getfBounded())) {
    for (UnifyType thetaS : greater) {
      Set<UnifyPair> resultPrime = new HashSet<>();
      Match match = new Match();

      UnifyType[] freshTphs = new UnifyType[thetaS.getTypeParams().size()];
      for (int i = 0; !allGen && i < freshTphs.length; i++) {
        freshTphs[i] = PlaceholderType.freshPlaceholder(context.placeholderRegistry());
        ((PlaceholderType) freshTphs[i]).setVariance(((PlaceholderType) a).getVariance());
        Set<UnifyType> fBounded = new HashSet<>(pair.getfBounded()); //PL 2019-01-09 new HashSet eingefuegt

        int i_ef = i;
        BiFunction<Boolean, UnifyType, Boolean> f = (x, y) ->
        {
          ArrayList<UnifyPair> termList = new ArrayList<UnifyPair>();
          termList.add(new UnifyPair(y, thetaS.getTypeParams().get(i_ef), PairOperator.EQUALSDOT));
          return ((match.match(termList).isPresent()) || x);
        };
        //if (parai.getName().equals("java.lang.Integer")) {
        //	context.logger().info("");
        //}
        BinaryOperator<Boolean> bo = (x, y) -> (x || y);
        if (fBounded.stream().reduce(false, f, bo)) {
          resultPrime.add(new UnifyPair(freshTphs[i], thetaS.getTypeParams().get(i), PairOperator.EQUALSDOT, pair.getSubstitution(), pair));
        } else {
          fBounded.add(thetaS.getTypeParams().get(i));
          resultPrime.add(new UnifyPair(thetaS.getTypeParams().get(i), freshTphs[i], PairOperator.SMALLERDOTWC, pair.getSubstitution(), pair, fBounded));
        }
      }

      if (allGen)
        resultPrime.add(new UnifyPair(a, thetaS, PairOperator.EQUALSDOT, pair.getSubstitution(), pair));
      else
        resultPrime.add(new UnifyPair(a, thetaS.setTypeParams(new TypeParams(freshTphs)), PairOperator.EQUALSDOT, pair.getSubstitution(), pair));
      result.add(resultPrime);
      //writeLog("FBOUNDED2: " + pair.getfBounded());
      //writeLog("resultPrime Theta < a: " + greater + pair + "THETA: " + theta + "FBOUNDED: " + pair.getfBounded() + " " + resultPrime.toString());
    }

    return result;
  }

  /**
   * Cartesian Product Case 8: (Theta <.? a)
   */
  private Set<Set<UnifyPair>> unifyCase8(UnifyPair pair, IFiniteClosure fc) {
    UnifyType theta = pair.getLhsType();
    PlaceholderType a = (PlaceholderType) pair.getRhsType();
    Set<Set<UnifyPair>> result = new HashSet<>();
    //for(UnifyType thetaS : fc.grArg(theta)) {
    Set<UnifyPair> resultPrime = new HashSet<>();
    resultPrime.add(new UnifyPair(a, theta, PairOperator.EQUALSDOT, pair.getSubstitution(), pair));
    result.add(resultPrime);
    //writeLog(resultPrime.toString());

    PlaceholderType freshTph = PlaceholderType.freshPlaceholder(context.placeholderRegistry());

    freshTph.setVariance(a.getVariance());
    freshTph.disableWildcardtable();
    resultPrime = new HashSet<>();
    resultPrime.add(new UnifyPair(a, new ExtendsType(freshTph), PairOperator.EQUALSDOT, pair.getSubstitution(), pair));
    resultPrime.add(new UnifyPair(theta, freshTph, PairOperator.SMALLERDOT, pair.getSubstitution(), pair, pair.getfBounded()));
    result.add(resultPrime);
    //writeLog("resultPrime: " + resultPrime.toString());

    resultPrime = new HashSet<>();
    resultPrime.add(new UnifyPair(a, new SuperType(freshTph), PairOperator.EQUALSDOT, pair.getSubstitution(), pair));
    resultPrime.add(new UnifyPair(freshTph, theta, PairOperator.SMALLERDOT, pair.getSubstitution(), pair));
    result.add(resultPrime);
    //writeLog(resultPrime.toString());
    //}

    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
   */
  private 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);
    }
  }
}
