package de.dhbwstuttgart.parser.SyntaxTreeGenerator;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import de.dhbwstuttgart.core.JavaTXCompiler;
import de.dhbwstuttgart.parser.NullToken;
import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.syntaxtree.Record;
import de.dhbwstuttgart.syntaxtree.statement.*;
import org.antlr.v4.runtime.CommonToken;
import org.antlr.v4.runtime.Token;

import com.google.common.graph.ElementOrder.Type;

import de.dhbwstuttgart.environment.PackageCrawler;
import de.dhbwstuttgart.exceptions.NotImplementedException;
import de.dhbwstuttgart.exceptions.TypeinferenceException;
import de.dhbwstuttgart.parser.antlr.Java17Parser;
import de.dhbwstuttgart.parser.antlr.Java17Parser.ClassBodyDeclarationContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.ClassOrInterfaceTypeContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.ClassorinterfacedeclContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.ConstructorDeclarationContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.ConstructordeclContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.EmptymethodContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.GenericConstructorDeclarationContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.GenericDeclarationListContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.GenericInterfaceMethodDeclarationContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.GenericconstructorContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.GenericinterfacemethodContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.GenericmethodContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.InterfaceBodyDeclarationContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.InterfaceCommonBodyDeclarationContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.InterfaceMethodDeclarationContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.InterfaceMethodModifierContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.InterfaceconstContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.InterfacememberContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.InterfacemethodContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.MemberclassorinterfaceContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.MemberconstructorContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.MemberdeclContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.MemberfieldContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.MembermethodContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.MethodBodyContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.MethodDeclarationContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.MethodHeaderContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.MethodblockContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.MethoddeclContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.ModifierContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.NoclassorinterfaceContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.RecordComponentContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.RecordDeclarationContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.RefType2Context;
import de.dhbwstuttgart.parser.antlr.Java17Parser.SrcfileContext;
import de.dhbwstuttgart.parser.antlr.Java17Parser.TypeArgumentsContext;
import de.dhbwstuttgart.parser.scope.GatherNames;
import de.dhbwstuttgart.parser.scope.GenericsRegistry;
import de.dhbwstuttgart.parser.scope.JavaClassName;
import de.dhbwstuttgart.parser.scope.JavaClassRegistry;
import de.dhbwstuttgart.syntaxtree.factory.ASTFactory;
import de.dhbwstuttgart.syntaxtree.type.RefType;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;
import de.dhbwstuttgart.syntaxtree.type.Void;

public class SyntaxTreeGenerator {
  private JavaClassRegistry reg;
  private final GenericsRegistry globalGenerics;
  public String pkgName = "";
  public Set<JavaClassName> imports = new HashSet<>();
  HashMap<String, Integer> allmodifiers = new HashMap<>();
  // PL 2018-11-01 fields eingefuegt, damit die fields immer die gleiche TPH
  // bekommen
  private final Map<String, FieldEntry> fields = new HashMap<>();
  // PL 2019-10-23: Muss für jede Klasse neu initilisiert werden
  List<Statement> fieldInitializations = new ArrayList<>();
  List<Statement> staticFieldInitializations = new ArrayList<>();

  private final JavaTXCompiler compiler;
  private RefType superClass;

  public final String fileName;

  public SyntaxTreeGenerator(JavaTXCompiler compiler, JavaClassRegistry reg, GenericsRegistry globalGenerics, String fileName) {
    // Die Generics müssen während des Bauens des AST erstellt werden,
    // da diese mit der Methode oder Klasse, in welcher sie deklariert werden
    // verknüpft sein müssen. Dennoch werden die Namen aller Generics in einer
    // globalen Datenbank benötigt.
    this.globalGenerics = globalGenerics;
    this.reg = reg;
    this.allmodifiers.put(Modifier.toString(Modifier.PUBLIC), Modifier.PUBLIC);
    this.allmodifiers.put(Modifier.toString(Modifier.PRIVATE), Modifier.PRIVATE);
    this.allmodifiers.put(Modifier.toString(Modifier.PROTECTED), Modifier.PROTECTED);
    this.allmodifiers.put(Modifier.toString(Modifier.ABSTRACT), Modifier.ABSTRACT);
    this.allmodifiers.put(Modifier.toString(Modifier.STATIC), Modifier.STATIC);
    this.allmodifiers.put(Modifier.toString(Modifier.STRICT), Modifier.STRICT);
    this.allmodifiers.put(Modifier.toString(Modifier.FINAL), Modifier.FINAL);
    this.allmodifiers.put(Modifier.toString(Modifier.TRANSIENT), Modifier.TRANSIENT);
    this.allmodifiers.put(Modifier.toString(Modifier.VOLATILE), Modifier.VOLATILE);
    this.allmodifiers.put(Modifier.toString(Modifier.SYNCHRONIZED), Modifier.SYNCHRONIZED);
    this.allmodifiers.put(Modifier.toString(Modifier.NATIVE), Modifier.NATIVE);
    this.allmodifiers.put(Modifier.toString(Modifier.INTERFACE), Modifier.INTERFACE);
    this.allmodifiers.put("sealed", 4096);
    this.allmodifiers.put("non-sealed", 8192);
    this.allmodifiers.put("default", 0); // Doesn't exist
    this.allmodifiers.put("strictfp", 32768);

    this.compiler = compiler;
    this.fileName = fileName;
  }

