package de.dhbwstuttgart.strucTypes5.algo;


import com.sun.org.apache.xpath.internal.SourceTree;
import de.dhbwstuttgart.strucTypes5.assumptions.AssumptionClass;
import de.dhbwstuttgart.strucTypes5.assumptions.AssumptionMap;
import de.dhbwstuttgart.strucTypes5.ausgabe.Class2String;
import de.dhbwstuttgart.strucTypes5.constraints.ConstraintAbstract;
import de.dhbwstuttgart.strucTypes5.constraints.ConstraintSubType;
import de.dhbwstuttgart.strucTypes5.constraints.OldConstraints;
import de.dhbwstuttgart.strucTypes5.solve.Rules;
import de.dhbwstuttgart.strucTypes5.typeVars.ResultTuple;
import de.dhbwstuttgart.strucTypes5.typeVars.TypeVar;
import de.dhbwstuttgart.strucTypes5.typeVars.TypeVarAbstract;
import de.dhbwstuttgart.strucTypes5.typeVars.TypeVarStore;
import de.dhbwstuttgart.syntaxtree.ClassOrInterface;
import de.dhbwstuttgart.syntaxtree.FormalParameter;
import de.dhbwstuttgart.syntaxtree.Method;
import de.dhbwstuttgart.typeinference.constraints.Constraint;

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

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

    public AssumptionMap assresult;

    // Nur zum Test
    public List<ConstraintAbstract> resultConstraints;


    // Start TypeInference Algorithm, need Assumptions and  a Class
    public TI(AssumptionMap ass , ClassOrInterface cl) {


        // ----- Type -----
        TypeVarStore typeVarStore = new TypeVarStore();
        Type type = new Type(ass , cl, typeVarStore);
        ausgabe(cl);
        System.out.println("TypVarStore 1 for: " + cl.getClassName().toString());
        System.out.println(typeVarStore);
        ausgabe2(cl,typeVarStore);
        System.out.println(type.constraintList);

        // Neue TypVariablen
        ResultTuple tp = typeVarStore.freshVariableAndMapping();

        // ---- Construct ----
        // Einfache Implementierung -> Es werden die erforderlichen Daten für die Constraits und Interface zusammengetragen
        ConstructInterfaceTemplates constructInterfaceTemplates = new ConstructInterfaceTemplates(cl, tp.getTypeVarStore(), type.constraintList , tp.getMappingAltNeu());
        System.out.println("Generierte Interfaces");
        System.out.println(constructInterfaceTemplates.interfaceList);
        System.out.println("Generierte Constraints");
        System.out.println(constructInterfaceTemplates.remainingConstraints);


        // Generiere Generics für die Klasse
        List<TypeVarAbstract> generics = new ArrayList<>();
        for (Method m : cl.getMethods()) {
            generics.add(tp.getTypeVarStore().getTypeVarByTPH(m.getType()));
            for (FormalParameter fp : m.getParameterList().getFormalparalist()) {
                generics.add(tp.getTypeVarStore().getTypeVarByTPH(fp.getType()));
            }
        }


        // -------- solve kommen ------------------------

        // Hier muss noch die Solve Funktion auf die Constraints angewendet werden
        // Eine erste auflösung mit dem TypeVarstore
        List<ConstraintAbstract> resultConstraints = constructInterfaceTemplates.remainingConstraints;
        List<ConstraintAbstract> newResultConstraints = new ArrayList<>();
        TypeVarStore aktTVS = typeVarStore;

        for (ConstraintAbstract cs : resultConstraints) {
            if (cs instanceof ConstraintSubType) {
                List<ConstraintAbstract> cslist = new ArrayList<>();
                cslist.add(cs);
                newResultConstraints.addAll(bearbeiteConstraints(cslist,aktTVS));
            }
            else if (cs instanceof OldConstraints) {
                OldConstraints oldConstraints = (OldConstraints) cs;
                newResultConstraints.addAll(bearbeiteConstraints(oldConstraints.constraintAbstractList , aktTVS));
            }
        }
        System.out.println("\n \n Alte Constraints: ");
        System.out.println(resultConstraints);
        System.out.println("Neue Result Constraints: ");
        System.out.println(newResultConstraints);


        //OldConstraints newOldConstraints = new OldConstraints(newResultConstraints);





        //  -------- Das Ergebnis aus Solve wird in die Assumptions gespeichert zu weiteren Verwendung ------------------------


        // Assumption Map für weitere Anwendungen
        AssumptionClass assumptionClass = new AssumptionClass(cl,tp.getTypeVarStore(),ass, newResultConstraints , constructInterfaceTemplates.interfaceList, generics);
        AssumptionMap assumptionMapNew = new AssumptionMap();
        assumptionMapNew.addAssumption(assumptionClass);
        assresult = assumptionMapNew;
        System.out.println("Typvarstore neue Variablen");
        System.out.println(tp.getTypeVarStore());
        ausgabe2(cl,tp.typeVarStore);
        System.out.println("generics");
        System.out.println(generics);
        //System.out.println(constructInterfaceTemplates.remainingConstraints);



        // Result of Construct Basis für die Unifikation
        System.out.println("Result of Solve: ");
        System.out.println(newResultConstraints);
        this.resultConstraints = newResultConstraints;




    }



    public void ausgabe(ClassOrInterface cl) {
        Class2String cs = new Class2String();
        String s = cs.generateStringTPH(cl);
        System.out.println(s);
    }


    public void ausgabe2(ClassOrInterface cl , TypeVarStore ts) {
        Class2String cs = new Class2String();
        String s  = cs.generateStringTypeVars(cl,ts);
        System.out.println(s);
    }




    public List<ConstraintAbstract> bearbeiteConstraints(List<ConstraintAbstract> constraints , TypeVarStore typeVarStore) {

        List<ConstraintAbstract> newResultConstraints = new ArrayList<>();
        for (ConstraintAbstract rc : constraints) {

            if (rc instanceof ConstraintSubType) {
                ConstraintSubType cSub = (ConstraintSubType) rc;

                TypeVarAbstract tSubNeu = null;
                TypeVarAbstract tSuperNeu = null;

                // Check for Klasse
                TypeVarAbstract tSub = cSub.getSubtype();
                TypeVarAbstract tSuper = cSub.getSuperType();

                if (tSub instanceof TypeVar) {
                    tSubNeu = typeVarStore.isType((TypeVar) tSub);
                }
                else {
                    tSubNeu = tSub;
                }

                if (tSuper instanceof TypeVar) {
                    tSuperNeu = typeVarStore.isType((TypeVar) tSuper);
                }
                else {
                    tSuperNeu = tSuper;
                }

                newResultConstraints.add(new ConstraintSubType(tSubNeu, tSuperNeu));
            }
            else {
                newResultConstraints.add(rc);
            }
        }
        return newResultConstraints;
    }
}
