package de.dhbwstuttgart.syntaxtree.factory;

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

import de.dhbwstuttgart.parser.NullToken;
import de.dhbwstuttgart.parser.SyntaxTreeGenerator.GenericContext;
import de.dhbwstuttgart.syntaxtree.Field;
import de.dhbwstuttgart.syntaxtree.Method;
import de.dhbwstuttgart.syntaxtree.type.*;
import de.dhbwstuttgart.syntaxtree.type.Void;
import de.dhbwstuttgart.typecheck.GenericTypeName;
import de.dhbwstuttgart.typecheck.JavaClassName;
import de.dhbwstuttgart.typecheck.JavaClassRegistry;
import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.syntaxtree.statement.Block;
import de.dhbwstuttgart.syntaxtree.statement.Statement;
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 {
	private final JavaClassRegistry names;


	public ASTFactory(JavaClassRegistry scope){
		names = scope;
	}

	public ClassOrInterface createClass(java.lang.Class jreClass){
		JavaClassName name = names.getName(jreClass.getName());
		List<Method> methoden = new ArrayList<>();
		for(java.lang.reflect.Constructor constructor : jreClass.getConstructors()){
            methoden.add(createConstructor(constructor, jreClass));
		}
		for(java.lang.reflect.Method method : jreClass.getMethods()){
			methoden.add(createMethod(method, jreClass));
		}
		List<Field> felder = new ArrayList<>();
		int modifier = jreClass.getModifiers();
		boolean isInterface = jreClass.isInterface();
		java.lang.Class superjreClass = jreClass.getSuperclass();
		RefType superClass;
		if(superjreClass != null){
			superClass = (RefType) createType(superjreClass, name, "");
		}else{//Jede Klasse und jedes Interface erbt von Object: (auch Object selbst!)
			superClass = (RefType) createType(java.lang.Object.class, name, "");
		}
		List<RefType> implementedInterfaces = new ArrayList<>();
		for(java.lang.Class jreInterface : jreClass.getInterfaces()){
			implementedInterfaces.add((RefType) createType(jreInterface, name, ""));
		}
        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, methoden, genericDeclarationList, superClass,isInterface, implementedInterfaces, offset);
	}

    private de.dhbwstuttgart.syntaxtree.Constructor createConstructor(Constructor constructor, Class inClass) {
		String name = constructor.getName();
		RefTypeOrTPHOrWildcardOrGeneric returnType = createType(inClass, names.getName(inClass.getName()), name);
		Parameter[] jreParams = constructor.getParameters();
		List<FormalParameter> params = new ArrayList<>();
		for(Parameter jreParam : jreParams){
			RefTypeOrTPHOrWildcardOrGeneric paramType = createType(jreParam.getType(),names.getName(inClass.getName()), name);
			params.add(new FormalParameter(jreParam.getName(),paramType, new NullToken()));
		}
		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();

		return new de.dhbwstuttgart.syntaxtree.Constructor(name,returnType, modifier, parameterList, block, gtvDeclarations, offset);
    }

    public Method createMethod(java.lang.reflect.Method jreMethod, java.lang.Class inClass){
		String name = jreMethod.getName();
		RefTypeOrTPHOrWildcardOrGeneric returnType;
			returnType = createType(jreMethod.getReturnType(),names.getName(inClass.getName()), name);
		Parameter[] jreParams = jreMethod.getParameters();
		List<FormalParameter> params = new ArrayList<>();
		for(Parameter jreParam : jreParams){
			RefTypeOrTPHOrWildcardOrGeneric paramType = createType(jreParam.getType(),names.getName(inClass.getName()), name);
			params.add(new FormalParameter(jreParam.getName(),paramType, new NullToken()));
		}
		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();
		int modifier = jreMethod.getModifiers();

		return new Method(name,returnType, modifier, parameterList, block, gtvDeclarations, offset);
	}

	public 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());
	}

    /*
	public RefType createType(java.lang.Class jreClass){
		List<RefTypeOrTPH> params = new ArrayList<>();
		for(TypeVariable jreTV : jreClass.getTypeParameters()){
			RefType gtv = createType(jreTV);
			params.add(gtv);
		}
		return new RefType(names.getName(jreClass.getName()), params, new NullToken());
	}
	*/

	public RefTypeOrTPHOrWildcardOrGeneric createType(java.lang.reflect.Type type, JavaClassName parentClass, String parentMethod){
		if(type.getTypeName().equals("void")){
			return new Void(new NullToken());
		}else if(type.getTypeName().equals("int")){
			return new RefType(this.names.getName("java.lang.Integer"), new ArrayList<>(), new NullToken());
		}else if(type.getTypeName().equals("byte")){
			return new RefType(this.names.getName("java.lang.Byte"), new ArrayList<>(), new NullToken());
		}else if(type.getTypeName().equals("boolean")){
			return new RefType(this.names.getName("java.lang.Boolean"), new ArrayList<>(), new NullToken());
		}else if(type.getTypeName().equals("char")){
			return new RefType(this.names.getName("java.lang.Char"), new ArrayList<>(), new NullToken());
		}else if(type.getTypeName().equals("short")){
			return new RefType(this.names.getName("java.lang.Short"), new ArrayList<>(), new NullToken());
		}else if(type.getTypeName().equals("double")){
			return new RefType(this.names.getName("java.lang.Double"), new ArrayList<>(), new NullToken());
		}else if(type.getTypeName().equals("long")){
			return new RefType(this.names.getName("java.lang.Long"), new ArrayList<>(), new NullToken());
		}else{
			if(type instanceof TypeVariable){
				//GTVDeclarationContext via "(TypeVariable) type).getGenericDeclaration()"
				return new GenericRefType(
						new GenericTypeName(new GenericContext(parentClass, parentMethod),type.getTypeName()),
						new NullToken());
			}
			List<RefTypeOrTPHOrWildcardOrGeneric> params = new ArrayList<>();
			if(type instanceof ParameterizedType){
				for(Type t : ((ParameterizedType)type).getActualTypeArguments()){
					params.add(createType(t, parentClass, parentMethod));
				}
			}
			RefType ret = new RefType(this.names.getName(type.getTypeName()), params, new NullToken());
			return ret;
		}
	}

	public de.dhbwstuttgart.syntaxtree.GenericTypeVar createGeneric(TypeVariable jreTypeVar, String jreTVName, Class context, String parentMethod){
		JavaClassName parentClass = names.getName(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, parentClass, parentMethod));
			}
		}
		return new de.dhbwstuttgart.syntaxtree.GenericTypeVar(
				new GenericTypeName(new GenericContext(parentClass, parentMethod), jreTVName)
				, genericBounds, new NullToken(), new NullToken());
	}

	public ClassOrInterface createObjectClass() {
		return this.createClass(Object.class);
	}

	/*
	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();
	}
	*/
}