package de.dhbwstuttgart.parser.SyntaxTreeGenerator;

import de.dhbwstuttgart.exceptions.NotImplementedException;
import de.dhbwstuttgart.exceptions.TypeinferenceException;
import de.dhbwstuttgart.parser.antlr.Java8Parser;
import de.dhbwstuttgart.parser.antlr.Java8Parser.UnannClassType_lfno_unannClassOrInterfaceTypeContext;
import de.dhbwstuttgart.parser.scope.GenericsRegistry;
import de.dhbwstuttgart.parser.scope.JavaClassName;
import de.dhbwstuttgart.parser.scope.JavaClassRegistry;
import de.dhbwstuttgart.syntaxtree.GenericDeclarationList;
import de.dhbwstuttgart.syntaxtree.GenericTypeVar;
import de.dhbwstuttgart.syntaxtree.factory.ASTFactory;
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;
import org.antlr.v4.runtime.Token;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TypeGenerator {

    public static RefTypeOrTPHOrWildcardOrGeneric convert(Java8Parser.UnannClassOrInterfaceTypeContext unannClassOrInterfaceTypeContext, JavaClassRegistry reg, GenericsRegistry generics) {
        Java8Parser.TypeArgumentsContext arguments=null;
        /* PL 2019-03-19 auskommentiert ANFANG
        if(unannClassOrInterfaceTypeContext.unannClassType_lfno_unannClassOrInterfaceType() != null){
            arguments = unannClassOrInterfaceTypeContext.unannClassType_lfno_unannClassOrInterfaceType().typeArguments();
        }else{// if(unannClassOrInterfaceTypeContext.unannInterfaceType_lfno_unannClassOrInterfaceType() != null){
            arguments = unannClassOrInterfaceTypeContext.unannInterfaceType_lfno_unannClassOrInterfaceType().unannClassType_lfno_unannClassOrInterfaceType().typeArguments();
        }
         PL 2019-03-19 auskommentiert ENDE */
        /**
         * Problem sind hier die verschachtelten Typen mit verschachtelten Typargumenten
         * Beispiel: Typ<String>.InnererTyp<Integer>
         */
        String name = unannClassOrInterfaceTypeContext.getText();
        if(name.contains("<")){
            name = name.split("<")[0]; //Der Typ ist alles vor den ersten Argumenten
            /* Fuer Debug-Zwecke
            unannClassOrInterfaceTypeContext.unannInterfaceType_lfno_unannClassOrInterfaceType();
            unannClassOrInterfaceTypeContext.unannClassType_lfno_unannClassOrInterfaceType().getText();
            //unannClassOrInterfaceTypeContext.unannInterfaceType_lfno_unannClassOrInterfaceType().unannClassType_lfno_unannClassOrInterfaceType().typeArguments();
            //UnannClassType_lfno_unannClassOrInterfaceTypeContext
            unannClassOrInterfaceTypeContext.unannClassType_lf_unannClassOrInterfaceType(0);
            unannClassOrInterfaceTypeContext.unannClassType_lf_unannClassOrInterfaceType(0).getText();
            unannClassOrInterfaceTypeContext.unannClassType_lf_unannClassOrInterfaceType(1);
            unannClassOrInterfaceTypeContext.unannClassType_lf_unannClassOrInterfaceType(1).getText();
            unannClassOrInterfaceTypeContext.unannClassType_lfno_unannClassOrInterfaceType().getText();
            //unannClassOrInterfaceTypeContext.unannInterfaceType_lf_unannClassOrInterfaceType();
            //unannClassOrInterfaceTypeContext.unannInterfaceType_lf_unannClassOrInterfaceType(0).getText();
            //unannClassOrInterfaceTypeContext.unannInterfaceType_lf_unannClassOrInterfaceType(1).getText();
            //unannClassOrInterfaceTypeContext.unannInterfaceType_lfno_unannClassOrInterfaceType().getText();
             */
            int lastElement = new ArrayList<>(unannClassOrInterfaceTypeContext.unannClassType_lf_unannClassOrInterfaceType()).size()-1;
            if (lastElement >=0) {//qualifizierter Name z.B.: java.util.Vector
            	arguments = unannClassOrInterfaceTypeContext.unannClassType_lf_unannClassOrInterfaceType(lastElement).typeArguments();
            }
            else { //unqualifizierter Name z.B.: Vector
            	arguments = unannClassOrInterfaceTypeContext.unannClassType_lfno_unannClassOrInterfaceType().typeArguments();
            }
        }
        return convertTypeName(name, arguments, unannClassOrInterfaceTypeContext.getStart(), reg, generics);
    }

    public static RefTypeOrTPHOrWildcardOrGeneric convert(Java8Parser.UnannTypeContext unannTypeContext, JavaClassRegistry reg, GenericsRegistry genericsRegistry) {
        if(unannTypeContext.unannPrimitiveType()!=null){
            if(unannTypeContext.unannPrimitiveType().getText().equals("boolean")){
                return new RefType(ASTFactory.createClass(Boolean.class).getClassName(), unannTypeContext.getStart());
            }else{
                Java8Parser.NumericTypeContext numericType = unannTypeContext.unannPrimitiveType().numericType();
                throw new NotImplementedException();
            }
        }else
        if(unannTypeContext.unannReferenceType().unannArrayType()!=null){
            //System.out.println(unannTypeContext.getText());
            throw new NotImplementedException();
        }else
        if(unannTypeContext.unannReferenceType().unannTypeVariable()!=null){
            JavaClassName name = reg.getName(unannTypeContext.unannReferenceType().unannTypeVariable().Identifier().toString());
            return new RefType(name, unannTypeContext.getStart());
        }
        return TypeGenerator.convert(unannTypeContext.unannReferenceType().unannClassOrInterfaceType(), reg, genericsRegistry);
    }

    public static GenericDeclarationList convert(Java8Parser.TypeParametersContext typeParametersContext,
                                                 JavaClassName parentClass, String parentMethod, JavaClassRegistry reg, GenericsRegistry generics) {
        Token endOffset = typeParametersContext.getStop();
        List<GenericTypeVar> typeVars = new ArrayList<>();
        for(Java8Parser.TypeParameterContext typeParameter : typeParametersContext.typeParameterList().typeParameter()){
            typeVars.add(convert(typeParameter, parentClass, parentMethod, reg, generics));
            endOffset = typeParameter.getStop();
        }
        return new GenericDeclarationList(typeVars, endOffset);
    }

    public static GenericTypeVar convert(Java8Parser.TypeParameterContext typeParameter, JavaClassName parentClass, String parentMethod, JavaClassRegistry reg, GenericsRegistry generics) {
        String name = typeParameter.Identifier().getText();
        //TODO: Es müssen erst alle GenericTypeVars generiert werden, dann können die bounds dieser Generics ermittelt werden
        //Problem <A extends B, B> ist erlaubt, würde aber bei den Bounds von A den Generic B nicht als solchen erkennen
        List<RefTypeOrTPHOrWildcardOrGeneric> bounds = TypeGenerator.convert(typeParameter.typeBound(),reg, generics);

        GenericTypeVar ret = new GenericTypeVar(name, bounds, typeParameter.getStart(), typeParameter.getStop());
        return ret;
    }

    public static List<RefTypeOrTPHOrWildcardOrGeneric> convert(Java8Parser.TypeBoundContext typeBoundContext, JavaClassRegistry reg, GenericsRegistry generics) {
        List<RefTypeOrTPHOrWildcardOrGeneric> ret = new ArrayList<>();
        if(typeBoundContext == null){
            ret.add(ASTFactory.createObjectType());
            return ret;
        }
        if(typeBoundContext.typeVariable() != null){
            ret.add(convertTypeName(typeBoundContext.typeVariable().Identifier().getText(), null, typeBoundContext.typeVariable().getStart(), reg, generics));
            return ret;
        }
        if(typeBoundContext.classOrInterfaceType() != null){
            ret.add(convert(typeBoundContext.classOrInterfaceType(), reg, generics));
            if(typeBoundContext.additionalBound() != null)
                for(Java8Parser.AdditionalBoundContext addCtx : typeBoundContext.additionalBound()){
                    ret.add(convert(addCtx.interfaceType()));
                }
            return ret;
        }else{
            throw new NotImplementedException();
        }
    }

    private static RefTypeOrTPHOrWildcardOrGeneric convert(Java8Parser.ClassOrInterfaceTypeContext classOrInterfaceTypeContext, JavaClassRegistry reg, GenericsRegistry generics) {
        Java8Parser.ClassType_lfno_classOrInterfaceTypeContext ctx = classOrInterfaceTypeContext.classType_lfno_classOrInterfaceType();
        if(ctx.typeArguments() != null &&
                classOrInterfaceTypeContext.classType_lf_classOrInterfaceType().size() > 0)
            throw new NotImplementedException();
        String typeName = ctx.Identifier().toString();
        Java8Parser.ClassType_lf_classOrInterfaceTypeContext nextCtx = null;
        for(Java8Parser.ClassType_lf_classOrInterfaceTypeContext forEachCtx : classOrInterfaceTypeContext.classType_lf_classOrInterfaceType()){
            nextCtx = forEachCtx;
            typeName += "." + forEachCtx.Identifier().toString();
        }
        Java8Parser.TypeArgumentsContext arguments = nextCtx!=null?nextCtx.typeArguments():ctx.typeArguments();
        return convertTypeName(typeName, arguments, classOrInterfaceTypeContext.getStart(), reg, generics);
    }

    private static RefTypeOrTPHOrWildcardOrGeneric convert(Java8Parser.InterfaceTypeContext interfaceTypeContext) {
        throw new NotImplementedException();
    }

    public static RefTypeOrTPHOrWildcardOrGeneric convert(Java8Parser.ReferenceTypeContext referenceTypeContext, JavaClassRegistry reg, GenericsRegistry generics) {
        if(referenceTypeContext.classOrInterfaceType() != null){
            if(referenceTypeContext.classOrInterfaceType().classType_lfno_classOrInterfaceType()!= null){
                return convert(referenceTypeContext.classOrInterfaceType(), reg, generics);//return convertTypeName(referenceTypeContext.getText(), ctx.typeArguments(),referenceTypeContext.getStart(), reg, generics);
            }else{
                throw new NotImplementedException();
            }
        }else{
            throw new NotImplementedException();
        }
    }
    
    public static RefTypeOrTPHOrWildcardOrGeneric convert(Java8Parser.WildcardContext wildcardContext, JavaClassRegistry reg, GenericsRegistry generics) {
        if(wildcardContext.wildcardBounds() != null){
            if(wildcardContext.wildcardBounds().getText().substring(0, 7).equals("extends")){
                return new ExtendsWildcardType(convert(wildcardContext.wildcardBounds().referenceType(), reg, generics), wildcardContext.getStart());
            }else{
            	return new SuperWildcardType(convert(wildcardContext.wildcardBounds().referenceType(), reg, generics), wildcardContext.getStart());
            }
        }else{
            throw new NotImplementedException(); //Wildcard ohne Bound
        }
    }

    public static RefTypeOrTPHOrWildcardOrGeneric convertTypeName(String name, Token offset, JavaClassRegistry reg, GenericsRegistry generics){
        return convertTypeName(name, null, offset, reg, generics);
    }

    public static RefTypeOrTPHOrWildcardOrGeneric convertTypeName(
            String name, Java8Parser.TypeArgumentsContext typeArguments, Token offset, JavaClassRegistry reg, GenericsRegistry generics){
        if(!reg.contains(name)){ //Dann könnte es ein generischer Type oder ein FunN$$-Type sein
            if(generics.contains(name)){
                return new GenericRefType(name, offset);
            }else{
            Pattern p = Pattern.compile("Fun(\\d+)[$][$]");
            Matcher m = p.matcher(name);
            if (m.matches()) {//es ist FunN$$-Type 
                return new RefType(new JavaClassName(name), convert(typeArguments, reg, generics), offset);
            } else {
                throw new TypeinferenceException("Der Typ "+ name + " ist nicht vorhanden",offset);
            }}
        }
        if(typeArguments == null){
            List<RefTypeOrTPHOrWildcardOrGeneric> params = new ArrayList<>();
            for(int i = 0; i<reg.getNumberOfGenerics(name);i++){
                params.add(TypePlaceholder.fresh(offset));
            }
            return new RefType(reg.getName(name), params, offset);
        }else{
            return new RefType(reg.getName(name), convert(typeArguments, reg, generics), offset);
        }
    }

    public static List<RefTypeOrTPHOrWildcardOrGeneric> convert(Java8Parser.TypeArgumentsContext typeArguments,
                                       JavaClassRegistry reg, GenericsRegistry generics){
        List<RefTypeOrTPHOrWildcardOrGeneric> ret = new ArrayList<>();
        for(Java8Parser.TypeArgumentContext arg : typeArguments.typeArgumentList().typeArgument()){
            if(arg.wildcard() != null){
            	ret.add(convert(arg.wildcard(), reg, generics));
            }else{
                ret.add(convert(arg.referenceType(), reg, generics));
            }
        }
        return ret;
    }

    public static RefTypeOrTPHOrWildcardOrGeneric convert(Java8Parser.ClassTypeContext ctx, JavaClassRegistry reg, GenericsRegistry generics) {
        if(ctx.classOrInterfaceType() != null)throw new NotImplementedException();
        return convertTypeName(ctx.Identifier().getText(), ctx.typeArguments(), ctx.getStart(), reg, generics);
    }
}