  public JavaClassRegistry getReg() {
    return this.reg;
  }

  public String convertQualifiedName(Java17Parser.QualifiedNameContext ctx) {
    /*
     * String ret = ""; for (Java17Parser.IdentifierContext ident : ctx.identifier()) { ret += ident.getText(); if (ctx.identifier().iterator().hasNext()) { ret += '.'; } }
     */
    return ctx.getText();
  }

  public void convert(List<ClassOrInterface> classes, Java17Parser.SourceFileContext ctx, PackageCrawler packageCrawler) throws ClassNotFoundException, NotImplementedException {
    SrcfileContext srcfile;
    if (ctx instanceof Java17Parser.SrcfileContext) {
      srcfile = (SrcfileContext) ctx;
    } else {
      return;
    }
    if (srcfile.packageDeclaration() != null)
      this.pkgName = convert(srcfile.packageDeclaration());
    Map<String, Integer> imports = GatherNames.getImports(srcfile, packageCrawler, compiler);
    this.imports.addAll(imports.keySet().stream().map(name -> reg.getName(name)).collect(Collectors.toSet()));

    for (Java17Parser.ClassOrInterfaceContext type : srcfile.classOrInterface()) {
      ClassorinterfacedeclContext clsoif;
      if (type instanceof NoclassorinterfaceContext) {
        continue;
      } else {
        clsoif = (ClassorinterfacedeclContext) type;
      }
      ClassOrInterface newClass;
      int modifiers = 0;
      if (!clsoif.classOrInterfaceModifier().isEmpty()) {
        for (Java17Parser.ClassOrInterfaceModifierContext mod : clsoif.classOrInterfaceModifier()) {
          modifiers += allmodifiers.get(mod.getText());
        }
      }
      fieldInitializations = new ArrayList<>(); // PL 2019-10-22: muss für jede Klasse neu initilisiert werden
      staticFieldInitializations = new ArrayList<>();
      if (!Objects.isNull(clsoif.classDeclaration())) {
        newClass = convertClass(clsoif.classDeclaration(), modifiers);
      } else if (!Objects.isNull(clsoif.interfaceDeclaration())) {
        newClass = convertInterface(clsoif.interfaceDeclaration(), modifiers);
      } else if (!Objects.isNull(clsoif.recordDeclaration())) {
        newClass = convertRecord(clsoif.recordDeclaration(), modifiers);
      } else {
        throw new NotImplementedException();
      }
      classes.add(newClass);
    }
    if (classes.isEmpty()) {
      throw new NotImplementedException("SourceFile enthält keine Klassen");
    }
  }

  private String convert(Java17Parser.PackageDeclarationContext ctx) {
    return convertQualifiedName(ctx.qualifiedName());
  }

