package de.dhbwstuttgart.parser.scope;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import com.google.common.collect.Iterables;
import de.dhbwstuttgart.core.JavaTXCompiler;
import de.dhbwstuttgart.environment.PackageCrawler;
import de.dhbwstuttgart.exceptions.NotImplementedException;
import de.dhbwstuttgart.parser.antlr.Java17Parser;
import de.dhbwstuttgart.parser.antlr.Java17Parser.AnnotationTypeElementDeclarationContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.ClassBodyContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.ClassBodyDeclarationContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.ClassOrInterfaceContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.ClassorinterfacedeclContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.EnumConstantContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.EnumConstantsContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.InterfaceBodyDeclarationContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.InterfacememberContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.MemberclassorinterfaceContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.MemberdeclContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.NoclassorinterfaceContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.SrcfileContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.SubclassorinterfaceContext;

public class GatherNames {

	public static Map<String, Integer> getNames(SrcfileContext ctx, PackageCrawler packages, JavaTXCompiler compiler) throws ClassNotFoundException {
		Map<String, Integer> ret = new HashMap<>();
		for (Java17Parser.ClassOrInterfaceContext clsoifctx : ctx.classOrInterface()) {
			if (clsoifctx instanceof NoclassorinterfaceContext) {
				continue;
			}
			ret.putAll(getNames(clsoifctx, getPackageName(ctx), packages, compiler));
		}
		ret.putAll(getImports(ctx, packages, compiler));
		return ret;
	}

	public static Map<String, Integer> getNames(ClassOrInterfaceContext clsoifctx, String pkgName, PackageCrawler packages, JavaTXCompiler compiler) throws ClassNotFoundException {
		Map<String, Integer> ret = new HashMap<>();
		ClassorinterfacedeclContext clsoif = (ClassorinterfacedeclContext) clsoifctx;
		String nameString = "";
		String fullname = clsoif.getChild(clsoif.getChildCount() - 1).getClass().getName();
		String classname = fullname.substring(fullname.indexOf("$") + 1);
		int numGenerics = 0;
		/*
		 * Es werden alle Namen gesammelt, die syntaktisch von Java-TX (sprich der Grammatik) erkannt werden. Auch wenn z.B. Annotationen oder Enumerationen noch nicht im Compiler implementiert sind. Die "NotImplementedException" wird dann im "SyntaxTreeGenerator" geworfen. Das Statement soll als Vorbereitung dienen, für den Fall, dass weitere Sprachkonstrukte in den Compiler aufgenommen werden.
		 */
		switch (classname) {
		case "ClassDeclarationContext":
			if (!pkgName.isEmpty()) {
				nameString = pkgName + "." + clsoif.classDeclaration().identifier().getText();
			} else {
				nameString = clsoif.classDeclaration().identifier().getText();
			}
			numGenerics = clsoif.classDeclaration().genericDeclarationList() != null ? clsoif.classDeclaration().genericDeclarationList().genericTypeVar().size() : 0;
			ret.put(nameString, numGenerics);
			ret.putAll(getNames(clsoif.classDeclaration().classBody().classBodyDeclaration(), pkgName, packages, compiler));
			break;
		case "EnumDeclarationContext":
			if (!pkgName.isEmpty()) {
				nameString = pkgName + "." + clsoif.enumDeclaration().identifier().getText();
			} else {
				nameString = clsoif.enumDeclaration().identifier().getText();
			}
			numGenerics = 0;
			ret.put(nameString, numGenerics);
			EnumConstantsContext enumConstants = clsoif.enumDeclaration().enumConstants();
			if (!Objects.isNull(enumConstants)) {
				for (EnumConstantContext enumConstant : enumConstants.enumConstant()) {
					ClassBodyContext enumConstClassBody = enumConstant.classBody();
					if (!Objects.isNull(enumConstClassBody)) {
						ret.putAll(getNames(enumConstClassBody.classBodyDeclaration(), pkgName, packages, compiler));
					}
				}
			}
			break;
		case "InterfaceDeclarationContext":
			if (pkgName != "") {
				nameString = pkgName + "." + clsoif.interfaceDeclaration().identifier().getText();
			} else {
				nameString = clsoif.interfaceDeclaration().identifier().getText();
			}
			numGenerics = clsoif.interfaceDeclaration().genericDeclarationList() != null ? clsoif.interfaceDeclaration().genericDeclarationList().genericTypeVar().size() : 0;
			ret.put(nameString, numGenerics);
			for (InterfaceBodyDeclarationContext ifbody : clsoif.interfaceDeclaration().interfaceBody().interfaceBodyDeclaration()) {
				if (ifbody instanceof InterfacememberContext member && member.interfaceMemberDeclaration() instanceof SubclassorinterfaceContext sub) {
					ret.putAll(getNames(sub.classOrInterface(), pkgName, packages, compiler));
				}
			}
			break;
		case "AnnotationTypeDeclarationContext":
			if (pkgName != "") {
				nameString = pkgName + "." + clsoif.annotationTypeDeclaration().identifier().getText();
			} else {
				nameString = clsoif.annotationTypeDeclaration().identifier().getText();
			}
			numGenerics = 0;
			ret.put(nameString, numGenerics);
			for (AnnotationTypeElementDeclarationContext anTypeElem : clsoif.annotationTypeDeclaration().annotationTypeBody().annotationTypeElementDeclaration()) {
				ClassOrInterfaceContext anClsoifctx = anTypeElem.annotationTypeElementRest().classOrInterface();
				if (!Objects.isNull(anClsoifctx)) {
					ret.putAll(getNames(anClsoifctx, pkgName, packages, compiler));
				}
			}
			break;
		case "RecordDeclarationContext":
			if (pkgName != "") {
				nameString = pkgName + "." + clsoif.recordDeclaration().identifier().getText();
			} else {
				nameString = clsoif.recordDeclaration().identifier().getText();
			}
			numGenerics = clsoif.recordDeclaration().genericDeclarationList() != null ? clsoif.recordDeclaration().genericDeclarationList().genericTypeVar().size() : 0;
			ret.put(nameString, numGenerics);
			ret.putAll(getNames(clsoif.recordDeclaration().recordBody().classBodyDeclaration(), pkgName, packages, compiler));
			break;
		default:
			throw new NotImplementedException();
		}
		return ret;
	}

