package de.dhbwstuttgart.typedeployment;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import de.dhbwstuttgart.target.generate.GenerateGenerics;
import de.dhbwstuttgart.target.generate.GenericsResult;
import de.dhbwstuttgart.target.generate.GenericsResultSet;
import de.dhbwstuttgart.typeinference.result.*;
import org.antlr.v4.runtime.Token;

import de.dhbwstuttgart.syntaxtree.ClassOrInterface;
import de.dhbwstuttgart.syntaxtree.Method;
import de.dhbwstuttgart.syntaxtree.SourceFile;
import de.dhbwstuttgart.syntaxtree.type.ExtendsWildcardType;
import de.dhbwstuttgart.syntaxtree.type.GenericRefType;
import de.dhbwstuttgart.syntaxtree.type.RefType;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import de.dhbwstuttgart.syntaxtree.type.SuperWildcardType;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;

/**
 * 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, GenericsResult generics) {
    	return new TypeInsertPlacer().getTypeInserts(forSourcefile, withResults, generics);
    }

    public static TypeInsert createInsertPoints(RefTypeOrTPHOrWildcardOrGeneric type, Token offset, ClassOrInterface cl, Method m,
                                                ResultSet resultSet, GenericsResultSet constraints, GenericsResultSet classConstraints) {
        
    	/* PL 2020-04-11 auskommentiert
    	 * try {
    	 */
			ResolvedType resolvedType = resultSet.resolveType(type);
			TypeInsertPoint insertPoint = new TypeInsertPoint(offset,
					new TypeToInsertString(resolvedType.resolvedType, constraints, classConstraints).insert, KindOfTypeInsertPoint.NORMAL_INSERT); 
			/* PL 2020-04-11 auskommentiert
			List<GenericGenratorResultForSourceFile> simplifyResults = JavaTXCompiler.INSTANCE.getGeneratedGenericResultsForAllSourceFiles(newResults);
			for (GenericGenratorResultForSourceFile simplifyResultsEntries : simplifyResults) {
				GenericsGeneratorResultForClass genericResultsForClass = simplifyResultsEntries.getSimplifyResultsByName(cl.getClassName());
				return new TypeInsert(insertPoint, createGenericInsert(genericResultsForClass, cl, m, resultSet, offset), resolvedType.getResultPair());
			}
			
			return new TypeInsert(insertPoint, new HashSet<>(), resolvedType.getResultPair());
			*/
			//GenericsGeneratorResultForClass genericResultsForClass = genericResult.getSimplifyResultsByName("", cl.getClassName().toString());
			return new TypeInsert(insertPoint, createGenericInsert(constraints, classConstraints, cl, m, resultSet, offset), resolvedType.getResultPair());
		
		/* PL 2020-04-11 auskommentiert
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		    return null;
		}
		*/
    }

    private static synchronized Set<TypeInsertPoint> createGenericInsert(GenericsResultSet methodConstraints, GenericsResultSet classConstraints,ClassOrInterface cl, Method m, ResultSet resultSet, Token mOffset){
        Set<TypeInsertPoint> result = createGenericClassInserts(classConstraints, cl);
    	
    	if (m != null) {
        	//List<GenericsGeneratorResult> methodConstraints = genericResult.getMethodConstraintsByID(MethodUtility.createID(resolver, m));
        	result.addAll(createMethodConstraints(methodConstraints, m.getOffset() != null ? m.getOffset() : mOffset));
    	}
        
        return result;
    }
    
    private static Set<TypeInsertPoint> createMethodConstraints(GenericsResultSet constraints, Token mOffset) {
    	Set<TypeInsertPoint> result = new HashSet<>();
    	Token offset = mOffset;

		if (constraints.size() == 0) {
			return result;
		} 
		
		String insert = " <";

        for (var genericInsertConstraint : constraints) {
        	if (genericInsertConstraint instanceof GenerateGenerics.PairEQ peq) {
        		insert += peq.left.resolve().getName();
        	} else if (genericInsertConstraint instanceof GenerateGenerics.PairLT psm) {
        		insert += psm.left.resolve().getName() + " extends " + psm.right.resolve().getName();
        	}
        	insert += ", ";
        }
        
        insert = insert.substring(0, insert.length() -2);
        insert += ">";

        result.add(new TypeInsertPoint(offset, insert, KindOfTypeInsertPoint.GENERERIC_METHOD_INSERT));
    	return result;
    }

	private static Set<TypeInsertPoint> createGenericClassInserts(GenericsResultSet classConstraints, ClassOrInterface cl) {
		Set<TypeInsertPoint> result = new HashSet<>();
		Token offset = cl.getGenerics().getOffset();

		if (classConstraints == null || classConstraints.size() == 0) {
			return result;
		} 
		
		String insert = " <";

        for (var genericInsertConstraint : classConstraints) {
            if (genericInsertConstraint instanceof GenerateGenerics.PairEQ peq) {
                insert += peq.left.resolve().getName();
            } else if (genericInsertConstraint instanceof GenerateGenerics.PairLT psm) {
                insert += psm.left.resolve().getName() + " extends " + psm.right.resolve().getName();
            }
            insert += ", ";
        }

        insert = insert.substring(0, insert.length() -2);
        insert += ">";

        result.add(new TypeInsertPoint(offset, insert, KindOfTypeInsertPoint.GENERIC_CLASS_INSERT));
        
        return result;
	}
}

class TypeToInsertString implements ResultSetVisitor{
    String insert = "";
    private GenericsResultSet constraints;
    private GenericsResultSet classConstraints;
    

    TypeToInsertString(RefTypeOrTPHOrWildcardOrGeneric type, GenericsResultSet constraints, GenericsResultSet classConstraints){
        this.constraints = constraints;
        this.classConstraints = classConstraints;
    	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, constraints, classConstraints).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(), constraints, classConstraints).insert;
    }

    @Override
    public void visit(TypePlaceholder typePlaceholder) {
        GenerateGenerics.Pair resultPair = null;
        if (constraints != null)
            resultPair = constraints.getResultPairFor(typePlaceholder).orElse(null);
        if (resultPair == null)
            resultPair = classConstraints.getResultPairFor(typePlaceholder).orElse(null);

        if (resultPair != null)
            insert += resultPair.left.toString();
    }

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