package de.dhbwstuttgart.typedeployment;

import de.dhbwstuttgart.bytecode.genericsGeneratorTypes.GenericGenratorResultForSourceFile;
import de.dhbwstuttgart.bytecode.genericsGeneratorTypes.GenericsGeneratorResult;
import de.dhbwstuttgart.bytecode.genericsGeneratorTypes.GenericsGeneratorResultForClass;
import de.dhbwstuttgart.bytecode.utilities.MethodUtility;
import de.dhbwstuttgart.bytecode.utilities.Resolver;
import de.dhbwstuttgart.core.JavaTXCompiler;
import de.dhbwstuttgart.parser.NullToken;
import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.syntaxtree.type.*;
import de.dhbwstuttgart.typeinference.result.*;

import org.antlr.v4.runtime.Token;
import org.objectweb.asm.Type;

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) {
        
		try {
			ResolvedType resolvedType = resultSet.resolveType(type);
			TypeInsertPoint insertPoint = new TypeInsertPoint(offset,
					new TypeToInsertString(resolvedType.resolvedType).insert); 
			List<GenericGenratorResultForSourceFile> simplifyResults = JavaTXCompiler.INSTANCE.getGeneratedGenericResultsForAllSourceFiles();
			for (GenericGenratorResultForSourceFile simplifyResultsEntries : simplifyResults) {
				GenericsGeneratorResultForClass genericResultsForClass = simplifyResultsEntries.getSimplifyResultsByName("", cl.getClassName().toString());
				return new TypeInsert(insertPoint, createGenericInsert(genericResultsForClass, cl, m, resultSet), resolvedType.getResultPair());
			}
			
			return new TypeInsert(insertPoint, new HashSet<>(), resolvedType.getResultPair());
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		    return null;
		}
    }

    private static synchronized Set<TypeInsertPoint> createGenericInsert(GenericsGeneratorResultForClass genericResult, ClassOrInterface cl, Method m, ResultSet resultSet){
        Set<TypeInsertPoint> result = createGenericClassInserts(genericResult, cl);
        
        for (Method method : cl.getMethods()) {        	
        	Resolver resolver = new Resolver(resultSet);
        	List<GenericsGeneratorResult> methodConstraints = genericResult.getMethodConstraintsByID(MethodUtility.createID(resolver, method));
        	createMethodConstraints(method, methodConstraints);
        }
        
        return result;
    }
    
    private static Set<TypeInsertPoint> createMethodConstraints(Method method, List<GenericsGeneratorResult> constraints) {
    	Set<TypeInsertPoint> result = new HashSet<>();
    	Token offset = new GenericDeclarationList(method.getGenerics(), new NullToken()).getOffset();

		if (constraints.size() == 0) {
			result.add(new TypeInsertPoint(offset, ""));
			return result;
		} 
		
		String insert = " <";

        for (GenericsGeneratorResult genericInsertConstraint : constraints) {
        	if (genericInsertConstraint.getConstraint().getRight().equals(Type.getInternalName(Object.class))) {
        		insert += genericInsertConstraint.getConstraint().getLeft();
        	} else {        		
        		insert += genericInsertConstraint.getConstraint().getLeft() + " extends " + genericInsertConstraint.getConstraint().getRight() + ", ";
        	}
        }
        
        insert = insert.substring(0, insert.length() -2);
        insert += ">";

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

	private static Set<TypeInsertPoint> createGenericClassInserts(GenericsGeneratorResultForClass genericResult, ClassOrInterface cl) {
		Set<TypeInsertPoint> result = new HashSet<>();
		Token offset = cl.getGenerics().getOffset();
    	
        List<GenericsGeneratorResult> classConstraints = genericResult.getClassConstraints();
        
		if (classConstraints.size() == 0) {
			result.add(new TypeInsertPoint(offset, ""));
			return result;
		} 
		
		String insert = " <";

        for (GenericsGeneratorResult genericInsertConstraint : classConstraints) {
        	if (genericInsertConstraint.getConstraint().getRight().equals(Type.getInternalName(Object.class))) {
        		insert += genericInsertConstraint.getConstraint().getLeft();
        	} else {        		
        		insert += genericInsertConstraint.getConstraint().getLeft() + " extends " + genericInsertConstraint.getConstraint().getRight() + ", ";
        	}
        }

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

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

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;
    }
}