  private ClassOrInterface convertClass(Java17Parser.ClassDeclarationContext ctx, int modifiers) {
    String className = this.pkgName + (this.pkgName.length() > 0 ? "." : "") + ctx.identifier().getText();
    JavaClassName name = reg.getName(className); // Holt den Package Namen mit dazu
    if (!name.toString().equals(className)) { // Kommt die Klasse schon in einem anderen Package vor?
      throw new TypeinferenceException("Name " + className + " bereits vorhanden in " + reg.getName(className).toString(), ctx.getStart());
    }
    GenericsRegistry generics = createGenerics(ctx.genericDeclarationList(), name, "", reg, new GenericsRegistry(globalGenerics));
    Token offset = ctx.getStart();
    GenericDeclarationList genericClassParameters;
    if (ctx.genericDeclarationList() == null) {
      genericClassParameters = new GenericDeclarationList(new ArrayList<>(), ctx.classBody().getStart());
    } else {
      genericClassParameters = TypeGenerator.convert(ctx.genericDeclarationList(), name, "", reg, generics);
    }
    RefType superClass;
    if (ctx.EXTENDS() != null) {
      superClass = convertSuperType(ctx.typeType());
    } else {
      superClass = new RefType(ASTFactory.createObjectClass().getClassName(), ctx.getStart());
    }
    this.superClass = superClass;
    List<Field> fielddecl = new ArrayList<>();
    List<Method> methods = new ArrayList<>();
    List<Constructor> constructors = new ArrayList<>();
    Boolean isInterface = false;
    Boolean isFunctionalInterface = false;
    List<RefType> implementedInterfaces = new ArrayList<>();
    List<RefType> permittedSubtypes = null;
    for (ClassBodyDeclarationContext clsbodydecl : ctx.classBody().classBodyDeclaration()) {
      convert(clsbodydecl, fielddecl, constructors, methods, name, superClass, generics);
    }
    if (constructors.isEmpty()) {
      constructors.add(generateStandardConstructor(ctx.identifier().getText(), name, superClass, genericClassParameters, offset));
    }
    if (ctx.IMPLEMENTS() != null) {
      implementedInterfaces.addAll(convert(ctx.typeList(0), generics));
    }
    // Ist Bit für 'sealed'-Modifier gesetzt
    if ((modifiers & 4096) != 0) {
      if (!Objects.isNull(ctx.PERMITS())) {
        // permitted subtypes sind letzte typeList (siehe Grammatikregel 'classDeclaration')
        permittedSubtypes = new ArrayList<>(convert(ctx.typeList(ctx.typeList().size() - 1), generics));
      } else {
        // falls sealed modifier ohne 'permits'-List oder umgekehrt
        throw new NotImplementedException("Invalid sealed class declaration");
      }
    }
    var ctor = Optional.of(this.generatePseudoConstructor(ctx.identifier().getText(), fieldInitializations, genericClassParameters, offset));
    var staticCtor = Optional.of(this.generateStaticConstructor(ctx.identifier().getText(), staticFieldInitializations, genericClassParameters, offset));
    return new ClassOrInterface(modifiers, name, fielddecl, ctor, staticCtor, methods, constructors, genericClassParameters, superClass, isInterface, isFunctionalInterface, implementedInterfaces, permittedSubtypes, offset, fileName);

  }

