//PL 2018-12-19: typeInferenceOld nach typeInference uebertragen
package de.dhbwstuttgart.core;

import de.dhbwstuttgart.bytecode.BytecodeGen;
import de.dhbwstuttgart.bytecode.Exception.BytecodeGeneratorError;
import de.dhbwstuttgart.bytecode.genericsGenerator.GeneratedGenericsFinder;
import de.dhbwstuttgart.bytecode.genericsGeneratorTypes.GenericGenratorResultForSourceFile;
import de.dhbwstuttgart.environment.CompilationEnvironment;
import de.dhbwstuttgart.parser.JavaTXParser;
import de.dhbwstuttgart.parser.scope.GenericsRegistry;
import de.dhbwstuttgart.parser.SyntaxTreeGenerator.SyntaxTreeGenerator;
import de.dhbwstuttgart.parser.antlr.Java8Parser.CompilationUnitContext;
import de.dhbwstuttgart.parser.scope.JavaClassName;
import de.dhbwstuttgart.syntaxtree.ClassOrInterface;
import de.dhbwstuttgart.syntaxtree.SourceFile;
import de.dhbwstuttgart.syntaxtree.factory.ASTFactory;
import de.dhbwstuttgart.syntaxtree.factory.UnifyTypeFactory;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;
import de.dhbwstuttgart.syntaxtree.visual.ASTTypePrinter;
import de.dhbwstuttgart.typeinference.constraints.Constraint;
import de.dhbwstuttgart.typeinference.constraints.ConstraintSet;
import de.dhbwstuttgart.typeinference.constraints.Pair;
import de.dhbwstuttgart.typeinference.result.ResultSet;
import de.dhbwstuttgart.typeinference.typeAlgo.TYPE;
import de.dhbwstuttgart.typeinference.unify.RuleSet;
import de.dhbwstuttgart.typeinference.unify.TypeUnify;
import de.dhbwstuttgart.typeinference.unify.distributeVariance;
import de.dhbwstuttgart.typeinference.unify.interfaces.IFiniteClosure;
import de.dhbwstuttgart.typeinference.unify.model.PairOperator;
import de.dhbwstuttgart.typeinference.unify.model.PlaceholderType;
import de.dhbwstuttgart.typeinference.unify.model.UnifyPair;
import de.dhbwstuttgart.typeinference.unify.model.UnifyType;
import de.dhbwstuttgart.typeinference.unify.TypeUnifyTask;
import de.dhbwstuttgart.typeinference.unify.UnifyResultListener;
import de.dhbwstuttgart.typeinference.unify.UnifyResultListenerImpl;
import de.dhbwstuttgart.typeinference.unify.UnifyResultModel;
import de.dhbwstuttgart.typeinference.unify.UnifyTaskModel;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
//import org.apache.commons.io.output.NullOutputStream;

public class JavaTXCompiler {

	public static JavaTXCompiler INSTANCE;
    final CompilationEnvironment environment;
    Boolean resultmodel = true;
    public final Map<File, SourceFile> sourceFiles = new HashMap<>();
    Boolean log = true; //gibt an ob ein Log-File nach System.getProperty("user.dir")+"src/test/java/logFiles" geschrieben werden soll?
    public volatile UnifyTaskModel usedTasks = new UnifyTaskModel();
    
    public JavaTXCompiler(File sourceFile) throws IOException, ClassNotFoundException {
        this(Arrays.asList(sourceFile));
        INSTANCE = this;
    }

    public JavaTXCompiler(File sourceFile, Boolean log) throws IOException, ClassNotFoundException {
    	this(sourceFile);
    	this.log = log;
    	INSTANCE = this;
    }
    
    public JavaTXCompiler(List<File> sources) throws IOException, ClassNotFoundException {
        environment = new CompilationEnvironment(sources);
        for (File s : sources) {
            sourceFiles.put(s, parse(s));
        }
        INSTANCE = this;
    }

    public ConstraintSet<Pair> getConstraints() throws ClassNotFoundException {
        List<ClassOrInterface> allClasses = new ArrayList<>();//environment.getAllAvailableClasses();
        for (SourceFile sf : sourceFiles.values()) {
            allClasses.addAll(sf.getClasses());
        }
        List<ClassOrInterface> importedClasses = new ArrayList<>();
        //Alle Importierten Klassen in allen geparsten Sourcefiles kommen ins FC
        for (File forSourceFile : sourceFiles.keySet())
            for (JavaClassName name : sourceFiles.get(forSourceFile).getImports()) {
                //TODO: Hier werden imports von eigenen (.jav) Klassen nicht beachtet
                ClassOrInterface importedClass = ASTFactory.createClass(
                        ClassLoader.getSystemClassLoader().loadClass(name.toString()));
                importedClasses.add(importedClass);
            }
        allClasses.addAll(importedClasses);

        return new TYPE(sourceFiles.values(), allClasses).getConstraints();
    }

