package de.dhbwstuttgart.strucTypes5.algo;

import de.dhbwstuttgart.strucTypes5.assumptions.AssumptionClass;
import de.dhbwstuttgart.strucTypes5.assumptions.AssumptionMakerLocal;
import de.dhbwstuttgart.strucTypes5.assumptions.AssumptionMap;
import de.dhbwstuttgart.strucTypes5.assumptions.ChangeTypeVars;
import de.dhbwstuttgart.strucTypes5.ausgabe.Class2String;
import de.dhbwstuttgart.strucTypes5.constraints.*;
import de.dhbwstuttgart.strucTypes5.typeVars.TypeVar;
import de.dhbwstuttgart.strucTypes5.typeVars.TypeVarAbstract;
import de.dhbwstuttgart.strucTypes5.typeVars.TypeVarFactory;
import de.dhbwstuttgart.strucTypes5.typeVars.TypeVarStore;
import de.dhbwstuttgart.syntaxtree.Method;
import de.dhbwstuttgart.syntaxtree.statement.*;
import de.dhbwstuttgart.syntaxtree.type.RefType;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;



import java.util.ArrayList;
import java.util.List;

/**
 * Created by sebastian on 20.04.17.
 */
public class TypeExpr {

    private TypeVarStore typeVarStore;
    private AssumptionMap assumptionMap;
    private List<ConstraintAbstract> resultConstraints;


    public TypeExpr(AssumptionMap assumptionAbstractList , Method method, TypeVarStore typeVarStore) {
        this.typeVarStore = typeVarStore;
        this.assumptionMap = makeAllAssumptions(assumptionAbstractList,method);

        if (method.get_Block().getStatements().get(0) instanceof  Return) {
            Return rt = (Return) method.get_Block().getStatements().get(0);

            // Adds the Relation    TypVariable Method == TypVariable Return Expression
            TypeVarAbstract typeVarMethod = typeVarStore.getTypeVarByTPH(method.getType());
            typeVarStore.addTuple(typeVarMethod, rt.getType());


            this.resultConstraints = typeExpression(rt.get_Expression());
        }
        else {
            System.err.println("Fehler???");
        }
    }


    private AssumptionMap makeAllAssumptions(AssumptionMap assumptionsAll , Method method) {

        // Globale Assumptions (erst kurz hinzugefügt)
        AssumptionMap assumptionMap = assumptionsAll;


        //Lokale Assumptions
        AssumptionMakerLocal assumptionMakerLocal = new AssumptionMakerLocal(method,typeVarStore);
        assumptionMap.addAll(assumptionMakerLocal.getResultAssumptionList());
        return assumptionMap;
    }


