package de.dhbwstuttgart.typedeployment;

import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.syntaxtree.type.*;
import de.dhbwstuttgart.typeinference.result.*;
import org.antlr.v4.runtime.Token;

import java.util.*;

/**
 * TODO:
 * Falls in Feldern Generics entstehen, dann werden diese als Klassenparameter eingesetzt
 * Für die Instanzierung von Klassen kann man dann beispielsweise nur noch den Diamond-Operator verwenden
 *
 * Es müssen zu einem TPH alle in Beziehung stehenden Constraints gefunden werden
 *
 * Anmekung: Es wird nur ein RefType gleichzeitug eingesetzt.
 * Steht dieser mit anderen Typen in Verbindung, so müssen diese nicht eingesetzt werden
 * im Result set können nur TPHs mit <. Beziehung stehen
 * Steht ein Typ A über TPHs mit anderen Typen B in Verbindung, so lassen sich diese auch im nächsten Durchgang
 * inferieren, wenn A bereits eingesetzt wurde. Es werden dann eben zusätzliche Generics entstehen
 */
public class TypeInsertFactory {


    public static Set<TypeInsert> createTypeInsertPoints(SourceFile forSourcefile, ResultSet withResults){
        return new TypeInsertPlacer().getTypeInserts(forSourcefile, withResults);
    }

    public static TypeInsert createInsertPoints(RefTypeOrTPHOrWildcardOrGeneric type, Token offset, ClassOrInterface cl, Method m,
                                                ResultSet resultSet) {
        ResolvedType resolvedType = resultSet.resolveType(type);
        TypeInsertPoint insertPoint = new TypeInsertPoint(offset,
                    new TypeToInsertString(resolvedType.resolvedType).insert);
        return new TypeInsert(insertPoint, new HashSet<>());
    }

    private static TypeInsertPoint createGenericInsert(Set<GenericInsertPair> toInsert, ClassOrInterface cl, Method m){
        //Momentan wird Methode ignoriert. Parameter werden immer als Klassenparameter angefügt:
        //Offset zum Einstzen bestimmen:
        Token offset;
        String insert = "";
        String end;
        if(cl.getGenerics().iterator().hasNext()){
            //offset = cl.getGenerics().iterator().next().getOffset();
            offset = cl.getGenerics().getOffset();
            end=",";
        }else{
            offset = cl.getGenerics().getOffset();
            insert += "<";
            end = ">";
        }

        //Alle einzusetzenden Generics und deren Bounds bestimmen:
        HashMap<TypePlaceholder, HashSet<TypePlaceholder>> genericsAndBounds = new HashMap<>();
        for(GenericInsertPair p : toInsert){
            if(!genericsAndBounds.containsKey(p.TA1)){
                genericsAndBounds.put((TypePlaceholder) p.TA1, new HashSet<>());
            }
            if(p.TA2 != null){
                genericsAndBounds.get(p.TA1).add((TypePlaceholder) p.TA2);
                if(!genericsAndBounds.containsKey(p.TA2)){
                    genericsAndBounds.put((TypePlaceholder) p.TA2, new HashSet<>());
                }
            }
        }

        //String zum Einsetzen (Generics mit bounds) generieren:
        Iterator<TypePlaceholder> it = genericsAndBounds.keySet().iterator();
        if(! it.hasNext())return new TypeInsertPoint(offset, "");
        while(it.hasNext()){
            TypePlaceholder tph = it.next();
            insert += tph.getName();
            Set<TypePlaceholder> bounds = genericsAndBounds.get(tph);
            if(bounds.size() > 0){
                insert += " extends ";
                Iterator<TypePlaceholder> boundIt = bounds.iterator();
                while(boundIt.hasNext()){
                    TypePlaceholder bound = boundIt.next();
                    insert += bound.getName();
                    if(boundIt.hasNext())insert += " & ";
                }
            }
            if(it.hasNext())insert+=",";
        }
        return new TypeInsertPoint(offset, insert + end);
    }
}

class TypeToInsertString implements ResultSetVisitor{
    String insert = "";

    TypeToInsertString(RefTypeOrTPHOrWildcardOrGeneric type){
        type.accept(this);
    }

    @Override
    public void visit(PairTPHsmallerTPH p) {

    }

    @Override
    public void visit(PairTPHequalRefTypeOrWildcardType p) {

    }

    @Override
    public void visit(PairTPHEqualTPH p) {

    }

    @Override
    public void visit(RefType resolved) {
        insert = resolved.getName().toString();
        if(resolved.getParaList().size() > 0){
            insert += "<";
            Iterator<RefTypeOrTPHOrWildcardOrGeneric> iterator = resolved.getParaList().iterator();
            while(iterator.hasNext()){
                RefTypeOrTPHOrWildcardOrGeneric typeParam = iterator.next();
                insert += new TypeToInsertString(typeParam).insert;
                if(iterator.hasNext())insert += ", ";
            }
            insert += ">";
        }
    }

    @Override
    public void visit(GenericRefType genericRefType) {
        insert += genericRefType.getParsedName();
    }

    @Override
    public void visit(SuperWildcardType superWildcardType) {
        insert += "? super " + new TypeToInsertString(superWildcardType.getInnerType()).insert;
    }

    @Override
    public void visit(TypePlaceholder typePlaceholder) {
        insert += typePlaceholder.getName();
    }

    @Override
    public void visit(ExtendsWildcardType extendsWildcardType) {
        insert += "? extends " + new TypeToInsertString(extendsWildcardType.getInnerType()).insert;
    }
}