  private de.dhbwstuttgart.syntaxtree.Record convertRecord(RecordDeclarationContext recordDeclaration, int modifiers) {
    this.superClass = new RefType(new JavaClassName("java.lang.Record"), new NullToken());
    String identifier = recordDeclaration.identifier().getText();
    String className = this.pkgName + (this.pkgName.length() > 0 ? "." : "") + identifier;
    JavaClassName name = reg.getName(className); // Holt den Package Namen mit dazu
    Token offset = recordDeclaration.getStart();
    GenericsRegistry generics = createGenerics(recordDeclaration.genericDeclarationList(), name, "", reg, new GenericsRegistry(globalGenerics));
    if (!name.toString().equals(className)) { // Kommt die Klasse schon in einem anderen Package vor?
      throw new TypeinferenceException("Name " + className + " bereits vorhanden in " + reg.getName(className).toString(), recordDeclaration.getStart());
    }
    GenericDeclarationList genericClassParameters;
    if (recordDeclaration.genericDeclarationList() == null) {
      genericClassParameters = new GenericDeclarationList(new ArrayList<>(), recordDeclaration.recordBody().getStart());
    } else {
      genericClassParameters = TypeGenerator.convert(recordDeclaration.genericDeclarationList(), name, "", reg, generics);
    }
    RefType superClass = new RefType(ASTFactory.createClass(java.lang.Record.class).getClassName(), offset);
    List<Field> fielddecl = new ArrayList<>();
    List<Method> methods = new ArrayList<>();
    List<Constructor> constructors = new ArrayList<>();
    Boolean isInterface = false;
    List<RefType> implementedInterfaces = new ArrayList<>();
    List<Pattern> constructorParameters = new ArrayList<>();
    List<Statement> constructorStatements = new ArrayList<>();

    List<Java17Parser.RecordComponentContext> components = recordDeclaration.recordHeader().recordComponentList() != null ?
              recordDeclaration.recordHeader().recordComponentList().recordComponent(): List.of();
    for (RecordComponentContext component : components) {
      int fieldmodifiers = allmodifiers.get("private") + allmodifiers.get("final");
      String fieldname = component.identifier().getText();
      Token fieldoffset = component.getStart();
      RefTypeOrTPHOrWildcardOrGeneric fieldtype = null;
      if (Objects.isNull(component.typeType())) {
        fieldtype = TypePlaceholder.fresh(offset);
      } else {
        fieldtype = TypeGenerator.convert(component.typeType(), reg, generics);
      }
      fielddecl.add(new Field(fieldname, fieldtype, fieldmodifiers, fieldoffset));
      constructorParameters.add(new FormalParameter(fieldname, fieldtype, fieldoffset));
      FieldVar fieldvar = new FieldVar(new This(offset), fieldname, fieldtype, fieldoffset);
      constructorStatements.add(new Assign(new AssignToField(fieldvar), new LocalVar(fieldname, fieldtype, fieldoffset), offset));
      Statement returnStatement = new Return(fieldvar, offset);
      methods.add(new Method(allmodifiers.get("public"), fieldname, fieldtype, new ParameterList(new ArrayList<>(), offset), new Block(Arrays.asList(returnStatement), offset), new GenericDeclarationList(new ArrayList<>(), offset), offset));
    }
    RefType classType = ClassOrInterface.generateTypeOfClass(reg.getName(className), genericClassParameters, offset);
    Constructor implicitConstructor = new Constructor(allmodifiers.get("public"), identifier, classType, new ParameterList(constructorParameters, offset), prepareBlock(new Block(constructorStatements, offset), superClass), genericClassParameters, offset);
    //Optional<Constructor> initializations = Optional.of(implicitConstructor);
    constructors.add(implicitConstructor);
    for (ClassBodyDeclarationContext bodyDeclaration : recordDeclaration.recordBody().classBodyDeclaration()) {
      convert(bodyDeclaration, fielddecl, constructors, methods, name, superClass, generics);
    }
    if (!Objects.isNull(recordDeclaration.IMPLEMENTS())) {
      implementedInterfaces.addAll(convert(recordDeclaration.typeList(), generics));
    }
    var staticCtor = Optional.of(this.generateStaticConstructor(recordDeclaration.identifier().getText(), staticFieldInitializations, genericClassParameters, offset));
    return new Record(modifiers, name, fielddecl, Optional.empty(), staticCtor, methods, constructors, genericClassParameters, superClass, isInterface, implementedInterfaces, offset, fileName);
  }

  private void convert(ClassBodyDeclarationContext classBody, List<Field> fields, List<Constructor> constructors, List<Method> methods, JavaClassName name, RefType superClass, GenericsRegistry generics) {
    MemberdeclContext member;
    if (classBody instanceof MemberdeclContext) {
      member = (MemberdeclContext) classBody;
      Integer membermodifiers = 0;
      for (ModifierContext mod : member.modifier()) {
        if (mod.classOrInterfaceModifier() != null && mod.classOrInterfaceModifier().annotation() != null)
          continue; // TODO don't eat annotations
        membermodifiers += allmodifiers.get(mod.getText());
      }
      switch (member.memberDeclaration()) {
      case MemberclassorinterfaceContext memberclsoif: {
        break;
      }
      case MemberfieldContext memberfield: {
        fields.addAll(convert(memberfield.fieldDeclaration(), membermodifiers, generics));
        break;
      }
      case MembermethodContext membermethod: {
        Method convertedMethod = convert(membermodifiers, membermethod.method(), name, superClass, generics);
        if (convertedMethod instanceof Constructor constructor) {
          constructors.add(constructor);
        } else {
          methods.add(convertedMethod);
        }
        break;
      }
      case MemberconstructorContext memberconstructor: {
        constructors.add(convert(membermodifiers, memberconstructor.constructor(), name, superClass, generics));
        break;
      }
      default:
        break;
      }
    } else if (classBody instanceof Java17Parser.ClassblockContext ctx && ctx.STATIC() != null) {
      // Static blocks
      var stmtgen = new StatementGenerator(superClass, compiler, reg, generics, this.fields, new HashMap<>());
      var block = stmtgen.convert(((Java17Parser.ClassblockContext) classBody).block(), false);
      staticFieldInitializations.addAll(block.statements);
    }
  }

