package de.dhbwstuttgart.syntaxtree.factory;

import java.lang.reflect.*;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import de.dhbwstuttgart.exceptions.NotImplementedException;
import de.dhbwstuttgart.parser.NullToken;
import de.dhbwstuttgart.parser.SyntaxTreeGenerator.GenericContext;
import de.dhbwstuttgart.parser.scope.JavaClassName;
import de.dhbwstuttgart.parser.scope.JavaClassRegistry;
import de.dhbwstuttgart.syntaxtree.Field;
import de.dhbwstuttgart.syntaxtree.Method;
import de.dhbwstuttgart.syntaxtree.type.*;
import de.dhbwstuttgart.syntaxtree.type.Void;
import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.syntaxtree.statement.Block;
import de.dhbwstuttgart.syntaxtree.statement.Statement;
import de.dhbwstuttgart.syntaxtree.type.WildcardType;
import org.antlr.v4.runtime.Token;

/**
 * Anmerkung:
 * Die ASTFactory Methoden, welche ASTBäume aus java.lang.Class Objekten generieren, können davon ausgehen,
 * dass alle Imports und Typnamen korrekt sind und müssen diese nicht überprüfen.
 */
public class ASTFactory {

	public static ClassOrInterface createClass(java.lang.Class jreClass){
		JavaClassName name = new JavaClassName(jreClass.getName());
		List<Method> methoden = new ArrayList<>();
		List<de.dhbwstuttgart.syntaxtree.Constructor> konstruktoren = new ArrayList<>();
		for(java.lang.reflect.Constructor constructor : jreClass.getConstructors()){
			konstruktoren.add(createConstructor(constructor, jreClass));
		}
		for(java.lang.reflect.Method method : jreClass.getMethods()){
			methoden.add(createMethod(method, jreClass));
		}
		List<Field> felder = new ArrayList<>();
		for(java.lang.reflect.Field field : jreClass.getDeclaredFields()){
			felder.add(createField(field, name));
		}
		int modifier = jreClass.getModifiers();
		boolean isInterface = jreClass.isInterface();
		//see: https://stackoverflow.com/questions/9934774/getting-generic-parameter-from-supertype-class
		ParameterizedType parameterSuperClass = null;
		Type tempSuperClass = jreClass.getGenericSuperclass();
		if(tempSuperClass != null && tempSuperClass instanceof ParameterizedType)
			parameterSuperClass = (ParameterizedType) tempSuperClass;
		java.lang.Class superjreClass = jreClass.getSuperclass();
		RefType superClass;
		if(parameterSuperClass != null){
			superClass = (RefType) createType(parameterSuperClass);
		}else if(superjreClass != null){
			superClass = (RefType) createType(superjreClass);
		}else{//Jede Klasse und jedes Interface erbt von Object: (auch Object selbst!)
			superClass = (RefType) createType(java.lang.Object.class);
		}
		List<RefType> implementedInterfaces = new ArrayList<>();
		for(Type jreInterface : jreClass.getGenericInterfaces()){
			implementedInterfaces.add((RefType) createType(jreInterface));
		}
        GenericDeclarationList genericDeclarationList = createGenerics(jreClass.getTypeParameters(), jreClass, null);

        Token offset = new NullToken(); //Braucht keinen Offset, da diese Klasse nicht aus einem Quellcode geparst wurde

		return new ClassOrInterface(modifier, name, felder, Optional.empty() /* eingefuegt PL 2018-11-24 */,methoden, konstruktoren, genericDeclarationList, superClass,isInterface, implementedInterfaces, offset);
	}

	private static Field createField(java.lang.reflect.Field field, JavaClassName jreClass) {
		return new Field(field.getName(), createType(field.getType()), field.getModifiers(), new NullToken());
	}

	//private static RefType createType(Class classType) {
	//	return createClass(classType).getType();
	//}

