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

import de.dhbwstuttgart.typeinference.unify.PlaceholderRegistry;
import de.dhbwstuttgart.typeinference.unify.TypeUnifyTaskHelper;
import java.util.*;
import java.util.stream.Collectors;

import de.dhbwstuttgart.exceptions.NotImplementedException;
import de.dhbwstuttgart.exceptions.TypeinferenceException;
import de.dhbwstuttgart.parser.NullToken;
import de.dhbwstuttgart.parser.SourceLoc;
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.ExtendsWildcardType;
import de.dhbwstuttgart.syntaxtree.type.GenericRefType;
import de.dhbwstuttgart.syntaxtree.type.RefType;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;
import de.dhbwstuttgart.syntaxtree.type.Void;
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.Constraint;
import de.dhbwstuttgart.typeinference.constraints.ConstraintSet;
import de.dhbwstuttgart.typeinference.constraints.GenericsResolver;
import de.dhbwstuttgart.typeinference.constraints.Pair;
import de.dhbwstuttgart.typeinference.unify.model.PairOperator;
import org.antlr.v4.runtime.Token;

public class TYPEStmt implements StatementVisitor {

    private final TypeInferenceBlockInformation info;
    private final ConstraintSet constraintsSet = new ConstraintSet();
    private final Stack<Switch> switchStack = new Stack<>();

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

    public ConstraintSet getConstraints() {
        return constraintsSet;
    }

    private SourceLoc loc(Token token) {
        return new SourceLoc(info.getCurrentClass().getFileName(), token.getLine());
    }

    ;