    public List<ClassOrInterface> getAvailableClasses(SourceFile forSourceFile) throws ClassNotFoundException {
        //PL 2018-09-18: List durch Set ersetzt, damit die Klassen nur einmal hinzugefuegt werden 
    	//List<ClassOrInterface> allClasses = new ArrayList<>();//environment.getAllAvailableClasses();
        Set<ClassOrInterface> allClasses = new HashSet<>();
    	
        /* PL 2018-09-19 geloescht werden bereits in typeInference hinzugefuegt
			}
		allClasses.addAll(importedClasses);

		return new TYPE(sourceFiles.values(), allClasses).getConstraints();
	}

	public List<ClassOrInterface> getAvailableClasses(SourceFile forSourceFile) throws ClassNotFoundException {
		// PL 2018-09-18: List durch Set ersetzt, damit die Klassen nur einmal
		// hinzugefuegt werden
		// List<ClassOrInterface> allClasses = new
		// ArrayList<>();//environment.getAllAvailableClasses();
		Set<ClassOrInterface> allClasses = new HashSet<>();

		/*
		 * PL 2018-09-19 geloescht werden bereits in typeInference hinzugefuegt for
		 * (SourceFile sf : sourceFiles.values()) { allClasses.addAll(sf.getClasses());
		 * }
		 */

		List<ClassOrInterface> importedClasses = new ArrayList<>();
		for (JavaClassName name : forSourceFile.getImports()) {
			// TODO: Hier werden imports von eigenen (.jav) Klassen nicht beachtet
			ClassOrInterface importedClass = ASTFactory
					.createClass(ClassLoader.getSystemClassLoader().loadClass(name.toString()));
			importedClasses.add(importedClass);
			allClasses.addAll(importedClasses);
		}
		return new ArrayList<>(allClasses);
	}