  private ClassOrInterface convertInterface(Java17Parser.InterfaceDeclarationContext ctx, int modifiers) {
    this.superClass = new RefType(new JavaClassName("java.lang.Object"), new NullToken());
    String className = (this.pkgName.length() > 0 ? this.pkgName + "." : "") + ctx.identifier().getText();
    JavaClassName name = reg.getName(className); // Holt den Package Namen mit dazu
    if (!name.toString().equals(className)) { // Kommt die Klasse schon in einem anderen Package vor?
      throw new TypeinferenceException("Name " + className + " bereits vorhanden in " + reg.getName(className).toString(), ctx.getStart());
    }
    if (!Modifier.isInterface(modifiers))
      modifiers += Modifier.INTERFACE;

    GenericsRegistry generics = createGenerics(ctx.genericDeclarationList(), name, "", reg, new GenericsRegistry(globalGenerics));

    GenericDeclarationList genericParams;
    if (!Objects.isNull(ctx.genericDeclarationList())) {
      genericParams = TypeGenerator.convert(ctx.genericDeclarationList(), name, "", reg, generics);
    } else {
      genericParams = createEmptyGenericDeclarationList(ctx.identifier().getStart());
    }
    RefType superClass = ASTFactory.createObjectType();

    List<Field> fields = new ArrayList<>();
    List<Method> methods = new ArrayList<>();
    for (InterfaceBodyDeclarationContext interfacebody : ctx.interfaceBody().interfaceBodyDeclaration()) {
      if (interfacebody instanceof Java17Parser.EmptyinterfaceContext) {
        continue;
      } else {
        InterfacememberContext interfacemember = (InterfacememberContext) interfacebody;
        int membermodifiers = 0;
        for (ModifierContext mod : interfacemember.modifier()) {
          membermodifiers += allmodifiers.get(mod.getText());
        }
        int methodmodifiers = membermodifiers;
        switch (interfacemember.interfaceMemberDeclaration()) {
        case InterfaceconstContext constant:
          fields.add(convert(constant));
          break;
        case InterfacemethodContext method:
          InterfaceMethodDeclarationContext declaration = method.interfaceMethodDeclaration();
          for (InterfaceMethodModifierContext mod : declaration.interfaceMethodModifier()) {
            methodmodifiers += allmodifiers.get(mod.getText());
          }
          InterfaceCommonBodyDeclarationContext commonbody = declaration.interfaceCommonBodyDeclaration();
          methods.add(convert(methodmodifiers, commonbody, new GenericDeclarationList(new ArrayList<>(), commonbody.getStart()), generics));
          break;
        case GenericinterfacemethodContext genericmethod:
          GenericInterfaceMethodDeclarationContext genericdeclaration = genericmethod.genericInterfaceMethodDeclaration();
          int genericmethodmodifiers = 0;
          for (InterfaceMethodModifierContext mod : genericdeclaration.interfaceMethodModifier()) {
            genericmethodmodifiers += allmodifiers.get(mod.getText());
          }
          commonbody = genericdeclaration.interfaceCommonBodyDeclaration();
          GenericDeclarationList gtv = TypeGenerator.convert(genericdeclaration.genericDeclarationList(), name, commonbody.identifier().getText(), reg, generics);
          methods.add(convert(genericmethodmodifiers, commonbody, gtv, generics));
          break;
        default:
          throw new NotImplementedException();
        }
      }
    }
    List<RefType> extendedInterfaces = new ArrayList<>();
    if (!Objects.isNull(ctx.EXTENDS())) {
      extendedInterfaces.addAll(convert(ctx.typeList(0), generics));
    }
    List<RefType> permittedSubtypes = null;
    // Ist Bit für 'sealed'-Modifier gesetzt
    if ((modifiers & 4096) != 0) {
      if (!Objects.isNull(ctx.PERMITS())) {
        // permitted subtypes sind letzte typeList (siehe Grammatikregel 'classDeclaration')
        permittedSubtypes = new ArrayList<>(convert(ctx.typeList(ctx.typeList().size() - 1), generics));
      } else {
        // falls sealed modifier ohne 'permits'-List oder umgekehrt
        throw new NotImplementedException("Invalid sealed class declaration");
      }
    }

    var staticCtor = Optional.of(this.generateStaticConstructor(ctx.identifier().getText(), staticFieldInitializations, genericParams, ctx.getStart()));
    return new ClassOrInterface(modifiers, name, fields, Optional.empty(), staticCtor, methods, new ArrayList<>(), genericParams, superClass, true, methods.size() == 1 ? true : false, extendedInterfaces, permittedSubtypes, ctx.getStart(), fileName);
  }