	private static de.dhbwstuttgart.syntaxtree.Constructor createConstructor(Constructor constructor, Class inClass) {
		String name = constructor.getName();
		RefTypeOrTPHOrWildcardOrGeneric returnType = createType(inClass);
		Parameter[] jreParams = constructor.getParameters();
		Type[] jreGenericParams = constructor.getGenericParameterTypes();
		List<FormalParameter> params = new ArrayList<>();
		int i = 0;
		for(Type jreParam : jreGenericParams){
			RefTypeOrTPHOrWildcardOrGeneric paramType = createType(jreParam);
			params.add(new FormalParameter(jreParams[i].getName(),paramType, new NullToken()));
			i++;
		}
		ParameterList parameterList = new ParameterList(params, new NullToken());
		Block block = new Block(new ArrayList<Statement>(), new NullToken());
		GenericDeclarationList gtvDeclarations = createGenerics(constructor.getTypeParameters(), inClass, constructor.getName());
		Token offset = new NullToken();
		int modifier = constructor.getModifiers();

		if(inClass.equals(java.lang.Object.class)){
			return null;
		}

		return new de.dhbwstuttgart.syntaxtree.Constructor(modifier, name,returnType, parameterList, block, gtvDeclarations, offset /*, new ArrayList<>() geloescht PL 2018-11-24 */);
	}

	public static Method createMethod(java.lang.reflect.Method jreMethod, java.lang.Class inClass){
		String name = jreMethod.getName();
		RefTypeOrTPHOrWildcardOrGeneric returnType;
		Type jreRetType;
		if(jreMethod.getGenericReturnType()!=null){
			jreRetType = jreMethod.getGenericReturnType();
		}else{
			jreRetType = jreMethod.getReturnType();
		}
			returnType = createType(jreRetType);
		Parameter[] jreParams = jreMethod.getParameters();
		Type[] jreGenericParams = jreMethod.getGenericParameterTypes();
		List<FormalParameter> params = new ArrayList<>();
		int i = 0;
		for(Type jreParam : jreGenericParams){
			RefTypeOrTPHOrWildcardOrGeneric paramType = createType(jreParam);
			params.add(new FormalParameter(jreParams[i].getName(),paramType, new NullToken()));
			i++;
		}
		ParameterList parameterList = new ParameterList(params, new NullToken());
		Block block = new Block(new ArrayList<Statement>(), new NullToken());
        GenericDeclarationList gtvDeclarations = createGenerics(jreMethod.getTypeParameters(), inClass, jreMethod.getName());
		Token offset = new NullToken();

		return new Method(jreMethod.getModifiers(), name,returnType, parameterList, block, gtvDeclarations, offset);
	}

	public static GenericDeclarationList createGenerics(TypeVariable[] typeParameters, Class context, String methodName){
		List<de.dhbwstuttgart.syntaxtree.GenericTypeVar> gtvs = new ArrayList<>();
		for(TypeVariable jreTV : typeParameters){
			de.dhbwstuttgart.syntaxtree.GenericTypeVar gtv = createGeneric(jreTV, jreTV.getName(), context, methodName);
			gtvs.add(gtv);
		}
		return new GenericDeclarationList(gtvs,new NullToken());
	}

