package de.dhbwstuttgart.strucTypes5.algo;

/**
 * Created by sebastian on 21.04.17.
 */



import de.dhbwstuttgart.strucTypes5.constraints.*;
import de.dhbwstuttgart.strucTypes5.interfaceTemplates.FieldInterface;
import de.dhbwstuttgart.strucTypes5.interfaceTemplates.Interface;
import de.dhbwstuttgart.strucTypes5.interfaceTemplates.MethodInterface;
import de.dhbwstuttgart.strucTypes5.typeVars.*;
import de.dhbwstuttgart.syntaxtree.*;


import java.util.*;

/**
 * Takes a Typled Class and a Constraint Set for construct the Interfaces
 * => typed class = cl + typVarStore + List with Constraints
 */
public class ConstructInterfaceTemplates {

    List<Interface> interfaceList = new ArrayList<>();
    List<ConstraintAbstract> remainingConstraints = new ArrayList<>();


    public ConstructInterfaceTemplates(ClassOrInterface classOrInterface, TypeVarStore typeVarStore , List<ConstraintAbstract> constraints, MappingAltNeu mappingAltNeu) {

        // Sort the Constriants
        Map<TypeVar, Set<ConstraintAbstract>> constraintSet =  sortConstriants(constraints, mappingAltNeu);

        // Generate a Interface for every StructuralType
        for (TypeVar typeVar  : constraintSet.keySet()) {
            interfaceList.add(generateInterface(typeVar , constraintSet.get(typeVar) , typeVarStore , mappingAltNeu));
        }

    }





    /**
     *
     * @param constraints
     * @return
     *
     * Method sortet Constraints for ReceiverType for the Structural Type.
     * Other Constraints as the Field or MethodsConstraints are copied to the global List remainungConstraints
     *
     */
    private Map<TypeVar,Set<ConstraintAbstract>> sortConstriants(List<ConstraintAbstract> constraints , MappingAltNeu mappingAltNeu) {
        Map<TypeVar, Set<ConstraintAbstract>> sortetConstraints  = new HashMap<>();

        for (ConstraintAbstract constraint : constraints) {

            if (constraint instanceof ConstraintMethod) {
                ConstraintMethod constraintMethod = (ConstraintMethod) constraint;

                if (sortetConstraints.containsKey(constraintMethod.getReceiver())) {
                    sortetConstraints.get(constraintMethod.getReceiver()).add(constraintMethod);
                }
                else {
                    Set<ConstraintAbstract> constraintSet = new HashSet<>();
                    constraintSet.add(constraint);
                    sortetConstraints.put(constraintMethod.getReceiver() , constraintSet);
                }

            }
            else if (constraint instanceof ConstraintField ) {
                ConstraintField constraintField = (ConstraintField) constraint;


                if (sortetConstraints.containsKey(constraintField.getReceiver())) {
                    sortetConstraints.get(constraintField.getReceiver()).add(constraintField);
                }
                else {
                    Set<ConstraintAbstract> constraintSet = new HashSet<>();
                    constraintSet.add(constraint);
                    sortetConstraints.put(constraintField.getReceiver() , constraintSet);
                }

            }
            else if (constraint instanceof ConstraintSubType) {
                ConstraintSubType constraintSubType = (ConstraintSubType) constraint;
                TypeVar lower = (TypeVar) mappingAltNeu.getNeu(constraintSubType.getSubtype());
                TypeVar higher = (TypeVar) mappingAltNeu.getNeu(constraintSubType.getSuperType());
                remainingConstraints.add(new ConstraintSubType(lower , higher));
            }
            else {
                //System.out.println(constraint.getClass());
                remainingConstraints.add(constraint);
            }
        }
        return sortetConstraints;
    }


    /**
     * Method Generates Interfaces for the Structural Type.
     * The Method also adds the new Constraints to the remaining Constraints List.
     *
     */
    private Interface generateInterface(TypeVar receiver , Set<ConstraintAbstract> constraintSet , TypeVarStore typeVarStore , MappingAltNeu mappingAltNeu) {

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


        for (ConstraintAbstract constraint : constraintSet) {
            if (constraint instanceof ConstraintField) {
                ConstraintField cf = (ConstraintField) constraint;
                fields.add(new FieldInterface(  typeVarStore.makeFreshTypeVar(), cf.getNameOfField() ));
                // ToDo make constraint
            }
            else if (constraint instanceof  ConstraintMethod) {
                ConstraintMethod cm = (ConstraintMethod) constraint;

                // Erstelle Generics für Constraint
                genericsForConstraint.add(mappingAltNeu.getNeu(cm.getReturnType()));
                for (TypeVar tv : cm.getArgumentTypesBeta()) {
                    genericsForConstraint.add(mappingAltNeu.getNeu(tv));
                }

                // Erstelle Generic für neuen Typvariablen für das Interface
                List<TypeVarAbstract> args = new ArrayList<>();
                for (TypeVar tv : cm.getArgumentTypes()) {
                    TypeVar generic = typeVarStore.makeFreshTypeVar();
                    args.add(generic);
                }
                TypeVar rtype = typeVarStore.makeFreshTypeVar();


                MethodInterface methodInterface = new MethodInterface(cm.getMethodName(), rtype , args );

                // Erstelle Generics für das Interface
                generics.add(rtype);
                generics.addAll(args);

                methods.add(methodInterface);
            }
        }




        TypeVar neuerTypeReveiver = (TypeVar) mappingAltNeu.getNeu(receiver);
        InterfaceForConstraint interfaceForConstraint = new InterfaceForConstraint(receiver, genericsForConstraint);

        TypeVarAbstract tSub = neuerTypeReveiver;
        TypeVarAbstract tSuper = TypeVarFactory.makeTypeVar(interfaceForConstraint);

        ConstraintSubType newConstraint = new ConstraintSubType(tSub, tSuper);
        remainingConstraints.add(newConstraint);

        // Old Version
        //remainingConstraints.add(new ConstraintSubTypeGeneric<>(neuerTypeReveiver , interfaceForConstraint ));

        Interface newInterface = new Interface(receiver, fields, methods , generics);
        return newInterface;
    }












}
