//PL 2018-12-19: Merge chekcen
package de.dhbwstuttgart.typeinference.typeAlgo;

import de.dhbwstuttgart.exceptions.NotImplementedException;
import de.dhbwstuttgart.exceptions.TypeinferenceException;
import de.dhbwstuttgart.parser.NullToken;
import de.dhbwstuttgart.parser.SyntaxTreeGenerator.AssignToLocal;
import de.dhbwstuttgart.parser.scope.JavaClassName;
import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.syntaxtree.factory.ASTFactory;
import de.dhbwstuttgart.syntaxtree.factory.NameGenerator;
import de.dhbwstuttgart.syntaxtree.statement.*;
import de.dhbwstuttgart.syntaxtree.type.*;
import de.dhbwstuttgart.typeinference.assumptions.FieldAssumption;
import de.dhbwstuttgart.typeinference.assumptions.FunNClass;
import de.dhbwstuttgart.typeinference.assumptions.MethodAssumption;
import de.dhbwstuttgart.typeinference.assumptions.TypeInferenceBlockInformation;
import de.dhbwstuttgart.typeinference.constraints.*;
import de.dhbwstuttgart.typeinference.unify.model.PairOperator;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class TYPEStmt implements StatementVisitor{

    private final TypeInferenceBlockInformation info;
    private final ConstraintSet constraintsSet = new ConstraintSet();

    public TYPEStmt(TypeInferenceBlockInformation info){
        this.info = info;
    }

    public ConstraintSet getConstraints() {
        return constraintsSet;
    }

    /**
     * Erstellt einen neuen GenericResolver
     * Die Idee dieser Datenstruktur ist es, GTVs einen eindeutigen TPH zuzuweisen.
     * Bei Methodenaufrufen oder anderen Zugriffen, bei denen alle benutzten GTVs jeweils einen einheitlichen TPH bekommen müssen
     * kann diese Klasse eingesetzt werden. Wichtig ist, dass hierfür jeweils eine frische Instanz benutzt wird.
     * @return
     */
    private static GenericsResolver getResolverInstance(){
        return new GenericsResolverSameName();
    }

    private static TypeScope createTypeScope(ClassOrInterface cl, Method method) {
        return null;
    }

    @Override
    public void visit(ArgumentList arglist) {
        for(int i = 0;i<arglist.getArguments().size();i++){
            arglist.getArguments().get(i).accept(this);
        }
    }

    @Override
    public void visit(LambdaExpression lambdaExpression) {
        TypePlaceholder tphRetType = TypePlaceholder.fresh(new NullToken());
        List<RefTypeOrTPHOrWildcardOrGeneric> lambdaParams = lambdaExpression.params.getFormalparalist().stream().map((formalParameter -> formalParameter.getType())).collect(Collectors.toList());
        lambdaParams.add(tphRetType);
        //lambdaParams.add(0,tphRetType);
        constraintsSet.addUndConstraint(
                new Pair(lambdaExpression.getType(),
                		new RefType(new JavaClassName("Fun"+(lambdaParams.size()-1)+"$$"), lambdaParams, new NullToken()),
                        //new FunN(lambdaParams),
                        PairOperator.EQUALSDOT));
        constraintsSet.addUndConstraint(
                new Pair(lambdaExpression.getReturnType(),
                        tphRetType,PairOperator.EQUALSDOT));

        //Constraints des Bodys generieren:
        TYPEStmt lambdaScope = new TYPEStmt(new TypeInferenceBlockInformation(info, lambdaExpression));
        lambdaExpression.methodBody.accept(lambdaScope);
        constraintsSet.addAll(lambdaScope.getConstraints());
    }

    @Override
    public void visit(Assign assign) {
        assign.lefSide.accept(this);
        assign.rightSide.accept(this);
        constraintsSet.addUndConstraint(new Pair(
                assign.rightSide.getType(), assign.lefSide.getType(), PairOperator.SMALLERDOT));
    }

    @Override
    public void visit(Block block) {
        for(Statement stmt : block.getStatements()){
            stmt.accept(this);
        }
    }

    @Override
    public void visit(CastExpr castExpr) {
        throw new NotImplementedException();
    }

    @Override
    public void visit(EmptyStmt emptyStmt) {
        //Nothing :)
    }

    @Override
    public void visit(FieldVar fieldVar) {
        fieldVar.receiver.accept(this);
        Set<Constraint> oderConstraints = new HashSet<>();
        for(FieldAssumption fieldAssumption : info.getFields(fieldVar.fieldVarName)){
            Constraint constraint = new Constraint();
            GenericsResolver resolver = getResolverInstance();
            constraint.add(new Pair(fieldVar.receiver.getType(), fieldAssumption.getReceiverType(resolver), PairOperator.EQUALSDOT));
            constraint.add(new Pair(
                    fieldVar.getType(), fieldAssumption.getType(resolver), PairOperator.EQUALSDOT));
            oderConstraints.add(constraint);
        }
        if(oderConstraints.size() == 0)
            throw new TypeinferenceException("Kein Feld "+fieldVar.fieldVarName+ " gefunden", fieldVar.getOffset());
        constraintsSet.addOderConstraint(oderConstraints);
    }

    @Override
    public void visit(ForStmt forStmt) {
        throw new NotImplementedException();
    }

    @Override
    public void visit(IfStmt ifStmt) {
        RefType booleanType = new RefType(ASTFactory.createClass(java.lang.Boolean.class).getClassName(), new NullToken());
        //Expression inferieren:
        ifStmt.expr.accept(this);
        //Expression muss boolean sein:
        constraintsSet.addUndConstraint(new Pair(ifStmt.expr.getType(), booleanType, PairOperator.EQUALSDOT));
        //Blöcke inferieren:
        ifStmt.then_block.accept(this);
        //Beide Blöcke müssen den gleichen Supertyp haben, welcher den Rückgabetyp des If-Stmts darstellt
        constraintsSet.addUndConstraint(new Pair(ifStmt.else_block.getType(), ifStmt.getType(), PairOperator.SMALLERDOT));
        if(ifStmt.else_block != null){
            ifStmt.else_block.accept(this);
            constraintsSet.addUndConstraint(new Pair(ifStmt.else_block.getType(), ifStmt.getType(), PairOperator.SMALLERDOT));
        }

    }

    @Override
    public void visit(InstanceOf instanceOf) {
        throw new NotImplementedException();
    }

    @Override
    public void visit(LocalVar localVar) {
        // Es werden nur bei Feldvariablen Constraints generiert. Lokale Variablen sind eindeutig
    }

    @Override
    public void visit(LocalVarDecl localVarDecl) {
        //Hier ist nichts zu tun. Allen lokalen Variablen bekommen beim parsen schon den korrekten Typ
    }

    @Override
    public void visit(MethodCall methodCall) {
        methodCall.receiver.accept(this);
        //Overloading:
        Set<Constraint> methodConstraints = new HashSet<>();
        for(MethodAssumption m : this.getMethods(methodCall.name, methodCall.arglist, info)){
            GenericsResolver resolver = getResolverInstance();
            methodConstraints.add(generateConstraint(methodCall, m, info, resolver));
        }
        if(methodConstraints.size()<1){
            throw new TypeinferenceException("Methode "+methodCall.name+" ist nicht vorhanden!",methodCall.getOffset());
        }
        constraintsSet.addOderConstraint(methodConstraints);
    }

    @Override
    public void visit(NewClass methodCall) {
        //Overloading:
        Set<Constraint> methodConstraints = new HashSet<>();
        for(MethodAssumption m : this.getConstructors(info, (RefType) methodCall.getType(), methodCall.getArgumentList())){
            methodConstraints.add(generateConstructorConstraint(methodCall, m, info, getResolverInstance()));
        }
        if(methodConstraints.size()<1){
            throw new TypeinferenceException("Konstruktor in Klasse "+methodCall.getType().toString()+" ist nicht vorhanden!",methodCall.getOffset());
        }
        constraintsSet.addOderConstraint(methodConstraints);
    }

    @Override
    public void visit(NewArray newArray) {
        throw new NotImplementedException();
    }

    @Override
    public void visit(ExpressionReceiver receiver) {
        receiver.expr.accept(this);
    }

    private final RefType number = new RefType(ASTFactory.createClass(Number.class).getClassName(), new NullToken());
    private final RefType longg = new RefType(ASTFactory.createClass(Long.class).getClassName(), new NullToken());
    private final RefType integer = new RefType(ASTFactory.createClass(Integer.class).getClassName(), new NullToken());
    private final RefType shortt = new RefType(ASTFactory.createClass(Short.class).getClassName(), new NullToken());
    private final RefType bytee = new RefType(ASTFactory.createClass(Byte.class).getClassName(), new NullToken());
    private final RefType floatt = new RefType(ASTFactory.createClass(Float.class).getClassName(), new NullToken());
    private final RefType doublee = new RefType(ASTFactory.createClass(Double.class).getClassName(), new NullToken());
    private final RefType string = new RefType(ASTFactory.createClass(String.class).getClassName(), new NullToken());
    private final RefType bool =  new RefType(ASTFactory.createClass(Boolean.class).getClassName(), new NullToken());
    @Override
    public void visit(UnaryExpr unaryExpr) {
        if(unaryExpr.operation == UnaryExpr.Operation.POSTDECREMENT ||
                unaryExpr.operation == UnaryExpr.Operation.POSTINCREMENT ||
                unaryExpr.operation == UnaryExpr.Operation.PREDECREMENT ||
                unaryExpr.operation == UnaryExpr.Operation.PREINCREMENT){
            //@see: https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.14.2
            //Expression muss zu Numeric Convertierbar sein. also von Numeric erben
            constraintsSet.addUndConstraint(new Pair(unaryExpr.expr.getType(), number, PairOperator.SMALLERNEQDOT));
            //The type of the postfix increment expression is the type of the variable
            constraintsSet.addUndConstraint(new Pair(unaryExpr.expr.getType(), unaryExpr.getType(), PairOperator.EQUALSDOT));
        }else{
            throw new NotImplementedException();
        }
    }

    @Override
    public void visit(BinaryExpr binary) {
    		binary.lexpr.accept(this);
    		binary.rexpr.accept(this);
        if(binary.operation.equals(BinaryExpr.Operator.DIV) ||
                binary.operation.equals(BinaryExpr.Operator.MUL)||
                binary.operation.equals(BinaryExpr.Operator.MOD)||
                binary.operation.equals(BinaryExpr.Operator.ADD)||
                binary.operation.equals(BinaryExpr.Operator.SUB)) {
            Set<Constraint<Pair>> numericAdditionOrStringConcatenation = new HashSet<>();

// TODO PL 2018-11-06
            
            //            Auf importierte Typen einschraenken
            //         	 pruefen, ob  Typen richtig bestimmt werden.
            

            
            //Zuerst der Fall für Numerische AusdrücPairOpnumericeratorke, das sind Mul, Mod und Div immer:
            //see: https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.17
            //Expression muss zu Numeric Convertierbar sein. also von Numeric erben
            Constraint<Pair> numeric;
            //PL eingefuegt 2018-07-17
            if (info.getAvailableClasses().stream().map(x -> x.getClassName()).collect(Collectors.toCollection(HashSet::new)).contains(bytee.getName())) {
            	numeric = new Constraint<>();
            	numeric.add(new Pair(binary.lexpr.getType(), bytee, PairOperator.SMALLERDOT));
            	numeric.add(new Pair(binary.rexpr.getType(), bytee, PairOperator.SMALLERDOT));
            	numeric.add(new Pair(binary.getType(), integer, PairOperator.SMALLERDOT));
                numericAdditionOrStringConcatenation.add(numeric);
            }
            //PL eingefuegt 2018-07-17
            if (info.getAvailableClasses().stream().map(x -> x.getClassName()).collect(Collectors.toCollection(HashSet::new)).contains(shortt.getName())) {
            	numeric = new Constraint<>();
            	numeric.add(new Pair(binary.lexpr.getType(), shortt, PairOperator.SMALLERDOT));
            	numeric.add(new Pair(binary.rexpr.getType(), shortt, PairOperator.SMALLERDOT));
            	numeric.add(new Pair(binary.getType(), integer, PairOperator.SMALLERDOT));
            	numericAdditionOrStringConcatenation.add(numeric);
            }
            //PL eingefuegt 2018-07-17
            if (info.getAvailableClasses().stream().map(x -> x.getClassName()).collect(Collectors.toCollection(HashSet::new)).contains(integer.getName())) {
            	numeric = new Constraint<>();
            	numeric.add(new Pair(binary.lexpr.getType(), integer, PairOperator.SMALLERDOT));
            	numeric.add(new Pair(binary.rexpr.getType(), integer, PairOperator.SMALLERDOT));
            	numeric.add(new Pair(integer, binary.getType(), PairOperator.SMALLERDOT));
            	numericAdditionOrStringConcatenation.add(numeric);
            }
            //PL eingefuegt 2018-07-17
            if (info.getAvailableClasses().stream().map(x -> x.getClassName()).collect(Collectors.toCollection(HashSet::new)).contains(longg.getName())) {
            	numeric = new Constraint<>();
            	numeric.add(new Pair(binary.lexpr.getType(), longg, PairOperator.SMALLERDOT));
            	numeric.add(new Pair(binary.rexpr.getType(), longg, PairOperator.SMALLERDOT));
            	numeric.add(new Pair(longg, binary.getType(), PairOperator.SMALLERDOT));
            	numericAdditionOrStringConcatenation.add(numeric);
            }
            //PL eingefuegt 2018-07-17
            if (info.getAvailableClasses().stream().map(x -> x.getClassName()).collect(Collectors.toCollection(HashSet::new)).contains(floatt.getName())) {
            	numeric = new Constraint<>();
            	numeric.add(new Pair(binary.lexpr.getType(), floatt, PairOperator.SMALLERDOT));
            	numeric.add(new Pair(binary.rexpr.getType(), floatt, PairOperator.SMALLERDOT));
            	numeric.add(new Pair(floatt, binary.getType(), PairOperator.SMALLERDOT));
            	numericAdditionOrStringConcatenation.add(numeric);
            }
            //PL eingefuegt 2018-07-17
            if (info.getAvailableClasses().stream().map(x -> x.getClassName()).collect(Collectors.toCollection(HashSet::new)).contains(doublee.getName())) {
            	numeric = new Constraint<>();
            	numeric.add(new Pair(binary.lexpr.getType(), doublee, PairOperator.SMALLERDOT));
            	numeric.add(new Pair(binary.rexpr.getType(), doublee, PairOperator.SMALLERDOT));
            	numeric.add(new Pair(doublee, binary.getType(), PairOperator.SMALLERDOT));
            	numericAdditionOrStringConcatenation.add(numeric);
            }
            /* PL auskommentiert Anfang 2018-07-17
            /*
            In Java passiert bei den binären Operatoren eine sogenannte Type Promotion:
            https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.6.2
            Das bedeutet, dass Java die Typen je nach belieben castet, so lange sie nur von Number erben
             
            numeric = new Constraint<>();
            numeric.add(new Pair(binary.getType(), number, PairOperator.SMALLERDOT));
            numericAdditionOrStringConcatenation.add(numeric);
            * PL auskommentiert Ende 2018-07-17 */
            
            if(binary.operation.equals(BinaryExpr.Operator.ADD)) {
                //Dann kann der Ausdruck auch das aneinanderfügen zweier Strings sein: ("a" + "b") oder (1 + 2)
            	if (info.getAvailableClasses().stream().map(x -> x.getClassName()).collect(Collectors.toCollection(HashSet::new)).contains(string.getName())) {
            		Constraint<Pair> stringConcat = new Constraint<>();
            		stringConcat.add(new Pair(binary.lexpr.getType(), string, PairOperator.EQUALSDOT));
            		stringConcat.add(new Pair(binary.rexpr.getType(), string, PairOperator.EQUALSDOT));
            		stringConcat.add(new Pair(string, binary.getType(), PairOperator.EQUALSDOT));
            		numericAdditionOrStringConcatenation.add(stringConcat);
            	}
            }
            if(numericAdditionOrStringConcatenation.size()<1){
                throw new TypeinferenceException("Kein Typ für " + binary.operation.toString() + "  vorhanden", binary.getOffset());
            }
            constraintsSet.addOderConstraint(numericAdditionOrStringConcatenation);
        }else if(binary.operation.equals(BinaryExpr.Operator.LESSEQUAL) ||
                binary.operation.equals(BinaryExpr.Operator.BIGGEREQUAL) ||
                binary.operation.equals(BinaryExpr.Operator.BIGGERTHAN) ||
                binary.operation.equals(BinaryExpr.Operator.LESSTHAN)) {
  /*      	//eingefuegt PL 2018-05-24
        	Set<Constraint<Pair>> numericRelationConcatenation = new HashSet<>();
        	Constraint<Pair> numeric = new Constraint<>();
        	numeric.add(new Pair(binary.lexpr.getType(), bytee, PairOperator.SMALLERDOT));
            numeric.add(new Pair(binary.rexpr.getType(), bytee, PairOperator.SMALLERDOT));
            numeric.add(new Pair(binary.getType(), bool, PairOperator.SMALLERDOT));
            numericRelationConcatenation.add(numeric);
            numeric = new Constraint<>();
            numeric.add(new Pair(binary.lexpr.getType(), shortt, PairOperator.SMALLERDOT));
            numeric.add(new Pair(binary.rexpr.getType(), shortt, PairOperator.SMALLERDOT));
            numeric.add(new Pair(binary.getType(), bool, PairOperator.SMALLERDOT));
            numericRelationConcatenation.add(numeric);
            numeric = new Constraint<>();
            numeric.add(new Pair(binary.lexpr.getType(), integer, PairOperator.SMALLERDOT));
            numeric.add(new Pair(binary.rexpr.getType(), integer, PairOperator.SMALLERDOT));
            numeric.add(new Pair(binary.getType(), bool, PairOperator.SMALLERDOT));
            numericRelationConcatenation.add(numeric);
            numeric = new Constraint<>();
            numeric.add(new Pair(binary.lexpr.getType(), longg, PairOperator.SMALLERDOT));
            numeric.add(new Pair(binary.rexpr.getType(), longg, PairOperator.SMALLERDOT));
            numeric.add(new Pair(binary.getType(), bool, PairOperator.SMALLERDOT));
            numericRelationConcatenation.add(numeric);
            numeric = new Constraint<>();
            numeric.add(new Pair(binary.lexpr.getType(), floatt, PairOperator.SMALLERDOT));
            numeric.add(new Pair(binary.rexpr.getType(), floatt, PairOperator.SMALLERDOT));
            numeric.add(new Pair(binary.getType(), bool, PairOperator.SMALLERDOT));
            numericRelationConcatenation.add(numeric);
            numeric = new Constraint<>();
            numeric.add(new Pair(binary.lexpr.getType(), doublee, PairOperator.SMALLERDOT));
            numeric.add(new Pair(binary.rexpr.getType(), doublee, PairOperator.SMALLERDOT));
            numeric.add(new Pair(binary.getType(), bool, PairOperator.SMALLERDOT));
            numericRelationConcatenation.add(numeric);
            
            //***ACHTUNG: Moeglicherweise oder und und-Contraint falsch
            constraintsSet.addOderConstraint(numericRelationConcatenation);
            //***ACHTUNG: Moeglicherweise oder und und-Contraint falsch
    */
            //Testeise eingefuegt PL 2018-05-24
        	//Hier sollte evtl. noch importe angefragt werden PL 2019-05-07
            constraintsSet.addUndConstraint(new Pair(binary.lexpr.getType(), number, PairOperator.SMALLERNEQDOT));
            constraintsSet.addUndConstraint(new Pair(binary.rexpr.getType(), number, PairOperator.SMALLERNEQDOT));
            //Rückgabetyp ist Boolean
            constraintsSet.addUndConstraint(new Pair(bool, binary.getType(), PairOperator.SMALLERDOT));

            //auskommentiert PL 2018-05-24
            //constraintsSet.addUndConstraint(new Pair(binary.lexpr.getType(), number, PairOperator.SMALLERDOT));
            //constraintsSet.addUndConstraint(new Pair(binary.rexpr.getType(), number, PairOperator.SMALLERDOT));
            //Rückgabetyp ist Boolean
            //constraintsSet.addUndConstraint(new Pair(bool, binary.getType(), PairOperator.EQUALSDOT));
        }else if(binary.operation.equals(BinaryExpr.Operator.EQUAL) || binary.operation.equals(BinaryExpr.Operator.NOTEQUAL)){
            /*Auszug aus https://docs.oracle.com/javase/specs/jls/se9/html/jls-15.html#jls-15.21
            The equality operators may be used to compare two operands that are convertible (§5.1.8) to numeric type, or two operands of type boolean or Boolean, or two operands that are each of either reference type or the null type. All other cases result in a compile-time error.
             */
            //Der Equals Operator geht mit fast allen Typen, daher werden hier keine Constraints gesetzt
            constraintsSet.addUndConstraint(new Pair(bool, binary.getType(), PairOperator.SMALLERDOT));
        }else{
                throw new NotImplementedException();
        }
    }

    @Override
    public void visit(Literal literal) {
        //Nothing to do here. Literale erzeugen keine Constraints 
    	//PL 2018-06-23 Sie haben einen Typ. Der muesste hier eingefuegt werden
    	//wie hier fuer double gezeigt. Im Momment auskommentiert, weil zu wenige Literaltypen
    	//funktionieren
    	if (literal.value instanceof Short) {
    		constraintsSet.addUndConstraint(new Pair(literal.getType(), shortt, PairOperator.EQUALSDOT));
    		return;
    	}
    	if (literal.value instanceof Byte) { 
    		constraintsSet.addUndConstraint(new Pair(literal.getType(), bytee, PairOperator.EQUALSDOT));
    		return;
    	}
    	if (literal.value instanceof Float) { 
    		constraintsSet.addUndConstraint(new Pair(literal.getType(), floatt, PairOperator.EQUALSDOT));
    		return;
    	}
    	if (literal.value instanceof Double) {
    		constraintsSet.addUndConstraint(new Pair(literal.getType(), doublee, PairOperator.EQUALSDOT));
    		return;
    	}
    	if (literal.value instanceof Long) { 
    		constraintsSet.addUndConstraint(new Pair(literal.getType(), longg, PairOperator.EQUALSDOT));
    		return;
    	}
    	if (literal.value instanceof Integer) { 
    		//constraintsSet.addUndConstraint(new Pair(literal.getType(),integer, PairOperator.EQUALSDOT)); 
    		// /*
    		HashSet<JavaClassName> clNames = info.getAvailableClasses().stream().map(x -> x.getClassName()).collect(Collectors.toCollection(HashSet::new));
    		Set<Constraint> oderConstraints = new HashSet<>();
    		Constraint constraint = new Constraint();
    		constraint.add(new Pair(literal.getType(), integer, PairOperator.EQUALSDOT));
    		oderConstraints.add(constraint);
    		if (clNames.stream().filter(x -> x.toString().equals("java.lang.Double")).findAny().isPresent()) {
    			constraint = new Constraint();
    			constraint.add(new Pair(literal.getType(), doublee, PairOperator.EQUALSDOT));
    			oderConstraints.add(constraint);
    		}
        	if (clNames.stream().filter(x -> x.toString().equals("java.lang.Long")).findAny().isPresent()) {
        		constraint = new Constraint();
        		constraint.add(new Pair(literal.getType(), longg, PairOperator.EQUALSDOT));
        		oderConstraints.add(constraint);
        	}
        	if (clNames.stream().filter(x -> x.toString().equals("java.lang.Float")).findAny().isPresent()) {
        		constraint = new Constraint();
        		constraint.add(new Pair(literal.getType(), floatt, PairOperator.EQUALSDOT));
        		oderConstraints.add(constraint);
        	}
        	if (clNames.stream().filter(x -> x.toString().equals("java.lang.Short")).findAny().isPresent()) {
        		constraint = new Constraint();
        		constraint.add(new Pair(literal.getType(), shortt, PairOperator.EQUALSDOT));
        		oderConstraints.add(constraint);
        	}
        	if (clNames.stream().filter(x -> x.toString().equals("java.lang.Byte")).findAny().isPresent()) {
        		constraint = new Constraint();
        		constraint.add(new Pair(literal.getType(), bytee, PairOperator.EQUALSDOT));
        		oderConstraints.add(constraint);
        	}
        	constraintsSet.addOderConstraint(oderConstraints);
    		// */
    		return;
    	}
    	if (literal.value instanceof Short) { 
    		constraintsSet.addUndConstraint(new Pair(literal.getType(),shortt, PairOperator.EQUALSDOT)); 
    		return;
    	}
    	if (literal.value instanceof Byte) { 
    		constraintsSet.addUndConstraint(new Pair(literal.getType(),bytee, PairOperator.EQUALSDOT));
    		return; 
    	}
    	if (literal.value instanceof Float) { 
    		constraintsSet.addUndConstraint(new Pair(literal.getType(),floatt, PairOperator.EQUALSDOT)); 
    		return;
    	}
    	if (literal.value instanceof String) { 
    		constraintsSet.addUndConstraint(new Pair(literal.getType(),string, PairOperator.EQUALSDOT));
    		return;
    	}
    	if (literal.value instanceof Boolean) {
    		constraintsSet.addUndConstraint(new Pair(literal.getType(),bool, PairOperator.EQUALSDOT));
    		return;
    	}
    	else {
    		throw new NotImplementedException();
    	}
    }

    @Override
    public void visit(Return returnExpr) {
        returnExpr.retexpr.accept(this);
        constraintsSet.addUndConstraint(new Pair(returnExpr.getType(),info.getCurrentTypeScope().getReturnType(), PairOperator.SMALLERDOT));
    }

    @Override
    public void visit(ReturnVoid aReturn) {
        visit((Return) aReturn);
    }

    @Override
    public void visit(StaticClassName staticClassName) {
        //Hier entstehen keine Constraints
    }

    @Override
    public void visit(Super aSuper) {
        throw new NotImplementedException();
    }

    @Override
    public void visit(This aThis) {
        //Im Falle von this, müssen die Generics in der Klasse als RefTypes behandelt werden.
        ClassOrInterface currentClass = info.getCurrentClass();
        List<RefTypeOrTPHOrWildcardOrGeneric> params = new ArrayList<>();
        for(GenericTypeVar gtv : currentClass.getGenerics()){
            params.add(new GenericRefType(gtv.getName(), aThis.getOffset()));
        }
        RefType thisType = new RefType(currentClass.getClassName(), params, aThis.getOffset());
        constraintsSet.addUndConstraint(new Pair(
                aThis.getType(), thisType, PairOperator.EQUALSDOT));
    }

    private static TypeScope createNullTypeScope() {
        return new TypeScope() {
            @Override
            public Iterable<? extends GenericTypeVar> getGenerics() {
                return new ArrayList<>();
            }

            @Override
            public RefTypeOrTPHOrWildcardOrGeneric getReturnType() {
                return null;
            }
        };
    }

    @Override
    public void visit(WhileStmt whileStmt) {
        RefType booleanType = new RefType(ASTFactory.createClass(java.lang.Boolean.class).getClassName(), new NullToken());
        //Expression inferieren:
        whileStmt.expr.accept(this);
        //Expression muss boolean sein:
        constraintsSet.addUndConstraint(new Pair(whileStmt.expr.getType(), booleanType, PairOperator.EQUALSDOT));
        //LoopBlock inferieren:
        whileStmt.loopBlock.accept(this);
    }

    @Override
    public void visit(DoStmt whileStmt) {
        throw new NotImplementedException();
    }

    @Override
    public void visit(AssignToField assignLeftSide) {
        //Hier ist kein Code nötig. Es werden keine extra Constraints generiert
    	//HIER muss Code rein PL 2018-10-24 
    	assignLeftSide.field.accept(this);
    }

    @Override
    public void visit(AssignToLocal assignLeftSide) {
        //Hier ist kein Code nötig. Es werden keine extra Constraints generiert
    }

    @Override
    public void visit(SuperCall superCall) {
        //TODO: Für einen super-Call werden keine Constraints erzeugt bisher
    }

    /*
    METHOD CALL Section:
     */

    protected Constraint<Pair> generateConstraint(MethodCall forMethod, MethodAssumption assumption,
                                                  TypeInferenceBlockInformation info, GenericsResolver resolver){
        Constraint methodConstraint = new Constraint();
        ClassOrInterface receiverCl = assumption.getReceiver();
        /*
        List<RefTypeOrTPHOrWildcardOrGeneric> params = new ArrayList<>();
        for(GenericTypeVar gtv : receiverCl.getGenerics()){
            //Die Generics werden alle zu TPHs umgewandelt.
            params.add(resolver.resolve(gtv.getName()));
        }

        RefTypeOrTPHOrWildcardOrGeneric receiverType = new RefType(assumption.getReceiver().getClassName(), params, forMethod.getOffset());
        */
        
        RefTypeOrTPHOrWildcardOrGeneric retType = assumption.getReceiverType(resolver);
        methodConstraint.add(new Pair(forMethod.receiver.getType(), retType,
                    PairOperator.SMALLERDOT));
        methodConstraint.add(new Pair(assumption.getReturnType(resolver), forMethod.getType(),
                PairOperator.EQUALSDOT));
        methodConstraint.addAll(generateParameterConstraints(forMethod, assumption, info, resolver));
        return methodConstraint;
    }

    protected Set<Pair> generateParameterConstraints(MethodCall foMethod, MethodAssumption assumption,
                                                     TypeInferenceBlockInformation info, GenericsResolver resolver) {
        Set<Pair> ret = new HashSet<>();
        for(int i = 0;i<foMethod.arglist.getArguments().size();i++){
            foMethod.arglist.getArguments().get(i).accept(this);
            RefTypeOrTPHOrWildcardOrGeneric argType = foMethod.arglist.getArguments().get(i).getType();
            RefTypeOrTPHOrWildcardOrGeneric assType = assumption.getArgTypes(resolver).get(i);
            ret.add(new Pair(argType, assType, PairOperator.SMALLERDOT));
        }
        return ret;
    }


    public static List<MethodAssumption> getMethods(String name, int numArgs, TypeInferenceBlockInformation info) {
        List<MethodAssumption> ret = new ArrayList<>();
        //TODO: apply Methoden wieder anfügen. Diese könnten möglicherweise auch in den Assumptions auftauchen (überdenken)
        if(name.equals("apply")){
            List<GenericRefType> funNParams = new ArrayList<>();
            for(int i = 0; i< numArgs + 1 ; i++){
                //funNParams.add(TypePlaceholder.fresh(new NullToken()));
            	funNParams.add(new GenericRefType(NameGenerator.makeNewName(),
                        new NullToken()));
            }
            funNParams.get(funNParams.size()-1);
            ret.add(new MethodAssumption(new FunNClass(funNParams), funNParams.get(funNParams.size()-1), funNParams.subList(0, funNParams.size()-1),
                    new TypeScope() {
                        @Override
                        public Iterable<? extends GenericTypeVar> getGenerics() {
                            throw new NotImplementedException();
                        }

                        @Override
                        public RefTypeOrTPHOrWildcardOrGeneric getReturnType() {
                            throw new NotImplementedException();
                        }
                    }));
        }
        for(ClassOrInterface cl : info.getAvailableClasses()){
            for(Method m : cl.getMethods()){
                if(m.getName().equals(name) &&
                        m.getParameterList().getFormalparalist().size() == numArgs){
                    RefTypeOrTPHOrWildcardOrGeneric retType = m.getReturnType();//info.checkGTV(m.getReturnType());

                    ret.add(new MethodAssumption(cl, retType, convertParams(m.getParameterList(),info),
                           createTypeScope(cl, m)));
                }
            }
        }
        return ret;
    }

    public static List<MethodAssumption> getMethods(String name, ArgumentList arglist, TypeInferenceBlockInformation info) {
        return getMethods(name, arglist.getArguments().size(), info);
    }

    protected static List<RefTypeOrTPHOrWildcardOrGeneric> convertParams(ParameterList parameterList, TypeInferenceBlockInformation info){
        //TODO: Hier müssen die Parameter mit den TPHs in den GEnerics des Receivers verknüpft werden
        /*
        BEispiel:
        auto test = new List<String>();
        test.add("hallo");

        Hier kriegt der Receiver ja den COnstraint TPH REceiver <. List<TPH A>
        Dann mus bei dem Parameter der COnstraint entstehen: TPH A <. String
         */
        List<RefTypeOrTPHOrWildcardOrGeneric> params = new ArrayList<>();
        for(FormalParameter fp : parameterList.getFormalparalist()){
            params.add(fp.getType()); //info.checkGTV(fp.getType())); //PL 2018-06-22 GTV sollen in Argumenten erhalten bleiben
        }
        return params;
    }

    public List<MethodAssumption> getConstructors(TypeInferenceBlockInformation info, RefType ofType, ArgumentList argList){
        List<MethodAssumption> ret = new ArrayList<>();
        for(ClassOrInterface cl : info.getAvailableClasses()){
            if(cl.getClassName().equals(ofType.getName())){
                for(Method m : cl.getConstructors()){
                    if(m.getParameterList().getFormalparalist().size() == argList.getArguments().size()){
                        ret.add(new MethodAssumption(cl, ofType, convertParams(m.getParameterList(),
                                info), createTypeScope(cl, m)));
                    }
                }
            }
        }
        return ret;
    }

    protected Constraint<Pair> generateConstructorConstraint(NewClass forConstructor, MethodAssumption assumption,
                                                             TypeInferenceBlockInformation info, GenericsResolver resolver){
        Constraint methodConstraint = new Constraint();
        methodConstraint.add(new Pair(assumption.getReturnType(resolver), forConstructor.getType(),
                PairOperator.SMALLERDOT));
        methodConstraint.addAll(generateParameterConstraints(forConstructor, assumption, info, resolver));
        return methodConstraint;
    }

}