	private static RefTypeOrTPHOrWildcardOrGeneric createType(java.lang.reflect.Type type){
		if(type.getTypeName().equals("void")){
			return new Void(new NullToken());
		}else if(type.getTypeName().equals("int")){
			return new RefType(new JavaClassName("java.lang.Integer"), new ArrayList<>(), new NullToken());
		}else if(type.getTypeName().equals("byte")){
			return new RefType(new JavaClassName("java.lang.Byte"), new ArrayList<>(), new NullToken());
		}else if(type.getTypeName().equals("boolean")){
			return new RefType(new JavaClassName("java.lang.Boolean"), new ArrayList<>(), new NullToken());
		}else if(type.getTypeName().equals("char")){
			return new RefType(new JavaClassName("java.lang.Char"), new ArrayList<>(), new NullToken());
		}else if(type.getTypeName().equals("short")){
			return new RefType(new JavaClassName("java.lang.Short"), new ArrayList<>(), new NullToken());
		}else if(type.getTypeName().equals("double")){
			return new RefType(new JavaClassName("java.lang.Double"), new ArrayList<>(), new NullToken());
		}else if(type.getTypeName().equals("long")){
			return new RefType(new JavaClassName("java.lang.Long"), new ArrayList<>(), new NullToken());
		}else{
			if(type instanceof TypeVariable){
				//GTVDeclarationContext via "(TypeVariable) type).getGenericDeclaration()"
				return new GenericRefType(type.getTypeName(), new NullToken());
			}
			List<RefTypeOrTPHOrWildcardOrGeneric> params = new ArrayList<>();
			if(type instanceof ParameterizedType){
				for(Type t : ((ParameterizedType)type).getActualTypeArguments()){
					params.add(createType(t));
				}
			}
			String name = type.getTypeName();
			if(name.contains("<")){ //Komischer fix. Type von Generischen Typen kann die Generics im Namen enthalten Type<A>
				//Diese entfernen:
				name = name.split("<")[0];
			}
			if(type instanceof java.lang.reflect.WildcardType){
				java.lang.reflect.WildcardType wildcardType = (java.lang.reflect.WildcardType) type;
				if(wildcardType.getLowerBounds().length > 0){
					return new SuperWildcardType(createType(wildcardType.getLowerBounds()[0]), new NullToken());
				}else if(wildcardType.getUpperBounds().length > 0){
					return new ExtendsWildcardType(createType(wildcardType.getUpperBounds()[0]), new NullToken());
				}else{//Es handelt sich um den '?'-Typ:
					return new ExtendsWildcardType(createObjectType(), new NullToken());
				}
			}else{
				RefType ret = new RefType(new JavaClassName(name), params, new NullToken());
				return ret;
			}
		}
	}

	public static de.dhbwstuttgart.syntaxtree.GenericTypeVar createGeneric(TypeVariable jreTypeVar, String jreTVName, Class context, String parentMethod){
		JavaClassName parentClass = new JavaClassName(context.getName());
		List<RefType> genericBounds = new ArrayList<>();
		java.lang.reflect.Type[] bounds = jreTypeVar.getBounds();
		if(bounds.length > 0){
			for(java.lang.reflect.Type bound : bounds){
				genericBounds.add((RefType) createType(bound));
			}
		}
		return new de.dhbwstuttgart.syntaxtree.GenericTypeVar(jreTVName, genericBounds, new NullToken(), new NullToken());
	}

	public static ClassOrInterface createObjectClass() {
		return createClass(Object.class);
	}
	public static RefType createObjectType() {
		return new RefType(createClass(Object.class).getClassName(), new NullToken());
	}

	/*
	public Constructor createEmptyConstructor(Class parent){
		Block block = new Block();
		block.setType(new de.dhbwstuttgart.syntaxtree.type.Void(block, 0));
		block.statements.add(new SuperCall(block));

		return ASTFactory.createConstructor(parent, new ParameterList(), block);
	}

	public static Constructor createConstructor(Class superClass, ParameterList paralist, Block block){
		block.parserPostProcessing(superClass);

		Method method = ASTFactory.createMethod("<init>", paralist, block, superClass);
		method.setType(new de.dhbwstuttgart.syntaxtree.type.Void(block, 0));

		return new Constructor(method, superClass);
	}

	public static Class createClass(String className, RefType type, Modifiers modifiers, Menge supertypeGenPara, SourceFile parent) {
		// TODO bytecode createClass
		//String name, RefType superClass, Modifiers modifiers, Menge<String> supertypeGenPara
		Class generatedClass = new Class(className, type, modifiers, supertypeGenPara);
		generatedClass.addField(ASTFactory.createEmptyConstructor(generatedClass));

		generatedClass.parserPostProcessing(parent);

		return generatedClass;
	}

	public static Class createObject(){
		return createClass(java.lang.Object.class);
	}

	public static Class createInterface(String className, RefType superClass, Modifiers modifiers,
										Menge supertypeGenPara, SourceFile parent){
		Class generatedClass = new Class(new JavaClassName(className), new ArrayList<Method>(), new ArrayList<Field>(), modifiers,
				true, superClass, new ArrayList<RefType>(), new GenericDeclarationList(), -1);
		generatedClass.parserPostProcessing(parent);
		return generatedClass;
	}

	public static RefType createObjectType(){
		return createObjectClass().getType();
	}
	*/
}