	public static Map<String, Integer> getNames(List<ClassBodyDeclarationContext> clsBodyDecl, String pkgName, PackageCrawler packages, JavaTXCompiler compiler) throws ClassNotFoundException {
		Map<String, Integer> ret = new HashMap<>();
		for (ClassBodyDeclarationContext clsbody : clsBodyDecl) {
			if (clsbody instanceof MemberdeclContext member && member.memberDeclaration() instanceof MemberclassorinterfaceContext memberclsoifctx) {
				ret.putAll(getNames(memberclsoifctx.classOrInterface(), pkgName, packages, compiler));
			}
		}
		return ret;
	}

	public static Map<String, Integer> getImports(Java17Parser.SrcfileContext ctx, PackageCrawler packages, JavaTXCompiler compiler) throws ClassNotFoundException {
		Map<String, Integer> ret = new HashMap<>();
		// ret.putAll(packages.getClassNames("java.lang"));
		for (Java17Parser.ImportDeclarationContext importDeclCtx : ctx.importDeclaration()) {
			if (importDeclCtx.MUL() == null) {
				var name = importDeclCtx.qualifiedName().getText();
				var className = new JavaClassName(name);
				if (compiler.input.contains(className)) {
					ret.put(name, compiler.classRegistry.getNumberOfGenerics(name));
				} else {
					Class<?> cl = compiler.getClassLoader().loadClass(name);
					ret.put(cl.getName(), cl.getTypeParameters().length);
				}
			} else if (importDeclCtx.MUL() != null) {
				// TODO Find stuff in user defined packages
				ret.putAll(packages.getClassNames(importDeclCtx.qualifiedName().getText()));
			}
			// Die Unterscheidungen für 'static imports' wurden herausgenommen, da sie den
			// auszuführenden Code nicht beeinflussen
		}
		return ret;
	}

	private static String getPackageName(Java17Parser.SrcfileContext ctx) {
		String pkgName = "";
		if (ctx.packageDeclaration() != null) {
			pkgName = ctx.packageDeclaration().qualifiedName().getText();
		}
		return pkgName;
	}
}