  private GenericDeclarationList createEmptyGenericDeclarationList(Token classNameIdentifier) {
    CommonToken gtvOffset = new CommonToken(classNameIdentifier);
    gtvOffset.setCharPositionInLine(gtvOffset.getCharPositionInLine() + classNameIdentifier.getText().length());
    gtvOffset.setStartIndex(gtvOffset.getStopIndex() + 1);
    return new GenericDeclarationList(new ArrayList<>(), gtvOffset);
  }

  private Field convert(InterfaceconstContext constant) {
    // TODO: Erstelle hier ein Feld!
    throw new NotImplementedException();
  }

  private Method convert(int modifiers, InterfaceCommonBodyDeclarationContext bodydeclaration, GenericDeclarationList gtvDeclarations, GenericsRegistry generics) {
    String name = bodydeclaration.identifier().getText();

    RefTypeOrTPHOrWildcardOrGeneric retType;
    if (Objects.isNull(bodydeclaration.refType())) {
      retType = TypePlaceholder.fresh(bodydeclaration.getStart());
    } else {
      if (bodydeclaration.refType() instanceof RefType2Context reftype) {
        retType = TypeGenerator.convert(reftype.typeType(), reg, generics);
      } else {
        retType = new Void(bodydeclaration.refType().getStart());
      }
    }
    StatementGenerator stmtgen = new StatementGenerator(superClass, compiler, reg, generics, fields, new HashMap<>());
    ParameterList paramlist = stmtgen.convert(bodydeclaration.formalParameters().formalParameterList(), true);
    MethodBodyContext body = bodydeclaration.methodBody();
    Block block = null;
    if (!(body instanceof EmptymethodContext)) {
      MethodblockContext methodblock = (MethodblockContext) body;
      block = stmtgen.convert(methodblock.block(), true);
    }
    else {
    	modifiers += Modifier.ABSTRACT;
    }
    return new Method(modifiers, name, retType, paramlist, block, gtvDeclarations, bodydeclaration.getStart());
  }

  protected static Block prepareBlock(Block constructorBlock, RefType superClass) {
    List<Statement> statements = constructorBlock.getStatements();
    if (statements.isEmpty() || !(statements.get(0) instanceof SuperCall || statements.get(0) instanceof ThisCall))  {
      var signature = new ArrayList<TypePlaceholder>();
      signature.add(TypePlaceholder.fresh(new NullToken()));
      statements.add(0, new SuperCall(superClass, signature, constructorBlock.getOffset()));
    }
    /* statements.addAll(fieldInitializations); geloescht PL 2018-11-24 */
    return new Block(statements, constructorBlock.getOffset());
  }

  /**
   * http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.8.9
   */
  private Constructor generateStandardConstructor(String className, JavaClassName parentClass, RefType superClass, GenericDeclarationList classGenerics, Token offset) {
    RefType classType = ClassOrInterface.generateTypeOfClass(reg.getName(className), classGenerics, offset);
    ParameterList params = new ParameterList(new ArrayList<>(), offset);
    return new Constructor(Modifier.PUBLIC, className, classType, params, prepareBlock(new Block(new ArrayList<>(), offset), superClass), classGenerics, offset); // fieldInitializations     // 2018-11-24
  }

  /*
   * fieldInitializations werden in einem Psedokonstruktor in der abstrakten Syntax gespeichert
   */
  private Constructor generatePseudoConstructor(String className, List<Statement> initializations, GenericDeclarationList classGenerics, Token offset) {
    RefType classType = ClassOrInterface.generateTypeOfClass(reg.getName(className), classGenerics, offset);
    ParameterList params = new ParameterList(new ArrayList<>(), offset);
    return new Constructor(Modifier.PUBLIC, className, classType, params, new Block(new ArrayList<>(initializations), offset), classGenerics, offset);
  }

  private Method generateStaticConstructor(String className, List<Statement> initializations, GenericDeclarationList classGenerics, Token offset) {
    RefType classType = ClassOrInterface.generateTypeOfClass(reg.getName(className), classGenerics, offset);
    ParameterList params = new ParameterList(new ArrayList<>(), offset);
    Block block = new Block(new ArrayList<>(initializations), offset);
    return new Method(Modifier.PUBLIC, className, classType, params, block, classGenerics, offset);
  }