	/*
	 * public List<ResultSet> typeInferenceOld() throws ClassNotFoundException {
	 * List<ClassOrInterface> allClasses = new
	 * ArrayList<>();//environment.getAllAvailableClasses(); //Alle Importierten
	 * Klassen in allen geparsten Sourcefiles kommen ins FC for(SourceFile sf :
	 * this.sourceFiles.values()) { allClasses.addAll(getAvailableClasses(sf));
	 * allClasses.addAll(sf.getClasses()); }
	 * 
	 * final ConstraintSet<Pair> cons = getConstraints();
	 * 
	 * FiniteClosure finiteClosure = UnifyTypeFactory.generateFC(allClasses);
	 * System.out.println(finiteClosure); ConstraintSet<UnifyPair> unifyCons =
	 * UnifyTypeFactory.convert(cons);
	 * 
	 * TypeUnify unify = new TypeUnify(); Set<Set<UnifyPair>> results = new
	 * HashSet<>(); try { File logPath = new
	 * File(System.getProperty("user.dir")+"/target/logFiles/"); logPath.mkdirs();
	 * FileWriter logFile = new FileWriter(new File(logPath, "log"));
	 * logFile.write("FC:\\" + finiteClosure.toString()+"\n"); for(SourceFile sf :
	 * this.sourceFiles.values()) { logFile.write(ASTTypePrinter.print(sf)); }
	 * logFile.flush(); Set<List<Constraint<UnifyPair>>> cardProd =
	 * unifyCons.cartesianProduct(); for (List<Constraint<UnifyPair>> xCons :
	 * cardProd ){ Set<UnifyPair> xConsSet = new HashSet<>(); for
	 * (Constraint<UnifyPair> constraint : xCons) { xConsSet.addAll(constraint); }
	 * //.collect(Collectors.toCollection(ArrayList::new))))
	 * System.out.println(xConsSet); Set<String> methodParaTypeVarNames =
	 * allClasses.stream().map(x -> x.getMethods().stream().map(y ->
	 * y.getParameterList().getFormalparalist() .stream().filter(z -> z.getType()
	 * instanceof TypePlaceholder) .map(z ->
	 * ((TypePlaceholder)z.getType()).getName()).collect(Collectors.toCollection(
	 * HashSet::new))) .reduce(new HashSet<String>(), (a,b) -> { a.addAll(b); return
	 * a;}, (a,b) -> { a.addAll(b); return a;} ) ) .reduce(new HashSet<String>(),
	 * (a,b) -> { a.addAll(b); return a;} );
	 * 
	 * Set<String> constructorParaTypeVarNames = allClasses.stream().map(x ->
	 * x.getConstructors().stream().map(y ->
	 * y.getParameterList().getFormalparalist() .stream().filter(z -> z.getType()
	 * instanceof TypePlaceholder) .map(z ->
	 * ((TypePlaceholder)z.getType()).getName()).collect(Collectors.toCollection(
	 * HashSet::new))) .reduce(new HashSet<String>(), (a,b) -> { a.addAll(b); return
	 * a;}, (a,b) -> { a.addAll(b); return a;} ) ) .reduce(new HashSet<String>(),
	 * (a,b) -> { a.addAll(b); return a;} );
	 * 
	 * Set<String> paraTypeVarNames = methodParaTypeVarNames;
	 * paraTypeVarNames.addAll(constructorParaTypeVarNames);
	 * 
	 * Set<String> returnTypeVarNames = allClasses.stream().map(x ->
	 * x.getMethods().stream().filter(y -> y.getReturnType() instanceof
	 * TypePlaceholder) .map(z ->
	 * ((TypePlaceholder)z.getReturnType()).getName()).collect(Collectors.
	 * toCollection(HashSet::new))).reduce((a,b) -> { a.addAll(b); return a;}
	 * ).get();
	 * 
	 * Set<String> fieldTypeVarNames = allClasses.stream().map(x ->
	 * x.getFieldDecl().stream().filter(y -> y.getReturnType() instanceof
	 * TypePlaceholder) .map(z ->
	 * ((TypePlaceholder)z.getReturnType()).getName()).collect(Collectors.
	 * toCollection(HashSet::new))).reduce((a,b) -> { a.addAll(b); return a;}
	 * ).get();
	 * 
	 * returnTypeVarNames.addAll(fieldTypeVarNames);
	 * 
	 * xConsSet = xConsSet.stream().map(x -> { //Hier muss ueberlegt werden, ob //1.
	 * alle Argument- und Retuntyp-Variablen in allen UnifyPairs // mit
	 * disableWildcardtable() werden. //2. alle Typvariablen mit Argument- oder
	 * Retuntyp-Variablen //in Beziehung auch auf disableWildcardtable() gesetzt
	 * werden muessen //PL 2018-04-23 if ((x.getLhsType() instanceof
	 * PlaceholderType)) { if (paraTypeVarNames.contains(x.getLhsType().getName()))
	 * { ((PlaceholderType)x.getLhsType()).setVariance((byte)1);
	 * ((PlaceholderType)x.getLhsType()).disableWildcardtable(); } if
	 * (returnTypeVarNames.contains(x.getLhsType().getName())) {
	 * ((PlaceholderType)x.getLhsType()).setVariance((byte)-1);
	 * ((PlaceholderType)x.getLhsType()).disableWildcardtable(); } } if
	 * ((x.getRhsType() instanceof PlaceholderType)) { if
	 * (paraTypeVarNames.contains(x.getRhsType().getName())) {
	 * ((PlaceholderType)x.getRhsType()).setVariance((byte)1);
	 * ((PlaceholderType)x.getRhsType()).disableWildcardtable(); } if
	 * (returnTypeVarNames.contains(x.getRhsType().getName())) {
	 * ((PlaceholderType)x.getRhsType()).setVariance((byte)-1);
	 * ((PlaceholderType)x.getRhsType()).disableWildcardtable(); } } return x;//HIER
	 * DIE JEWEILS RECHT BZW. LINKE SEITE AUF GLEICHE VARIANZ SETZEN WIE DIE JEWEILS
	 * ANDERE SEITE }).map( y -> { if ((y.getLhsType() instanceof PlaceholderType)
	 * && (y.getRhsType() instanceof PlaceholderType)) { if
	 * (((PlaceholderType)y.getLhsType()).getVariance() != 0 &&
	 * ((PlaceholderType)y.getRhsType()).getVariance() == 0) {
	 * ((PlaceholderType)y.getRhsType()).setVariance(((PlaceholderType)y.getLhsType(
	 * )).getVariance()); } if (((PlaceholderType)y.getLhsType()).getVariance() == 0
	 * && ((PlaceholderType)y.getRhsType()).getVariance() != 0) {
	 * ((PlaceholderType)y.getLhsType()).setVariance(((PlaceholderType)y.getRhsType(
	 * )).getVariance()); } } return y; } )
	 * .collect(Collectors.toCollection(HashSet::new));
	 * varianceInheritance(xConsSet); Set<Set<UnifyPair>> result =
	 * unify.unifySequential(xConsSet, finiteClosure, logFile, log);
	 * //Set<Set<UnifyPair>> result = unify.unify(xConsSet, finiteClosure);
	 * System.out.println("RESULT: " + result); logFile.write("RES: " +
	 * result.toString()+"\n"); logFile.flush(); results.addAll(result); }
	 * 
	 * results = results.stream().map(x -> { Optional<Set<UnifyPair>> res = new
	 * RuleSet().subst(x.stream().map(y -> { if (y.getPairOp() ==
	 * PairOperator.SMALLERDOTWC) y.setPairOp(PairOperator.EQUALSDOT); return y;
	 * //alle Paare a <.? b erden durch a =. b ersetzt
	 * }).collect(Collectors.toCollection(HashSet::new))); if (res.isPresent())
	 * {//wenn subst ein Erg liefert wurde was veraendert return new
	 * TypeUnifyTask().applyTypeUnificationRules(res.get(), finiteClosure); } else
	 * return x; //wenn nichts veraendert wurde wird x zurueckgegeben
	 * }).collect(Collectors.toCollection(HashSet::new));
	 * System.out.println("RESULT Final: " + results); logFile.write("RES_FINAL: " +
	 * results.toString()+"\n"); logFile.flush(); logFile.write("PLACEHOLDERS: " +
	 * PlaceholderType.EXISTING_PLACEHOLDERS); logFile.flush(); } catch (IOException
	 * e) { e.printStackTrace(); } return results.stream().map((unifyPairs -> new
	 * ResultSet(UnifyTypeFactory.convert(unifyPairs,
	 * generateTPHMap(cons))))).collect(Collectors.toList()); }
	 */
	/**
	 * Vererbt alle Variancen bei Paaren (a <. theta) oder (Theta <. a) wenn a eine
	 * Variance !=0 hat auf alle Typvariablen in Theta.
	 * 
	 *
	 */
	/*
	 * 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 UnifyResultModel typeInferenceAsync(UnifyResultListener resultListener, Writer logFile)
			throws ClassNotFoundException {
		List<ClassOrInterface> allClasses = new ArrayList<>();// environment.getAllAvailableClasses();
		// Alle Importierten Klassen in allen geparsten Sourcefiles kommen ins FC
		for (SourceFile sf : this.sourceFiles.values()) {
			allClasses.addAll(getAvailableClasses(sf));
			allClasses.addAll(sf.getClasses());
		}

		final ConstraintSet<Pair> cons = getConstraints();
		Set<Set<UnifyPair>> results = new HashSet<>();
		UnifyResultModel urm = null;
		// urm.addUnifyResultListener(resultListener);
		try {
			logFile = logFile == null
					? new FileWriter(new File("log_" + sourceFiles.keySet().iterator().next().getName()))
					: logFile;
			IFiniteClosure finiteClosure = UnifyTypeFactory.generateFC(allClasses, logFile);
			System.out.println(finiteClosure);
			urm = new UnifyResultModel(cons, finiteClosure);
			urm.addUnifyResultListener(resultListener);
			ConstraintSet<UnifyPair> unifyCons = UnifyTypeFactory.convert(cons);

			Function<UnifyPair, UnifyPair> distributeInnerVars = x -> {
				UnifyType lhs, rhs;
				if (((lhs = x.getLhsType()) instanceof PlaceholderType)
						&& ((rhs = x.getRhsType()) instanceof PlaceholderType)
						&& (((PlaceholderType) lhs).isInnerType() || ((PlaceholderType) rhs).isInnerType())) {
					((PlaceholderType) lhs).setInnerType(true);
					((PlaceholderType) rhs).setInnerType(true);
				}
				return x;

			};
			logFile.write(unifyCons.toString());
			unifyCons = unifyCons.map(distributeInnerVars);
			logFile.write(unifyCons.toString());
			TypeUnify unify = new TypeUnify();
			// Set<Set<UnifyPair>> results = new HashSet<>(); Nach vorne gezogen
			logFile.write("FC:\\" + finiteClosure.toString() + "\n");
			for (SourceFile sf : this.sourceFiles.values()) {
				logFile.write(ASTTypePrinter.print(sf));
			}
			logFile.flush();

			Set<String> methodParaTypeVarNames = allClasses.stream().map(x -> x.getMethods().stream()
					.map(y -> y.getParameterList().getFormalparalist().stream()
							.filter(z -> z.getType() instanceof TypePlaceholder)
							.map(z -> ((TypePlaceholder) z.getType()).getName())
							.collect(Collectors.toCollection(HashSet::new)))
					.reduce(new HashSet<String>(), (a, b) -> {
						a.addAll(b);
						return a;
					}, (a, b) -> {
						a.addAll(b);
						return a;
					})).reduce(new HashSet<String>(), (a, b) -> {
						a.addAll(b);
						return a;
					});

			Set<String> constructorParaTypeVarNames = allClasses.stream().map(x -> x.getConstructors().stream()
					.map(y -> y.getParameterList().getFormalparalist().stream()
							.filter(z -> z.getType() instanceof TypePlaceholder)
							.map(z -> ((TypePlaceholder) z.getType()).getName())
							.collect(Collectors.toCollection(HashSet::new)))
					.reduce(new HashSet<String>(), (a, b) -> {
						a.addAll(b);
						return a;
					}, (a, b) -> {
						a.addAll(b);
						return a;
					})).reduce(new HashSet<String>(), (a, b) -> {
						a.addAll(b);
						return a;
					});

			Set<String> paraTypeVarNames = methodParaTypeVarNames;
			paraTypeVarNames.addAll(constructorParaTypeVarNames);

			Set<String> returnTypeVarNames = allClasses.stream()
					.map(x -> x.getMethods().stream().filter(y -> y.getReturnType() instanceof TypePlaceholder)
							.map(z -> ((TypePlaceholder) z.getReturnType()).getName())
							.collect(Collectors.toCollection(HashSet::new)))
					.reduce((a, b) -> {
						a.addAll(b);
						return a;
					}).get();

			Set<String> fieldTypeVarNames = allClasses.stream()
					.map(x -> x.getFieldDecl().stream().filter(y -> y.getReturnType() instanceof TypePlaceholder)
							.map(z -> ((TypePlaceholder) z.getReturnType()).getName())
							.collect(Collectors.toCollection(HashSet::new)))
					.reduce((a, b) -> {
						a.addAll(b);
						return a;
					}).get();

			returnTypeVarNames.addAll(fieldTypeVarNames);

			unifyCons = unifyCons.map(x -> {
				// Hier muss ueberlegt werden, ob
				// 1. alle Argument- und Retuntyp-Variablen in allen UnifyPairs
				// mit disableWildcardtable() werden.
				// 2. alle Typvariablen mit Argument- oder Retuntyp-Variablen
				// in Beziehung auch auf disableWildcardtable() gesetzt werden muessen
				// PL 2018-04-23
				if ((x.getLhsType() instanceof PlaceholderType)) {
					if (paraTypeVarNames.contains(x.getLhsType().getName())) {
						((PlaceholderType) x.getLhsType()).setVariance((byte) 1);
						((PlaceholderType) x.getLhsType()).disableWildcardtable();
					}
					if (returnTypeVarNames.contains(x.getLhsType().getName())) {
						((PlaceholderType) x.getLhsType()).setVariance((byte) -1);
						((PlaceholderType) x.getLhsType()).disableWildcardtable();
					}
				}
				if ((x.getRhsType() instanceof PlaceholderType)) {
					if (paraTypeVarNames.contains(x.getRhsType().getName())) {
						((PlaceholderType) x.getRhsType()).setVariance((byte) 1);
						((PlaceholderType) x.getRhsType()).disableWildcardtable();
					}
					if (returnTypeVarNames.contains(x.getRhsType().getName())) {
						((PlaceholderType) x.getRhsType()).setVariance((byte) -1);
						((PlaceholderType) x.getRhsType()).disableWildcardtable();
					}
				}
				return x;// HIER DIE JEWEILS RECHT BZW. LINKE SEITE AUF GLEICHE VARIANZ SETZEN WIE DIE
							// JEWEILS ANDERE SEITE
			});
			Set<PlaceholderType> varianceTPHold;
			Set<PlaceholderType> varianceTPH = new HashSet<>();
			varianceTPH = varianceInheritanceConstraintSet(unifyCons);

			/*
			 * PL 2018-11-07 wird in varianceInheritanceConstraintSet erledigt do { //PL
			 * 2018-11-05 Huellenbildung Variance auf alle TPHs der Terme auf der jeweiligen
			 * //anderen Seite übertragen varianceTPHold = new HashSet<>(varianceTPH);
			 * varianceTPH = varianceInheritanceConstraintSet(unifyCons); unifyCons.map( y
			 * -> { if ((y.getLhsType() instanceof PlaceholderType) && (y.getRhsType()
			 * instanceof PlaceholderType)) { if
			 * (((PlaceholderType)y.getLhsType()).getVariance() != 0 &&
			 * ((PlaceholderType)y.getRhsType()).getVariance() == 0) {
			 * ((PlaceholderType)y.getRhsType()).setVariance(((PlaceholderType)y.getLhsType(
			 * )).getVariance()); } if (((PlaceholderType)y.getLhsType()).getVariance() == 0
			 * && ((PlaceholderType)y.getRhsType()).getVariance() != 0) {
			 * ((PlaceholderType)y.getLhsType()).setVariance(((PlaceholderType)y.getRhsType(
			 * )).getVariance()); } } return y; } ); } while
			 * (!varianceTPHold.equals(varianceTPH));
			 */