    private List<ConstraintAbstract> typeExpression(Expression expression) {

        if (expression instanceof LocalVar) {
            LocalVar localVar = (LocalVar) expression;
            // Erstellung keines Constriants
            // Lediglich eintrag in TypeVarStore
            TypeVarAbstract typeVar = assumptionMap.getVarAssumption(localVar.get_expression());
            typeVarStore.addTuple(typeVar,localVar.getType());
            List<ConstraintAbstract> result = new ArrayList<>();
            return result;
        }
        else if (expression instanceof FieldVar) {
            FieldVar fieldVar = (FieldVar) expression;
            List<ConstraintAbstract> result =  typeExpression(fieldVar.getReceiver());
            result.add(ConstraintFactory.generateConstriantField(fieldVar,typeVarStore));

            return result;
        }
        else if (expression instanceof MethodCall) {
            MethodCall methodCall = (MethodCall) expression;

            // Call of non-Abstract Field do not add constraints
            if (methodCall.get_Receiver().getType() instanceof RefType) {
                List<ConstraintAbstract> result = typeExpression(methodCall.get_Receiver());
                typeVarStore.storeTPH(methodCall.getType());
                // TypeExpr für die Argumente
                for (Expression arg : methodCall.get_ArgList().getArguments()) {
                    result.addAll(typeExpression(arg));
                }


                //  zuordnen der Variablen zu den ermittelten typen
                AssumptionClass assumptionClass = assumptionMap.getClassAssumption(methodCall.get_Receiver().getType().toString());
                if (assumptionClass != null) {
                    Method m = assumptionClass.getCl().getMethods().get(0);
                    for (int i = 0; i < methodCall.get_ArgList().getArguments().size()  ; i++) {
                        // Verknüpfen von typeVar und typeVar
                        RefTypeOrTPHOrWildcardOrGeneric tmold = m.getParameterList().getFormalparalist().get(i).getType();
                        RefTypeOrTPHOrWildcardOrGeneric takt = methodCall.get_ArgList().getArguments().get(i).getType();
                        //System.out.println(assumptionClass.getTypeVarStore().getTypeVarByTPH(tmold) +  "<" +  typeVarStore.getTypeVarByTPH(takt));
                        TypeVar x = (TypeVar) assumptionClass.getTypeVarStore().getTypeVarByTPH(tmold);
                        TypeVar y = (TypeVar) typeVarStore.getTypeVarByTPH(takt);
                        List<ConstraintAbstract> foroldConstraints = new ArrayList<>();
                        foroldConstraints.add(new ConstraintSubType(y,x));
                        OldConstraints oldConstraints = new OldConstraints(foroldConstraints);
                        result.add(oldConstraints);
                    }
                }

                return result;
            }
            else {
                List<ConstraintAbstract> result = typeExpression(methodCall.get_Receiver());

                // TypeExpr für die Argumente
                for (Expression arg : methodCall.get_ArgList().getArguments()) {
                    result.addAll(typeExpression(arg));
                }
                result.addAll(ConstraintFactory.generateConstraintMethod(methodCall, typeVarStore));
                return result;
            }
        }
        else if (expression instanceof Receiver) {
            Receiver receiver = (Receiver) expression;
            List<ConstraintAbstract> result = typeExpression(receiver.get_Expression());
            return result;
        }
        else if (expression instanceof NewClass) {
            NewClass newClass = (NewClass) expression;

            // Argumente
            List<ConstraintAbstract> result = new ArrayList<>();

            // TypeExpr für die Argumente
            for (Expression arg : newClass.getArgumentList().getArguments()) {
                result.addAll(typeExpression(arg));
            }
;



            /*
            Information:
            Als erstes werden die Typvariablen verwendet die bereits zuvor in Construct eingefügt wurden.
            Danach wird die Assumption ausgetauscht mit einer neuen Klasse mit neuen Typvariablen.
            Diese können dann verwendet werden.
             */


            //Versuche Informationen zu laden
            AssumptionClass assumptionClass = assumptionMap.getClassAssumption(newClass.getType().toString());
            if (assumptionClass != null) {

                ChangeTypeVars changeTypeVars = new ChangeTypeVars();
                assumptionClass = changeTypeVars.change(assumptionClass);
                assumptionMap.putClass(assumptionClass);

                OldConstraints oldConstraints= new OldConstraints(assumptionClass.getConstraints());
                result.add(oldConstraints);

                System.out.println("-----------------KlassenAssumption ------");
                System.out.println(assumptionClass.getTypeVarStore().toString());
                Class2String class2String = new Class2String();
                System.out.println(class2String.generateStringTypeVars(assumptionClass.getCl(), assumptionClass.getTypeVarStore()));
                System.out.println("-----------------------");

                //ChangeTypeVars changeTypeVars = new ChangeTypeVars();
                //AssumptionClass assumptionClass1 = changeTypeVars.change(assumptionClass);

                // ToDO Problem mit den neuen Typvariablen da die infos nochmals geladen werden beim Method call !!!
                // (Das einige logische wäre bereits davor die TypVariablen zu tauschen -> So steht es ja eigentlich auf im Skript
                //assumptionMap.putClass(assumptionClass1);
            }





            /*
            //Versuche Informationen zu laden
            AssumptionClass assumptionClass = assumptionMap.getClassAssumption(newClass.getType().toString());
            if (assumptionClass != null) {

                //  an dieser Stelle müssen die Variablen getauscht werden
                ChangeTypeVars changeTypeVars = new ChangeTypeVars();
                AssumptionClass assumptionClass1 = changeTypeVars.change(assumptionClass);
                OldConstraints oldConstraints = new OldConstraints(assumptionClass1.getConstraints());
                result.add(oldConstraints);

                // print new generatet class
                System.out.println("-----------------neue Klasse ------");
                Class2String class2String = new Class2String();

                System.out.println(class2String.generateStringTypeVars(assumptionClass1.getCl(), assumptionClass1.getTypeVarStore()));
                System.out.println("-----------------------");
               }
               */


            typeVarStore.addTuple(TypeVarFactory.makeTypeVar() , newClass.getType());
            return result;
        }
        else {
            System.err.println("Fehler unbekannt");
            System.out.println(expression);
            List<ConstraintAbstract> noResult = new ArrayList<>();
            return noResult;
        }
    }


    public List<ConstraintAbstract> getResultConstraints() {
        return resultConstraints;
    }

    public TypeVarStore getTypeVarStore() {
        return typeVarStore;
    }

}