  private RefType convertSuperType(Java17Parser.TypeTypeContext typeType) {
    ClassOrInterfaceTypeContext supertypecontext = typeType.classOrInterfaceType();
    if (supertypecontext != null && supertypecontext.DOT().size() > 0) {
      throw new NotImplementedException();
    } else {
      TypeArgumentsContext typeArguments = (typeType.classOrInterfaceType().typeArguments().size() > 0) ? typeType.classOrInterfaceType().typeArguments().get(typeType.classOrInterfaceType().typeArguments().size() - 1) : null;
      RefTypeOrTPHOrWildcardOrGeneric ret = TypeGenerator.convertTypeName(typeType.classOrInterfaceType().typeIdentifier().getText(), typeArguments, typeType.getStart(), reg, globalGenerics);
      if (ret instanceof RefType) {
        return (RefType) ret;
      } else {
        throw new TypeinferenceException(typeType.getText() + " ist kein gültiger Supertyp", typeType.getStart());
      }
    }
  }

  private List<RefType> convert(Java17Parser.TypeListContext ctx, GenericsRegistry generics) {
    List<RefType> ret = new ArrayList<>();
    for (Java17Parser.TypeTypeContext type : ctx.typeType()) {
      ret.add((RefType) TypeGenerator.convert(type, reg, generics));
    }
    return ret;
  }

  public Method convert(int modifiers, Java17Parser.MethodContext methodContext, JavaClassName parentClass, RefType superClass, GenericsRegistry generics) {
    GenericsRegistry localgenerics = generics;
    MethodDeclarationContext methoddeclaration;
    GenericDeclarationListContext genericdeclarations;
    GenericDeclarationList gtvDeclarations;
    MethodHeaderContext header;
    String name;
    if (methodContext instanceof GenericmethodContext) {
      GenericmethodContext gmc = (GenericmethodContext) methodContext;
      genericdeclarations = gmc.genericMethodDeclaration().genericDeclarationList();
      methoddeclaration = gmc.genericMethodDeclaration().methodDeclaration();
      header = methoddeclaration.methodHeader();
      name = header.identifier().getText();
      localgenerics.putAll(createGenerics(genericdeclarations, parentClass, name, reg, generics));
      gtvDeclarations = TypeGenerator.convert(genericdeclarations, parentClass, name, reg, localgenerics);
    } else {
      MethoddeclContext mdc = (MethoddeclContext) methodContext;
      methoddeclaration = mdc.methodDeclaration();
      header = methoddeclaration.methodHeader();
      gtvDeclarations = new GenericDeclarationList(new ArrayList<>(), header.getStart());
      name = header.identifier().getText();
    }

    RefTypeOrTPHOrWildcardOrGeneric retType;
    if (Objects.isNull(header.refType())) {
      retType = TypePlaceholder.fresh(header.getStart(), -1, false);
    } else {
      if (header.refType() instanceof RefType2Context reftype) {
        retType = TypeGenerator.convert(reftype.typeType(), reg, generics);
      } else {
        retType = new Void(header.refType().getStart());
      }
    }
    StatementGenerator stmtgen = new StatementGenerator(superClass, compiler, reg, localgenerics, fields, new HashMap<>());
    ParameterList paramlist = stmtgen.convert(header.formalParameters().formalParameterList(), true);
    MethodBodyContext body = methoddeclaration.methodBody();
    Block block = null;
    if (body instanceof EmptymethodContext emptymethod) {
      if (!Modifier.isAbstract(modifiers)) {
        // TODO: Error! Abstrakte Methode ohne abstrakt Keyword
      }
    } else {
      MethodblockContext methodblock = (MethodblockContext) body;
      block = stmtgen.convert(methodblock.block(), true);
    }
    if (name.equals(parentClass.getClassName())) {
      return new Constructor(modifiers, name, retType, paramlist, prepareBlock(block, superClass), gtvDeclarations, methoddeclaration.getStart());
    } else {
      return new Method(modifiers, name, retType, paramlist, block, gtvDeclarations, methoddeclaration.getStart());
    }
  }