			// Set<Set<UnifyPair>> result = unify.unifySequential(xConsSet, finiteClosure,
			// logFile, log);
			// Set<Set<UnifyPair>> result = unify.unify(xConsSet, finiteClosure);
			List<Set<Set<UnifyPair>>> oderConstraints = unifyCons.getOderConstraints().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));
			unify.unifyAsync(unifyCons.getUndConstraints(), oderConstraints, finiteClosure, logFile, log, urm,
					usedTasks);
		} catch (IOException e) {
			System.err.println("kein LogFile");
		}
		return urm;
	}

	public List<ResultSet> typeInference() throws ClassNotFoundException {
		List<ClassOrInterface> allClasses = new ArrayList<>();// environment.getAllAvailableClasses();
		// Alle Importierten Klassen in allen geparsten Sourcefiles kommen ins FC
		for (SourceFile sf : this.sourceFiles.values()) {
			allClasses.addAll(getAvailableClasses(sf));
			allClasses.addAll(sf.getClasses());
		}

		final ConstraintSet<Pair> cons = getConstraints();
		Set<Set<UnifyPair>> results = new HashSet<>();
		try {
			Writer logFile = // new OutputStreamWriter(new NullOutputStream());
					// new FileWriter(new
					// File(System.getProperty("user.dir")+"/src/test/resources/logFiles/"+"log_"+sourceFiles.keySet().iterator().next().getName()));
					new FileWriter(new File(System.getProperty("user.dir") + "/logFiles/" + "log_"
							+ sourceFiles.keySet().iterator().next().getName()));
			IFiniteClosure finiteClosure = UnifyTypeFactory.generateFC(allClasses, logFile);
			System.out.println(finiteClosure);
			ConstraintSet<UnifyPair> unifyCons = UnifyTypeFactory.convert(cons);

			Function<UnifyPair, UnifyPair> distributeInnerVars = x -> {
				UnifyType lhs, rhs;
				if (((lhs = x.getLhsType()) instanceof PlaceholderType)
						&& ((rhs = x.getRhsType()) instanceof PlaceholderType)
						&& (((PlaceholderType) lhs).isInnerType() || ((PlaceholderType) rhs).isInnerType())) {
					((PlaceholderType) lhs).setInnerType(true);
					((PlaceholderType) rhs).setInnerType(true);
				}
				return x;

			};
			logFile.write(unifyCons.toString());
			unifyCons = unifyCons.map(distributeInnerVars);
			logFile.write(unifyCons.toString());
			TypeUnify unify = new TypeUnify();
			// Set<Set<UnifyPair>> results = new HashSet<>(); Nach vorne gezogen
			logFile.write("FC:\\" + finiteClosure.toString() + "\n");
			for (SourceFile sf : this.sourceFiles.values()) {
				logFile.write(ASTTypePrinter.print(sf));
			}
			logFile.flush();

			Set<String> methodParaTypeVarNames = allClasses.stream().map(x -> x.getMethods().stream()
					.map(y -> y.getParameterList().getFormalparalist().stream()
							.filter(z -> z.getType() instanceof TypePlaceholder)
							.map(z -> ((TypePlaceholder) z.getType()).getName())
							.collect(Collectors.toCollection(HashSet::new)))
					.reduce(new HashSet<String>(), (a, b) -> {
						a.addAll(b);
						return a;
					}, (a, b) -> {
						a.addAll(b);
						return a;
					})).reduce(new HashSet<String>(), (a, b) -> {
						a.addAll(b);
						return a;
					});

			Set<String> constructorParaTypeVarNames = allClasses.stream().map(x -> x.getConstructors().stream()
					.map(y -> y.getParameterList().getFormalparalist().stream()
							.filter(z -> z.getType() instanceof TypePlaceholder)
							.map(z -> ((TypePlaceholder) z.getType()).getName())
							.collect(Collectors.toCollection(HashSet::new)))
					.reduce(new HashSet<String>(), (a, b) -> {
						a.addAll(b);
						return a;
					}, (a, b) -> {
						a.addAll(b);
						return a;
					})).reduce(new HashSet<String>(), (a, b) -> {
						a.addAll(b);
						return a;
					});

			Set<String> paraTypeVarNames = methodParaTypeVarNames;
			paraTypeVarNames.addAll(constructorParaTypeVarNames);

			Set<String> returnTypeVarNames = allClasses.stream()
					.map(x -> x.getMethods().stream().filter(y -> y.getReturnType() instanceof TypePlaceholder)
							.map(z -> ((TypePlaceholder) z.getReturnType()).getName())
							.collect(Collectors.toCollection(HashSet::new)))
					.reduce((a, b) -> {
						a.addAll(b);
						return a;
					}).get();

			Set<String> fieldTypeVarNames = allClasses.stream()
					.map(x -> x.getFieldDecl().stream().filter(y -> y.getReturnType() instanceof TypePlaceholder)
							.map(z -> ((TypePlaceholder) z.getReturnType()).getName())
							.collect(Collectors.toCollection(HashSet::new)))
					.reduce((a, b) -> {
						a.addAll(b);
						return a;
					}).get();

			returnTypeVarNames.addAll(fieldTypeVarNames);

			unifyCons = unifyCons.map(x -> {
				// Hier muss ueberlegt werden, ob
				// 1. alle Argument- und Retuntyp-Variablen in allen UnifyPairs
				// mit disableWildcardtable() werden.
				// 2. alle Typvariablen mit Argument- oder Retuntyp-Variablen
				// in Beziehung auch auf disableWildcardtable() gesetzt werden muessen
				// PL 2018-04-23
				if ((x.getLhsType() instanceof PlaceholderType)) {
					if (paraTypeVarNames.contains(x.getLhsType().getName())) {
						((PlaceholderType) x.getLhsType()).setVariance((byte) 1);
						((PlaceholderType) x.getLhsType()).disableWildcardtable();
					}
					if (returnTypeVarNames.contains(x.getLhsType().getName())) {
						((PlaceholderType) x.getLhsType()).setVariance((byte) -1);
						((PlaceholderType) x.getLhsType()).disableWildcardtable();
					}
				}
				if ((x.getRhsType() instanceof PlaceholderType)) {
					if (paraTypeVarNames.contains(x.getRhsType().getName())) {
						((PlaceholderType) x.getRhsType()).setVariance((byte) 1);
						((PlaceholderType) x.getRhsType()).disableWildcardtable();
					}
					if (returnTypeVarNames.contains(x.getRhsType().getName())) {
						((PlaceholderType) x.getRhsType()).setVariance((byte) -1);
						((PlaceholderType) x.getRhsType()).disableWildcardtable();
					}
				}
				return x;// HIER DIE JEWEILS RECHT BZW. LINKE SEITE AUF GLEICHE VARIANZ SETZEN WIE DIE
							// JEWEILS ANDERE SEITE
			});
			Set<PlaceholderType> varianceTPHold;
			Set<PlaceholderType> varianceTPH = new HashSet<>();
			varianceTPH = varianceInheritanceConstraintSet(unifyCons);

			/*
			 * PL 2018-11-07 wird in varianceInheritanceConstraintSet erledigt do { //PL
			 * 2018-11-05 Huellenbildung Variance auf alle TPHs der Terme auf der jeweiligen
			 * //anderen Seite übertragen varianceTPHold = new HashSet<>(varianceTPH);
			 * varianceTPH = varianceInheritanceConstraintSet(unifyCons); unifyCons.map( y
			 * -> { if ((y.getLhsType() instanceof PlaceholderType) && (y.getRhsType()
			 * instanceof PlaceholderType)) { if
			 * (((PlaceholderType)y.getLhsType()).getVariance() != 0 &&
			 * ((PlaceholderType)y.getRhsType()).getVariance() == 0) {
			 * ((PlaceholderType)y.getRhsType()).setVariance(((PlaceholderType)y.getLhsType(
			 * )).getVariance()); } if (((PlaceholderType)y.getLhsType()).getVariance() == 0
			 * && ((PlaceholderType)y.getRhsType()).getVariance() != 0) {
			 * ((PlaceholderType)y.getLhsType()).setVariance(((PlaceholderType)y.getRhsType(
			 * )).getVariance()); } } return y; } ); } while
			 * (!varianceTPHold.equals(varianceTPH));
			 */

			// Set<Set<UnifyPair>> result = unify.unifySequential(xConsSet, finiteClosure,
			// logFile, log);
			// Set<Set<UnifyPair>> result = unify.unify(xConsSet, finiteClosure);
			List<Set<Set<UnifyPair>>> oderConstraints = unifyCons.getOderConstraints().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));
			if (resultmodel) {
				/* UnifyResultModel Anfang */
				UnifyResultModel urm = new UnifyResultModel(cons, finiteClosure);
				UnifyResultListenerImpl li = new UnifyResultListenerImpl();
				urm.addUnifyResultListener(li);
				unify.unifyParallel(unifyCons.getUndConstraints(), oderConstraints, finiteClosure, logFile, log, urm,
						usedTasks);
				System.out.println("RESULT Final: " + li.getResults());
				logFile.write("RES_FINAL: " + li.getResults().toString() + "\n");
				logFile.flush();
				return li.getResults();
			}
			/* UnifyResultModel End */
			else {
				// Set<Set<UnifyPair>> result = unify.unify(unifyCons.getUndConstraints(),
				// oderConstraints, finiteClosure, logFile, log, new UnifyResultModel(cons,
				// finiteClosure));
				Set<Set<UnifyPair>> result = unify.unifyOderConstraints(unifyCons.getUndConstraints(), oderConstraints,
						finiteClosure, logFile, log, new UnifyResultModel(cons, finiteClosure), usedTasks);
				System.out.println("RESULT: " + result);
				logFile.write("RES: " + result.toString() + "\n");
				logFile.flush();
				results.addAll(result);

				results = results.stream().map(x -> {
					Optional<Set<UnifyPair>> res = new RuleSet().subst(x.stream().map(y -> {
						if (y.getPairOp() == PairOperator.SMALLERDOTWC)
							y.setPairOp(PairOperator.EQUALSDOT);
						return y; // alle Paare a <.? b erden durch a =. b ersetzt
					}).collect(Collectors.toCollection(HashSet::new)));
					if (res.isPresent()) {// wenn subst ein Erg liefert wurde was veraendert
						return new TypeUnifyTask().applyTypeUnificationRules(res.get(), finiteClosure);
					} else
						return x; // wenn nichts veraendert wurde wird x zurueckgegeben
				}).collect(Collectors.toCollection(HashSet::new));
				System.out.println("RESULT Final: " + results);
				logFile.write("RES_FINAL: " + results.toString() + "\n");
				logFile.flush();
				logFile.write("PLACEHOLDERS: " + PlaceholderType.EXISTING_PLACEHOLDERS);
				logFile.flush();
			}
		} catch (IOException e) {
			System.err.println("kein LogFile");
		}
		return results.stream()
				.map((unifyPairs -> new ResultSet(UnifyTypeFactory.convert(unifyPairs, Pair.generateTPHMap(cons)))))
				.collect(Collectors.toList());
	}

	/**
	 * Vererbt alle Variancen bei Paaren (a <. theta) oder (Theta <. a) wenn a eine
	 * Variance !=0 hat auf alle Typvariablen in Theta.
	 * 
	 *
	 */
	private Set<PlaceholderType> varianceInheritanceConstraintSet(ConstraintSet<UnifyPair> cons) {
		Set<UnifyPair> eq = cons.getAll();
		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());
			// ConstraintSet<UnifyPair> eq1 = cons;
			// eq1.removeIf(x -> !(x.getLhsType() instanceof PlaceholderType &&
			// ((PlaceholderType)x.getLhsType()).equals(a)));
			// durch if-Abfrage im foreach geloest
			cons.forEach(x -> {
				if (x.getLhsType() instanceof PlaceholderType && ((PlaceholderType) x.getLhsType()).equals(a)) {
					x.getRhsType().accept(new distributeVariance(), a.getVariance());
				}
			});
			// ` eq1 = new HashSet<>(eq);
			// eq1.removeIf(x -> !(x.getRhsType() instanceof PlaceholderType &&
			// ((PlaceholderType)x.getRhsType()).equals(a)));
			// durch if-Abfrage im foreach geloest
			cons.forEach(x -> {
				if (x.getRhsType() instanceof PlaceholderType && ((PlaceholderType) x.getRhsType()).equals(a)) {
					x.getLhsType().accept(new distributeVariance(), a.getVariance());
				}
			});
			phSetVariance = new ArrayList<>(phSet); // macht vermutlich keinen Sinn PL 2018-10-18, doch, es koennen neue
													// TPHs mit Variancen dazugekommen sein PL 2018-11-07
			phSetVariance.removeIf(x -> (x.getVariance() == 0 || usedTPH.contains(x)));
		}
		return usedTPH;
	}

	private SourceFile parse(File sourceFile) throws IOException, java.lang.ClassNotFoundException {
		CompilationUnitContext tree = JavaTXParser.parse(sourceFile);
		SyntaxTreeGenerator generator = new SyntaxTreeGenerator(environment.getRegistry(sourceFile),
				new GenericsRegistry(null));
		SourceFile ret = generator.convert(tree, environment.packageCrawler);
		return ret;
	}

	public void generateBytecodForFile(String path, HashMap<String, byte[]> classFiles, SourceFile sf,
			List<ResultSet> typeinferenceResult) throws IOException {
		try {
			List<GenericGenratorResultForSourceFile> genericResults = getGeneratedGenericResultsForAllSourceFiles(typeinferenceResult);
			BytecodeGen bytecodeGen = new BytecodeGen(classFiles,typeinferenceResult, genericResults, sf,path);
			bytecodeGen.visit(sf);
			this.writeClassFile(bytecodeGen.getClassFiles(), path);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}


	public List<GenericGenratorResultForSourceFile> getGeneratedGenericResultsForAllSourceFiles()
			throws ClassNotFoundException {
		List<GenericGenratorResultForSourceFile> result = new ArrayList<>();
		for (File f : sourceFiles.keySet()) {
			SourceFile sf = sourceFiles.get(f);
			List<ResultSet> typeinferenceResult = this.typeInference();
			GeneratedGenericsFinder sResFinder = new GeneratedGenericsFinder(sf, typeinferenceResult);
			GenericGenratorResultForSourceFile simplifyResOfSF = sResFinder.findGeneratedGenerics();
			result.add(simplifyResOfSF);
		}
		return result;
	}

	public List<GenericGenratorResultForSourceFile> getGeneratedGenericResultsForAllSourceFiles(
			List<ResultSet> typeinferenceResult) throws ClassNotFoundException {
		List<GenericGenratorResultForSourceFile> result = new ArrayList<>();
		for (File f : sourceFiles.keySet()) {
			SourceFile sf = sourceFiles.get(f);
			GeneratedGenericsFinder sResFinder = new GeneratedGenericsFinder(sf, typeinferenceResult);
			GenericGenratorResultForSourceFile simplifyResOfSF = sResFinder.findGeneratedGenerics();
			result.add(simplifyResOfSF);
		}
		return result;
	}

	// um pfad erweitern
	public void generateBytecode(String path) throws ClassNotFoundException, IOException, BytecodeGeneratorError {
		List<ResultSet> typeinferenceResult = this.typeInference();
		List<GenericGenratorResultForSourceFile> simplifyResultsForAllSourceFiles = getGeneratedGenericResultsForAllSourceFiles(
				typeinferenceResult);
		generateBytecode(path, typeinferenceResult, simplifyResultsForAllSourceFiles);
	}

	/**
	 * @param path
	 * @param typeinferenceResult
	 * @param simplifyResultsForAllSourceFiles
	 * @throws IOException
	 */
	public void generateBytecode(String path, List<ResultSet> typeinferenceResult,
			List<GenericGenratorResultForSourceFile> simplifyResultsForAllSourceFiles) throws IOException {
		for (File f : sourceFiles.keySet()) {
			HashMap<String, byte[]> classFiles = new HashMap<>();
			SourceFile sf = sourceFiles.get(f);
			BytecodeGen bytecodeGen = new BytecodeGen(classFiles, typeinferenceResult, simplifyResultsForAllSourceFiles,
					sf, path);
			bytecodeGen.visit(sf);
			writeClassFile(bytecodeGen.getClassFiles(), path);
		}
	}

	private void writeClassFile(HashMap<String, byte[]> classFiles, String path) throws IOException {
		FileOutputStream output;
		for (String name : classFiles.keySet()) {
			byte[] bytecode = classFiles.get(name);
			System.out.println("generating " + name + ".class file ...");
			// output = new FileOutputStream(new File(System.getProperty("user.dir") +
			// "/testBytecode/generatedBC/" +name+".class"));
			output = new FileOutputStream(new File(path + name + ".class"));
			output.write(bytecode);
			output.close();
			System.out.println(name + ".class file generated");
		}
	}

}
