package de.dhbwstuttgart.strucTypes4.algo;

import de.dhbwstuttgart.strucTypes4.syntaxtree.*;
import de.dhbwstuttgart.strucTypes4.syntaxtree.Class;
import de.dhbwstuttgart.strucTypes4.typeVars.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by sebastian on 15.04.17.
 */
public class Construct2 {

   public List<ConstraintAbstract> neueConstraits;

    public Class  construct(Class aClass, List<ConstraintAbstract> constraints) {

        //neue constriants
        List<ConstraintAbstract> constraintsNeu = new ArrayList<>();

        //neue Interfaces
        List<Interface> interfaceList = new ArrayList<>();

        // Sammeln der Constriants
        Map<TypeVarAbstract,List<ConstraintAbstract>> sortetConstr = sortiereConstraints(constraints);


        // Erstellen der Interfaces
        // einmal alle Constraints durchlaufen und neue TV anlegen.


        ResultTuple resultTuple = aClass.typeVarStore.freshVariableAndMapping();
        TypeVarStore tvsNeu = resultTuple.getTypeVarStore();
        MappingAltNeu mapAN = resultTuple.getMappingAltNeu();


        // Erstelle neue SubConstraitnts
        for (ConstraintAbstract constraint : constraints) {
            if (constraint.getClass().equals(ConstraintSubType.class)) {
                ConstraintSubType constraintSubType = (ConstraintSubType) constraint;
                TypeVarAbstract kleiner = mapAN.getNeu(constraintSubType.getTypeKleiner());
                TypeVarAbstract groeßer = mapAN.getNeu(constraintSubType.getTypeGroeßer());
                ConstraintSubType cNeu = new ConstraintSubType(kleiner,groeßer);
                constraintsNeu.add(cNeu);
            }
        }


        //Kopiere constraints von den Interfaces
        for (ConstraintAbstract constraint :constraints) {
            if (constraint.getClass().equals(ConstraintSubTypeInterface.class)) {
                constraintsNeu.add(constraint);
            }
        }


        // Erstelle constraints + Interfaces für den strukturellen Typen
        for (TypeVarAbstract typeVar  : sortetConstr.keySet()) {
            ConstructInterfaceForType2 constructInterfaceForType2 = new ConstructInterfaceForType2(sortetConstr.get(typeVar),typeVar,mapAN.getNeu(typeVar) , mapAN);
            interfaceList.add(constructInterfaceForType2.getResultInterface());
            constraintsNeu.addAll(constructInterfaceForType2.getResultConstraints());
        }


        Class bClass = aClass;
        bClass.typeVarStore = tvsNeu;
        bClass.setInterfaces(interfaceList);
        neueConstraits = constraintsNeu;
        bClass.setConstraintSet(neueConstraits);
        return bClass;
    }


    // Sortieren der Constriants nach Strukturellem Typ
    private Map<TypeVarAbstract,List<ConstraintAbstract>> sortiereConstraints(List<ConstraintAbstract> constraints) {

        Map<TypeVarAbstract,List<ConstraintAbstract>> constraintMap = new ConcurrentHashMap<>();

        for (ConstraintAbstract con : constraints) {

            TypeVarAbstract receiver = null;

            // Suche den Receiver der Metohde oder des Feldes
            if (con.getClass().equals(ConstraintMethod.class)) {
                ConstraintMethod cm = (ConstraintMethod) con;
                receiver = cm.getReceiver();
            }
            else if (con.getClass().equals(ConstraintField.class)) {
                ConstraintField cf = (ConstraintField) con;
                receiver = cf.getReceiver();
            }


            // wenn Receiver vorhanden -> füge ihn in die Map ein;
            if (receiver != null ) {
                if (constraintMap.containsKey(receiver)) {
                    constraintMap.get(receiver).add(con);
                }
                else {
                    List<ConstraintAbstract> clist = new ArrayList<>();
                    clist.add(con);
                    constraintMap.put(receiver,clist);
                }
            }
        }
        return constraintMap;
    }
}



class ConstructInterfaceForType2 {

    private Interface resultInterface;
    private List<ConstraintAbstract> resultConstraints;


    public ConstructInterfaceForType2(List<ConstraintAbstract> constraints , TypeVarAbstract receiverAlt , TypeVarAbstract receiverNeu , MappingAltNeu altNeu) {


        resultConstraints = new ArrayList<>();

        List<FieldInterface> fields = new ArrayList<>();
        List<MethodInterface> methods = new ArrayList<>();
        List<TypeVarAbstract> genericsInterface = new ArrayList<>();


        // Erstelle Felder sowie Methoden des Interfaces
        for (ConstraintAbstract constraint : constraints) {

            // Erstelle Felder
            if (constraint.getClass().equals(ConstraintField.class)) {
                ConstraintField constraintField = (ConstraintField) constraint;
                FieldInterface fieldInterface = new FieldInterface( constraintField.getCaller(), constraintField.getNameField()  );
                fields.add(fieldInterface);
            }


            // Erstelle Methoden
            if (constraint.getClass().equals(ConstraintMethod.class)) {
                ConstraintMethod constraintMethod = (ConstraintMethod) constraint;
                TypeVarAbstract rtType = TypeVarFactory.makeTypeVar();

                List<TypeVarAbstract> neueArgsTV = new ArrayList<>();
                for (TypeVarAbstract tv  :  constraintMethod.getArgsTypes()) {
                    neueArgsTV.add(TypeVarFactory.makeTypeVar());
                }

                genericsInterface.add(rtType);
                genericsInterface.addAll(neueArgsTV);
                resultConstraints.add(new ConstraintSubTypeInterface(receiverNeu , receiverAlt , genericsInterface));



                MethodInterface methodInterface = new MethodInterface(constraintMethod.getMethodName() , rtType, neueArgsTV);
                methods.add(methodInterface);
            }
        }
        resultInterface = new Interface( receiverAlt, fields,methods, genericsInterface );
    }


    public Interface getResultInterface() {
        return resultInterface;
    }

    public void setResultInterface(Interface resultInterface) {
        this.resultInterface = resultInterface;
    }

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

    public void setResultConstraints(List<ConstraintAbstract> resultConstraints) {
        this.resultConstraints = resultConstraints;
    }
}