  public Constructor convert(int modifiers, Java17Parser.ConstructorContext constructorContext, JavaClassName parentClass, RefType superClass, GenericsRegistry generics) {
    GenericsRegistry localgenerics = generics;
    GenericDeclarationListContext genericdeclarations;
    GenericDeclarationList gtvDeclarations;
    ConstructorDeclarationContext constructordeclaration;
    String name;
    if (constructorContext instanceof GenericconstructorContext) {
      GenericconstructorContext genericconstructor = (GenericconstructorContext) constructorContext;
      GenericConstructorDeclarationContext gcdeclaration = genericconstructor.genericConstructorDeclaration();
      name = gcdeclaration.constructorDeclaration().identifier().getText();
      genericdeclarations = gcdeclaration.genericDeclarationList();
      constructordeclaration = gcdeclaration.constructorDeclaration();
      localgenerics.putAll(createGenerics(genericdeclarations, parentClass, name, reg, generics));
      gtvDeclarations = TypeGenerator.convert(genericdeclarations, parentClass, name, reg, localgenerics);
    } else {
      ConstructordeclContext constructordeclarationcontext = (ConstructordeclContext) constructorContext;
      constructordeclaration = constructordeclarationcontext.constructorDeclaration();
      name = constructordeclaration.identifier().getText();
      gtvDeclarations = new GenericDeclarationList(new ArrayList<>(), constructordeclaration.getStart());
    }
    RefTypeOrTPHOrWildcardOrGeneric retType = TypeGenerator.convertTypeName(name, constructordeclaration.getStart(), reg, localgenerics);
    StatementGenerator stmtgen = new StatementGenerator(superClass, compiler, reg, localgenerics, fields, new HashMap<>());
    ParameterList paramlist = stmtgen.convert(constructordeclaration.formalParameters().formalParameterList(), true);
    Block block = prepareBlock(stmtgen.convert(constructordeclaration.constructorBody, true), superClass);
    return new Constructor(modifiers, name, retType, paramlist, block, gtvDeclarations, constructordeclaration.getStart());
  }

  List<? extends Field> convert(Java17Parser.FieldDeclarationContext fieldDeclContext, int modifiers, GenericsRegistry generics) {
    List<Field> ret = new ArrayList<>();
    RefTypeOrTPHOrWildcardOrGeneric fieldType;
    if (fieldDeclContext.typeType() != null) {
      fieldType = TypeGenerator.convert(fieldDeclContext.typeType(), reg, generics);
    } else {
      // PL 2019-12-06: variableDeclaratorList() eingefuegt, um als Token nicht die
      // Modifier zu bekommen
      fieldType = TypePlaceholder.fresh(fieldDeclContext.variableDeclarators().getStart(), -1, false);
    }
    for (Java17Parser.VariableDeclaratorContext varDecl : fieldDeclContext.variableDeclarators().variableDeclarator()) {
      String fieldName = varDecl.variableDeclaratorId().getText();
      this.fields.put(fieldName, new FieldEntry(fieldName, fieldType, modifiers));
      if (varDecl.variableInitializer() != null) {
        initializeField(varDecl, Modifier.isStatic(modifiers), fieldType, generics);
      }
      ret.add(new Field(fieldName, fieldType, modifiers, varDecl.getStart()));
    }
    return ret;
  }

  public static String convert(Java17Parser.VariableDeclaratorIdContext variableDeclaratorIdContext) {
    return variableDeclaratorIdContext.getText();
  }

  // Initialize a field by creating implicit constructor.
  private void initializeField(Java17Parser.VariableDeclaratorContext ctx, boolean isStatic, RefTypeOrTPHOrWildcardOrGeneric typeOfField, GenericsRegistry generics) {
    StatementGenerator statementGenerator = new StatementGenerator(superClass, compiler, reg, generics, fields, new HashMap<>());
    var assignment = statementGenerator.generateFieldAssignment(ctx, typeOfField);
    if (isStatic) {
      staticFieldInitializations.add(assignment);
    }
    else fieldInitializations.add(assignment);
  }

  public int convertModifier(String modifier) {
    return allmodifiers.get(modifier);
  }

  private GenericsRegistry createGenerics(Java17Parser.GenericDeclarationListContext ctx, JavaClassName parentClass, String parentMethod, JavaClassRegistry reg, GenericsRegistry generics) {
    GenericsRegistry ret = new GenericsRegistry(this.globalGenerics);
    ret.putAll(generics);
    if (ctx == null)
      return ret;
    for (Java17Parser.GenericTypeVarContext tp : ctx.genericTypeVar()) {
      ret.put(tp.identifier().getText(), new GenericContext(parentClass, parentMethod));
      TypeGenerator.convert(tp, parentClass, parentMethod, reg, ret);
    }
    return ret;
  }
}
