package de.dhbwstuttgart.parser.SyntaxTreeGenerator;

import de.dhbwstuttgart.exceptions.NotImplementedException;
import de.dhbwstuttgart.exceptions.TypeinferenceException;
import de.dhbwstuttgart.parser.NullToken;
import de.dhbwstuttgart.parser.antlr.Java17Parser;
import de.dhbwstuttgart.parser.antlr.Java17Parser.IdentifierContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.TypeArgumentsContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.TypeTypeContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.WildcardTypeContext;
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.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TypeGenerator {

    public static RefTypeOrTPHOrWildcardOrGeneric convert(Java17Parser.ClassOrInterfaceTypeContext classOrInterfaceTypeContext, JavaClassRegistry reg, GenericsRegistry generics) {
        Java17Parser.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>
         */
        if (classOrInterfaceTypeContext.typeArguments().size() > 1)
            throw new NotImplementedException();

        String name = "";
        for (IdentifierContext id : classOrInterfaceTypeContext.identifier()) {
            name += id.getText() + '.';
        }
        name += classOrInterfaceTypeContext.typeIdentifier().getText();
        if (classOrInterfaceTypeContext.getStop().getText().equals(">")) {
            /*
             * 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();
             */
            List<TypeArgumentsContext> typeargs = classOrInterfaceTypeContext.typeArguments();
            arguments = typeargs.size() != 0 ? classOrInterfaceTypeContext.typeArguments(0) : null;
        }
        return convertTypeName(name, arguments, classOrInterfaceTypeContext.getStart(), reg, generics);
    }

    public static RefTypeOrTPHOrWildcardOrGeneric convert(Java17Parser.TypeTypeContext typeContext, JavaClassRegistry reg, GenericsRegistry genericsRegistry) {
        if (typeContext.primitiveType() != null) {
            switch (typeContext.primitiveType().getText()) {
            case "boolean":
                return new RefType(ASTFactory.createClass(Boolean.class).getClassName(), typeContext.getStart());
            case "char":
                return new RefType(ASTFactory.createClass(Character.class).getClassName(), typeContext.getStart());
            case "int":
                return new RefType(ASTFactory.createClass(Integer.class).getClassName(), typeContext.getStart());
            case "double":
                return new RefType(ASTFactory.createClass(Double.class).getClassName(), typeContext.getStart());
            case "float":
                return new RefType(ASTFactory.createClass(Float.class).getClassName(), typeContext.getStart());
            default:
                throw new NotImplementedException(typeContext.primitiveType().getText());
            }
        } else if (!typeContext.LBRACK().isEmpty()) { // ArrayType über eckige Klammer prüfen
            // JavaTXParser.logger.info(unannTypeContext.getText());
            throw new NotImplementedException();
        }
        /*
         * else if (typeContext.classOrInterfaceType() != null) { JavaClassName name = reg .getName(typeContext.classOrInterfaceType().typeIdentifier().getText()); return new RefType(name, typeContext.getStart()); }
         */
        return TypeGenerator.convert(typeContext.classOrInterfaceType(), reg, genericsRegistry);
    }

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

    public static GenericTypeVar convert(Java17Parser.GenericTypeVarContext typeVar, JavaClassName parentClass, String parentMethod, JavaClassRegistry reg, GenericsRegistry generics) {
        String name = typeVar.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(typeVar.typeBound(), reg, generics);

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

    public static List<RefTypeOrTPHOrWildcardOrGeneric> convert(Java17Parser.TypeBoundContext typeBoundContext, JavaClassRegistry reg, GenericsRegistry generics) {
        List<RefTypeOrTPHOrWildcardOrGeneric> ret = new ArrayList<>();
        if (Objects.isNull(typeBoundContext)) {
            ret.add(ASTFactory.createObjectType());
            return ret;
        }
        if (typeBoundContext.typeType().size() > 0) {
            for (TypeTypeContext tt : typeBoundContext.typeType()) {
                ret.add(convert(tt, reg, generics));
            }
            return ret;
        }
        throw new NotImplementedException();
    }

    public static RefTypeOrTPHOrWildcardOrGeneric convert(Java17Parser.WildcardTypeContext wildcardContext, JavaClassRegistry reg, GenericsRegistry generics) {
        if (wildcardContext.getChildCount() < 3) {
            if (!Objects.isNull(wildcardContext.extendsWildcardType())) {
                return new ExtendsWildcardType(convert(wildcardContext.extendsWildcardType().typeType(), reg, generics), wildcardContext.getStart());
            } else if (!Objects.isNull(wildcardContext.superWildcardType())) {
                return new SuperWildcardType(convert(wildcardContext.superWildcardType().typeType(), reg, generics), wildcardContext.getStart());
            } else {
                return new ExtendsWildcardType(new RefType(new JavaClassName("java.lang.Object"), new NullToken()), wildcardContext.getStart());
            }
        } else {
            throw new NotImplementedException(); // Wildcard ohne Bound
        }
    }

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

    public static RefTypeOrTPHOrWildcardOrGeneric convertTypeName(String name, Java17Parser.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 RefTypeOrTPHOrWildcardOrGeneric convertTypeName(String name, Java17Parser.NonWildcardTypeArgumentsContext 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(Void|VoidImpl|Wrapper)?(\\d+)[$][$]"); // TODO Regex shenanigans
                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(Java17Parser.TypeArgumentsContext typeArguments, JavaClassRegistry reg, GenericsRegistry generics) {
        List<RefTypeOrTPHOrWildcardOrGeneric> ret = new ArrayList<>();
        for (Java17Parser.TypeArgumentContext arg : typeArguments.typeArgument()) {
            WildcardTypeContext wc = arg.wildcardType();
            if (!Objects.isNull(wc)) {
                ret.add(convert(wc, reg, generics));
            } else {
                ret.add(convert(arg.typeType(), reg, generics));
            }
        }
        return ret;
    }

    public static List<RefTypeOrTPHOrWildcardOrGeneric> convert(Java17Parser.NonWildcardTypeArgumentsContext typeArguments, JavaClassRegistry reg, GenericsRegistry generics) {
        List<RefTypeOrTPHOrWildcardOrGeneric> ret = new ArrayList<>();
        for (Java17Parser.TypeTypeContext arg : typeArguments.typeList().typeType()) {
            ret.add(convert(arg, reg, generics));
        }
        return ret;
    }
}