    /**
     * 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(), -1, false);
        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, loc(lambdaExpression.getOffset())));
        constraintsSet.addUndConstraint(new Pair(lambdaExpression.getReturnType(), tphRetType, PairOperator.EQUALSDOT, loc(lambdaExpression.getOffset())));

        // 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, loc(assign.getOffset())));
    }

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

    @Override
    public void visit(CastExpr castExpr) {
        castExpr.expr.accept(this);
        constraintsSet.addUndConstraint(new Pair(castExpr.getType(), castExpr.expr.getType(), PairOperator.SMALLERDOT));
    }

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

    @Override
    public void visit(FieldVar fieldVar) {
        fieldVar.receiver.accept(this);
        List<FieldAssumption> fieldAssumptions = info.getFields(fieldVar.fieldVarName);
        Set<Constraint> oderConstraints = TypeUnifyTaskHelper.getPresizedHashSet(fieldAssumptions.size());


        for (FieldAssumption fieldAssumption : fieldAssumptions) {
            Constraint constraint = new Constraint();
            GenericsResolver resolver = getResolverInstance();
            constraint.add(new Pair(fieldVar.receiver.getType(), fieldAssumption.getReceiverType(resolver), PairOperator.SMALLERDOT, loc(fieldVar.getOffset()))); // PL 2019-12-09: SMALLERDOT eingefuegt, EQUALSDOT entfernt, wenn ds Field privat ist muesste es EQUALSDOT lauten
            constraint.add(new Pair(fieldVar.getType(), fieldAssumption.getType(resolver), PairOperator.EQUALSDOT, loc(fieldVar.getOffset())));
            oderConstraints.add(constraint);
        }
        if (oderConstraints.isEmpty())
            throw new TypeinferenceException("Kein Feld " + fieldVar.fieldVarName + " gefunden", fieldVar.getOffset());
        constraintsSet.addOderConstraint(oderConstraints);
    }
    @Override
    public void visit(ForStmt forStmt) {
        forStmt.initializer.forEach(s -> s.accept(this));
        if (forStmt.condition != null)
            forStmt.condition.accept(this);
        forStmt.loopExpr.forEach(e -> e.accept(this));
        forStmt.block.accept(this);
    }

    @Override
    public void visit(ForEachStmt forEachStmt) {
        var iterableType = new RefType(ASTFactory.createClass(java.lang.Iterable.class).getClassName(), List.of(new ExtendsWildcardType(forEachStmt.statement.getType(), new NullToken())), new NullToken());
        constraintsSet.addUndConstraint(new Pair(forEachStmt.expression.getType(), iterableType, PairOperator.SMALLERDOT, loc(forEachStmt.getOffset())));
        forEachStmt.statement.accept(this);
        forEachStmt.expression.accept(this);
        forEachStmt.block.accept(this);
    }

    @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, loc(ifStmt.getOffset())));
        // 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, loc(ifStmt.getOffset())));
        }

    }

    @Override
    public void visit(InstanceOf instanceOf) {
        instanceOf.getExpression().accept(this);
    }

    @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
    // Es wird in OderConstraints davon ausgegangen dass die Bedingungen für die Typen der Argumente links stehen
    // und die Typen der Rückgabewerte immer rechts stehen (vgl. JavaTXCompiler)
    public void visit(MethodCall methodCall) {

        methodCall.receiver.accept(this);
        // Overloading:
        Set<Constraint<Pair>> methodConstraints = new HashSet<>();
        for (MethodAssumption m : TYPEStmt.getMethods(methodCall.name, methodCall.arglist, info)) {
            GenericsResolver resolver = getResolverInstance();
            Set<Constraint<Pair>> oneMethodConstraints = generateConstraint(methodCall, m, info, resolver);
            methodConstraints.addAll(oneMethodConstraints);

            /*
             * pl 2023-01-20: in generateConstraint bereits umgesetzt Constraint<Pair> extendsOneMethodConstraint = oneMethodConstraint.stream() .map(x -> (x.TA1 instanceof TypePlaceholder && x.GetOperator() == PairOperator.EQUALSDOT && !(x.TA2 instanceof TypePlaceholder)) ? new Pair(x.TA1, new ExtendsWildcardType(x.TA2, x.TA2.getOffset()), PairOperator.EQUALSDOT) : x) .collect(Collectors.toCollection(() -> new Constraint<Pair>(oneMethodConstraint.isInherited())));
             * oneMethodConstraint.setExtendConstraint(extendsOneMethodConstraint); extendsOneMethodConstraint.setExtendConstraint(oneMethodConstraint); methodConstraints.add(extendsOneMethodConstraint);
             */
        }
        if (methodConstraints.isEmpty()) {
            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.isEmpty()) {
            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());

    private final RefType charr = new RefType(ASTFactory.createClass(Character.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, loc(unaryExpr.getOffset())));
            // The type of the postfix increment expression is the type of the variable
            constraintsSet.addUndConstraint(new Pair(unaryExpr.expr.getType(), unaryExpr.getType(), PairOperator.EQUALSDOT, loc(unaryExpr.getOffset())));
        } else if (unaryExpr.operation == UnaryExpr.Operation.NOT) {
            constraintsSet.addUndConstraint(new Pair(unaryExpr.expr.getType(), unaryExpr.getType(), PairOperator.EQUALSDOT, loc(unaryExpr.getOffset())));
            constraintsSet.addUndConstraint(new Pair(unaryExpr.expr.getType(), new RefType(ASTFactory.createClass(java.lang.Boolean.class).getClassName(), new NullToken()), PairOperator.EQUALSDOT, loc(unaryExpr.getOffset())));
        } else {
            throw new NotImplementedException();
        }
    }

    @Override
    // Es wird in OderConstraints davon ausgegangen dass die Bedingungen für die Typen der Argumente links stehen
    // und die Typen der Rückgabewerte immer rechts stehen (vgl. JavaTXCompiler)
    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)
                || binary.operation.equals(BinaryExpr.Operator.OR)
                || binary.operation.equals(BinaryExpr.Operator.AND)
                || binary.operation.equals(BinaryExpr.Operator.XOR)) {

            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;
            HashSet<JavaClassName> classNames = TypeUnifyTaskHelper.getPresizedHashSet(info.getAvailableClasses().size());
            for (var classEl : info.getAvailableClasses()) {
                classNames.add(classEl.getClassName());
            }

            // PL eingefuegt 2018-07-17
            if (classNames.contains(bytee.getName())) {
                numeric = new Constraint<>();
                numeric.add(new Pair(binary.lexpr.getType(), bytee, PairOperator.SMALLERDOT, loc(binary.getOffset())));
                numeric.add(new Pair(binary.rexpr.getType(), bytee, PairOperator.SMALLERDOT, loc(binary.getOffset())));
                numeric.add(new Pair(binary.getType(), integer, PairOperator.EQUALSDOT, loc(binary.getOffset())));
                numericAdditionOrStringConcatenation.add(numeric);
            }
            // PL eingefuegt 2018-07-17
            if (classNames.contains(shortt.getName())) {
                numeric = new Constraint<>();
                numeric.add(new Pair(binary.lexpr.getType(), shortt, PairOperator.SMALLERDOT, loc(binary.getOffset())));
                numeric.add(new Pair(binary.rexpr.getType(), shortt, PairOperator.SMALLERDOT, loc(binary.getOffset())));
                numeric.add(new Pair(binary.getType(), integer, PairOperator.EQUALSDOT, loc(binary.getOffset())));
                numericAdditionOrStringConcatenation.add(numeric);
            }
            // PL eingefuegt 2018-07-17
            if (classNames.contains(integer.getName())) {
                numeric = new Constraint<>();
                numeric.add(new Pair(binary.lexpr.getType(), integer, PairOperator.SMALLERDOT, loc(binary.getOffset())));
                numeric.add(new Pair(binary.rexpr.getType(), integer, PairOperator.SMALLERDOT, loc(binary.getOffset())));
                numeric.add(new Pair(integer, binary.getType(), PairOperator.EQUALSDOT, loc(binary.getOffset())));
                numericAdditionOrStringConcatenation.add(numeric);
            }
            // PL eingefuegt 2018-07-17
            if (classNames.contains(longg.getName())) {
                numeric = new Constraint<>();
                numeric.add(new Pair(binary.lexpr.getType(), longg, PairOperator.SMALLERDOT, loc(binary.getOffset())));
                numeric.add(new Pair(binary.rexpr.getType(), longg, PairOperator.SMALLERDOT, loc(binary.getOffset())));
                numeric.add(new Pair(longg, binary.getType(), PairOperator.EQUALSDOT, loc(binary.getOffset())));
                numericAdditionOrStringConcatenation.add(numeric);
            }
            // PL eingefuegt 2018-07-17
            if (classNames.contains(floatt.getName())) {
                numeric = new Constraint<>();
                numeric.add(new Pair(binary.lexpr.getType(), floatt, PairOperator.SMALLERDOT, loc(binary.getOffset())));
                numeric.add(new Pair(binary.rexpr.getType(), floatt, PairOperator.SMALLERDOT, loc(binary.getOffset())));
                numeric.add(new Pair(floatt, binary.getType(), PairOperator.EQUALSDOT, loc(binary.getOffset())));
                numericAdditionOrStringConcatenation.add(numeric);
            }
            // PL eingefuegt 2018-07-17
            if (classNames.contains(doublee.getName())) {
                numeric = new Constraint<>();
                numeric.add(new Pair(binary.lexpr.getType(), doublee, PairOperator.SMALLERDOT, loc(binary.getOffset())));
                numeric.add(new Pair(binary.rexpr.getType(), doublee, PairOperator.SMALLERDOT, loc(binary.getOffset())));
                numeric.add(new Pair(doublee, binary.getType(), PairOperator.EQUALSDOT, loc(binary.getOffset())));
                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 (classNames.contains(string.getName())) {
                    Constraint<Pair> stringConcat = new Constraint<>();
                    stringConcat.add(new Pair(binary.lexpr.getType(), string, PairOperator.EQUALSDOT, loc(binary.getOffset())));
                    stringConcat.add(new Pair(binary.rexpr.getType(), string, PairOperator.EQUALSDOT, loc(binary.getOffset())));
                    stringConcat.add(new Pair(string, binary.getType(), PairOperator.EQUALSDOT, loc(binary.getOffset())));
                    numericAdditionOrStringConcatenation.add(stringConcat);
                }
            }
            if (numericAdditionOrStringConcatenation.isEmpty()) {
                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, loc(binary.getOffset())));
            constraintsSet.addUndConstraint(new Pair(binary.rexpr.getType(), number, PairOperator.SMALLERNEQDOT, loc(binary.getOffset())));
            // Rückgabetyp ist Boolean
            constraintsSet.addUndConstraint(new Pair(bool, binary.getType(), PairOperator.EQUALSDOT, loc(binary.getOffset())));

            // 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.EQUALSDOT, loc(binary.getOffset())));
        } else {
            throw new NotImplementedException();
        }
    }

    @Override
    public void visit(BoolExpression expr) {

        constraintsSet.addUndConstraint(new Pair(bool, expr.getType(), PairOperator.EQUALSDOT, loc(expr.getOffset())));
        constraintsSet.addUndConstraint(new Pair(bool, expr.lexpr.getType(), PairOperator.EQUALSDOT, loc(expr.getOffset())));
        constraintsSet.addUndConstraint(new Pair(bool, expr.rexpr.getType(), PairOperator.EQUALSDOT, loc(expr.getOffset())));

        // TODO
        return;
    }

    @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, loc(literal.getOffset())));
            return;
        }
        if (literal.value instanceof Byte) {
            constraintsSet.addUndConstraint(new Pair(literal.getType(), bytee, PairOperator.EQUALSDOT, loc(literal.getOffset())));
            return;
        }
        if (literal.value instanceof Float) {
            constraintsSet.addUndConstraint(new Pair(literal.getType(), floatt, PairOperator.EQUALSDOT, loc(literal.getOffset())));
            return;
        }
        if (literal.value instanceof Double) {
            constraintsSet.addUndConstraint(new Pair(literal.getType(), doublee, PairOperator.EQUALSDOT, loc(literal.getOffset())));
            return;
        }
        if (literal.value instanceof Long) {
            constraintsSet.addUndConstraint(new Pair(literal.getType(), longg, PairOperator.EQUALSDOT, loc(literal.getOffset())));
            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, loc(literal.getOffset())));
            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, loc(literal.getOffset())));
                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, loc(literal.getOffset())));
                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, loc(literal.getOffset())));
                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, loc(literal.getOffset())));
                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, loc(literal.getOffset())));
                oderConstraints.add(constraint);
            }
            constraintsSet.addOderConstraint(oderConstraints);
            // */
            return;
        }
        if (literal.value instanceof Short) {
            constraintsSet.addUndConstraint(new Pair(literal.getType(), shortt, PairOperator.EQUALSDOT, loc(literal.getOffset())));
            return;
        }
        if (literal.value instanceof Byte) {
            constraintsSet.addUndConstraint(new Pair(literal.getType(), bytee, PairOperator.EQUALSDOT, loc(literal.getOffset())));
            return;
        }
        if (literal.value instanceof Float) {
            constraintsSet.addUndConstraint(new Pair(literal.getType(), floatt, PairOperator.EQUALSDOT, loc(literal.getOffset())));
            return;
        }
        if (literal.value instanceof String) {
            constraintsSet.addUndConstraint(new Pair(literal.getType(), string, PairOperator.EQUALSDOT, loc(literal.getOffset())));
            return;
        }
        if (literal.value instanceof Boolean) {
            constraintsSet.addUndConstraint(new Pair(literal.getType(), bool, PairOperator.EQUALSDOT, loc(literal.getOffset())));
            return;
        }
        if (literal.value instanceof Character) {
            constraintsSet.addUndConstraint(new Pair(literal.getType(), charr, PairOperator.EQUALSDOT, loc(literal.getOffset())));
            return;
        }
        if (literal.value != null) {
            throw new NotImplementedException();
        }
    }

    @Override
    public void visit(Throw aThrow) {
        aThrow.expr.accept(this);
    }

    @Override
    public void visit(Ternary ternary) {
        ternary.cond.accept(this);
        ternary.iftrue.accept(this);
        ternary.iffalse.accept(this);
        constraintsSet.addUndConstraint(new Pair(ternary.cond.getType(), bool, PairOperator.EQUALSDOT));
        constraintsSet.addUndConstraint(new Pair(ternary.iftrue.getType(), ternary.getType(), PairOperator.SMALLERDOT));
        constraintsSet.addUndConstraint(new Pair(ternary.iffalse.getType(), ternary.getType(), PairOperator.SMALLERDOT));
    }

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

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

    @Override
    public void visit(Break aBreak) {

    }

    @Override
    public void visit(Continue aContinue) {

    }

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

    @Override
    public void visit(Super aSuper) {
        var superClass = info.getSuperClass();
        var params = new ArrayList<RefTypeOrTPHOrWildcardOrGeneric>();
        for (var gtv : superClass.getGenerics()) {
            params.add(new GenericRefType(gtv.getName(), aSuper.getOffset()));
        }
        var superType = new RefType(superClass.getClassName(), params, aSuper.getOffset());
        constraintsSet.addUndConstraint(new Pair(aSuper.getType(), superType, PairOperator.EQUALSDOT, loc(aSuper.getOffset())));
    }

    @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, loc(aThis.getOffset())));
    }

    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, loc(whileStmt.expr.getOffset())));
        // LoopBlock inferieren:
        whileStmt.loopBlock.accept(this);
    }

    @Override
    public void visit(DoStmt whileStmt) {
        RefType booleanType = new RefType(ASTFactory.createClass(java.lang.Boolean.class).getClassName(), new NullToken());
        whileStmt.expr.accept(this);
        constraintsSet.addUndConstraint(new Pair(whileStmt.expr.getType(), booleanType, PairOperator.EQUALSDOT, loc(whileStmt.expr.getOffset())));
        whileStmt.loopBlock.accept(this);
    }

    @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) {
        Set<Constraint<Pair>> methodConstraints = new HashSet<>();
        var clazz = info.getSuperClass();
        for (var ctor : clazz.getConstructors()) {
            var params = convertParams(ctor.getParameterList(), info);
            if (params.size() != superCall.arglist.getArguments().size()) continue;
            var assumption = new MethodAssumption(null, new Void(new NullToken()), params, createTypeScope(clazz, ctor), ctor.isInherited, false);

            GenericsResolver resolver = getResolverInstance();
            Set<Constraint<Pair>> oneMethodConstraints = generateConstraint(superCall, assumption, info, resolver);
            methodConstraints.addAll(oneMethodConstraints);
        }
        constraintsSet.addUndConstraint(new Pair(superCall.receiver.getType(), getReceiverType(info.getSuperClass(), getResolverInstance()), PairOperator.EQUALSDOT));
        constraintsSet.addOderConstraint(methodConstraints);
    }

    @Override
    public void visit(ThisCall thisCall) {
        Set<Constraint<Pair>> methodConstraints = new HashSet<>();
        for (var ctor : info.getCurrentClass().getConstructors()) {
            var params = convertParams(ctor.getParameterList(), info);
            if (params.size() != thisCall.arglist.getArguments().size()) continue;
            var assumption = new MethodAssumption(null, new Void(new NullToken()), params, createTypeScope(info.getCurrentClass(), ctor), ctor.isInherited, false);

            GenericsResolver resolver = getResolverInstance();
            Set<Constraint<Pair>> oneMethodConstraints = generateConstraint(thisCall, assumption, info, resolver);
            methodConstraints.addAll(oneMethodConstraints);
        }
        constraintsSet.addUndConstraint(new Pair(thisCall.receiver.getType(), getReceiverType(info.getCurrentClass(), getResolverInstance()), PairOperator.EQUALSDOT));
        constraintsSet.addOderConstraint(methodConstraints);
    }

    public static RefTypeOrTPHOrWildcardOrGeneric getReceiverType(ClassOrInterface receiver, GenericsResolver resolver) {
        if (receiver == null) return null;
        List<RefTypeOrTPHOrWildcardOrGeneric> params = new ArrayList<>();
        for (GenericTypeVar gtv : receiver.getGenerics()) {
            //Die Generics werden alle zu TPHs umgewandelt.
            params.add(resolver.resolve(new GenericRefType(gtv.getName(), new NullToken())));
        }
        RefTypeOrTPHOrWildcardOrGeneric receiverType;
        
        if (receiver instanceof FunNClass) {
            receiverType = new RefType(new JavaClassName(receiver.getClassName().toString() + "$$"), params, new NullToken()); // new FunN(params);
        } else {
            receiverType = new RefType(receiver.getClassName(), params, new NullToken());
        }

        return receiverType;
    }

    /*
     * METHOD CALL Section:
     */

    protected Set<Constraint<Pair>> generateConstraint(MethodCall forMethod, MethodAssumption assumption, TypeInferenceBlockInformation info, GenericsResolver resolver) {
        Constraint<Pair> methodConstraint, extendsMethodConstraint;
        methodConstraint = new Constraint<>(assumption.isInherited(), assumption.isOverridden());
        extendsMethodConstraint = new Constraint<>(assumption.isInherited(), assumption.isOverridden());// PL 2023-01-24: Ersetzt die Dopplung in visit(MethodCall)

        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 receiverType = assumption.getReceiverType(resolver);
        if (receiverType != null) {
            methodConstraint.add(new Pair(forMethod.receiver.getType(), receiverType, PairOperator.EQUALSDOT, loc(forMethod.getOffset())));// PL 2020-03-17 SMALLERDOT in EQUALSDOT umgewandelt, weil alle geerbten Methoden in den jeweilen Klassen enthalten sind.

            // PL 2023-01-24: dafuer ? extends receiverType noch ergaenzt
            extendsMethodConstraint.add(new Pair(forMethod.receiver.getType(), new ExtendsWildcardType(receiverType, receiverType.getOffset()), PairOperator.EQUALSDOT, loc(forMethod.getOffset())));
        }

        // gegenseite Verschraenkung der beiden Mengen von Typannahmen
        methodConstraint.setExtendConstraint(extendsMethodConstraint);
        extendsMethodConstraint.setExtendConstraint(methodConstraint);

        // Fuer Bytecodegenerierung PL 2020-03-09 wird derzeit nicht benutzt ANFANG
        // methodConstraint.add(new Pair(forMethod.receiverType, retType,
        // PairOperator.EQUALSDOT));
        // Fuer Bytecodegenerierung PL 2020-03-09 wird derzeit nicht benutzt ENDE

        methodConstraint.add(new Pair(assumption.getReturnType(resolver), forMethod.getType(), PairOperator.SMALLERDOT, loc(forMethod.getOffset())));
        extendsMethodConstraint.add(new Pair(assumption.getReturnType(resolver), forMethod.getType(), PairOperator.SMALLERDOT, loc(forMethod.getOffset())));

        // methodConstraint.add(new Pair(assumption.getReturnType(resolver), forMethod.getType(), PairOperator.EQUALSDOT));
        // extendsMethodConstraint.add(new Pair(assumption.getReturnType(resolver), forMethod.getType(), PairOperator.EQUALSDOT));

        if (forMethod.getType() instanceof TypePlaceholder tph)
            tph.setOrCons((byte) -1);// fuer Maximums-Bestimmung

        Set<Pair> parameterContraints = generateParameterConstraints(forMethod, assumption, info, resolver);

        methodConstraint.addAll(parameterContraints);
        extendsMethodConstraint.addAll(parameterContraints);

        Set<Pair> methodSignatureConstraint = generatemethodSignatureConstraint(forMethod, assumption, info, resolver);

        //context.logger().info("methodSignatureConstraint: " + methodSignatureConstraint);
        //context.logger().info("methodConstraint: " + methodConstraint);

        methodConstraint.setmethodSignatureConstraint(methodSignatureConstraint);
        extendsMethodConstraint.setmethodSignatureConstraint(methodSignatureConstraint);

        Set<Constraint<Pair>> ret = new HashSet<>();
        ret.add(methodConstraint);
        ret.add(extendsMethodConstraint);

        return ret;
    }

    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, loc(foMethod.getOffset())));

            // Fuer Bytecodegenerierung PL 2020-03-09 wird derzeit nicht benutzt ANFANG
            // ret.add(new Pair(foMethod.argTypes.get(i), assType, PairOperator.EQUALSDOT));
            // Fuer Bytecodegenerierung PL 2020-03-09 wird derzeit nicht benutzt ENDE
        }
        return ret;
    }

    protected Set<Pair> generatemethodSignatureConstraint(MethodCall foMethod, MethodAssumption assumption, TypeInferenceBlockInformation info, GenericsResolver resolver) {
        Set<Pair> ret = new HashSet<>();

        for (int i = 0; i < foMethod.arglist.getArguments().size(); i++) {
            // Zuordnung von MethoCall.signature (Argumenttypen) zu der Argumenttypen der ausgewaehlten Methode (assumption.params)
            ret.add(new Pair(resolver.resolve(foMethod.signature.get(i)), resolver.resolve(assumption.getArgTypes().get(i)), PairOperator.EQUALSDOT));

        }

        // Zuordnung von MethodCall.signature(ReturnType) zu dem ReturnType der ausgewaehlten Methode (assumption.returnType)
        ret.add(new Pair(resolver.resolve(foMethod.signature.getLast()), resolver.resolve(assumption.getReturnType()), PairOperator.EQUALSDOT));
        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.getLast();
            ret.add(new MethodAssumption(new FunNClass(funNParams), funNParams.getLast(), funNParams.subList(0, funNParams.size() - 1), new TypeScope() {
                @Override
                public Iterable<? extends GenericTypeVar> getGenerics() {
                    throw new NotImplementedException();
                }

                @Override
                public RefTypeOrTPHOrWildcardOrGeneric getReturnType() {
                    throw new NotImplementedException();
                }
            }, false, false));
        }
        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), m.isInherited, m.isImplemented));
                }
            }
        }
        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 (Pattern 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()) {
                        var params = convertParams(m.getParameterList(), info);
                        ret.add(new MethodAssumption(cl, cl.generateTypeOfThisClass(), params, createTypeScope(cl, m), m.isInherited, m.isImplemented));
                    }
                }
            }
        }
        return ret;
    }

    protected Constraint<Pair> generateConstructorConstraint(NewClass forConstructor, MethodAssumption assumption, TypeInferenceBlockInformation info, GenericsResolver resolver) {
        Constraint methodConstraint = new Constraint();
        // WELCHEN SINN MACHT DIESER CONSTRAINT???
        // Ist er nicht immer classname <. classname und damit redundant?
        methodConstraint.add(new Pair(assumption.getReturnType(resolver), forConstructor.getType(), PairOperator.SMALLERDOT, loc(forConstructor.getOffset())));
        // WELCHEN SINN MACHT DIESER CONSTRAINT???
        methodConstraint.addAll(generateParameterConstraints(forConstructor, assumption, info, resolver));

        Set<Pair> methodSignatureConstraint = generatemethodSignatureConstraint(forConstructor, assumption, info, resolver);
        methodConstraint.setmethodSignatureConstraint(methodSignatureConstraint);

        methodConstraint.addAll(methodSignatureConstraint);

        return methodConstraint;
    }

    @Override
    public void visit(Switch switchStmt) {
        switchStack.push(switchStmt);

        if(switchStmt.getSwitch().getType() instanceof TypePlaceholder){
            ((TypePlaceholder) switchStmt.getSwitch().getType()).setVariance(-1);
        }

        RefTypeOrTPHOrWildcardOrGeneric caseExpressionType = null;
        for (var child : switchStmt.getBlocks()) {
            for (var label : child.getLabels()) {
                if (!(label.getPattern() instanceof FormalParameter) && !(label.getType() instanceof Void)) {
                    if (caseExpressionType != null && !caseExpressionType.equals(label.getType()))
                        throw new TypeinferenceException("Case labels must all have the same Type if they are expressions", label);
                    caseExpressionType = label.getType();
                }
            }
        }
        if (caseExpressionType == null) {
            for (var child : switchStmt.getBlocks()) {
                for (var label : child.getLabels()) {
                    if (label.getPattern() == null) {
                        //context.logger().info("DefaultCase");
                    } else {
                        constraintsSet.addUndConstraint(
                                new Pair(
                                        label.getPattern().getType(),
                                        switchStmt.getSwitch().getType(),
                                        PairOperator.SMALLERDOT,
                                        loc(label.getOffset())
                                )
                        );
                    }
                }
            }
        } else {
            constraintsSet.addUndConstraint(new Pair(caseExpressionType, switchStmt.getSwitch().getType(), PairOperator.EQUALSDOT, loc(switchStmt.getSwitch().getOffset())));
        }

        if (caseExpressionType == null) {
            for (var child : switchStmt.getBlocks()) {
                for (var label : child.getLabels()) {
                    if (label.getPattern() == null) {
                        constraintsSet.addUndConstraint(new Pair(new RefType(new JavaClassName("java.lang.Object"), new NullToken()), switchStmt.getSwitch().getType(), PairOperator.SMALLERDOT, loc(label.getOffset())));
                    }
                    if (label.getPattern() instanceof RecordPattern) {
                        RecordPattern recordPattern = (RecordPattern) label.getPattern();
                        //constraintsSet.addUndConstraint(new Pair(label.getPattern().getType(), switchStmt.getSwitch().getType(), PairOperator.SMALLERDOT, loc(label.getOffset())));
                    } else if (label.getPattern() instanceof FormalParameter) {
                        constraintsSet.addUndConstraint(new Pair(label.getPattern().getType(), switchStmt.getSwitch().getType(), PairOperator.SMALLERDOT, loc(label.getOffset())));
                    }
                }
            }
        } else {
            constraintsSet.addUndConstraint(new Pair(caseExpressionType, switchStmt.getSwitch().getType(), PairOperator.EQUALSDOT, loc(switchStmt.getSwitch().getOffset())));
        }


        for (var child : switchStmt.getBlocks()) {


            child.getLabels().forEach(el -> {
                if (el.getType() instanceof RefType) {
                  if (el.getPattern() instanceof RecordPattern pattern) {
                    recursivelyAddRecordConstraints(pattern);
                  }
                }
            });

            constraintsSet.addUndConstraint(new Pair(child.getType(), switchStmt.getType(), PairOperator.SMALLERDOT, loc(switchStmt.getOffset())));
            child.accept(this);
        }


        switchStack.pop();
    }

    public void recursivelyAddRecordConstraints(RecordPattern pattern) {

        var allClasses = info.getAvailableClasses();
        var interestingClasses = allClasses.stream().filter(as -> as.getClassName().equals(((RefType) pattern.getType()).getName())).toList();
        var constructors = interestingClasses.getFirst().getConstructors();

        int counter = 0;

        for (var subPattern : pattern.getSubPattern()) {
            for (Constructor con : constructors) {
                //context.logger().info("----------------------\n" + subPattern.getType() + " | " + con.getParameterList().getParameterAt(counter).getType() + "\n----------------------\n");
                constraintsSet.addUndConstraint(new Pair(subPattern.getType(), con.getParameterList().getParameterAt(counter).getType(), PairOperator.SMALLERDOT, loc(con.getParameterList().getParameterAt(counter).getOffset())));
            }
            if (subPattern instanceof RecordPattern) recursivelyAddRecordConstraints((RecordPattern) subPattern);
            else if (subPattern instanceof LiteralPattern lp) lp.value.accept(this);
            counter++;
        }
    }


    @Override
    public void visit(SwitchBlock switchBlock) {
        for (var stmt : switchBlock.statements) {
            stmt.accept(this);
        }
    }

    @Override
    public void visit(SwitchLabel switchLabel) {
    }

    @Override
    public void visit(Yield aYield) {
        aYield.retexpr.accept(this);
        constraintsSet.addUndConstraint(new Pair(aYield.getType(), switchStack.peek().getType(), PairOperator.SMALLERDOT, loc(aYield.getOffset())));
        // TODO Auto-generated method stub
    }
}
