package de.dhbwstuttgart.parser.SyntaxTreeGenerator;

import de.dhbwstuttgart.exceptions.NotImplementedException;
import de.dhbwstuttgart.parser.InvalidClassNameException;
import de.dhbwstuttgart.parser.NullToken;
import de.dhbwstuttgart.parser.PackageCrawler;
import de.dhbwstuttgart.parser.antlr.Java8Parser;
import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.syntaxtree.factory.ASTFactory;
import de.dhbwstuttgart.syntaxtree.statement.*;
import de.dhbwstuttgart.syntaxtree.type.RefType;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;
import de.dhbwstuttgart.typecheck.*;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.TerminalNode;

public class SyntaxTreeGenerator{
  private JavaClassRegistry reg;
  private String pkgName = "";
  List<JavaClassName> imports = new ArrayList();
  private GenericsRegistry generics = new GenericsRegistry();

  public SyntaxTreeGenerator(JavaClassRegistry reg){
    this.reg = reg;
  }
  
  public void setPackageName(Java8Parser.CompilationUnitContext ctx){
    if(ctx.packageDeclaration() != null){
      for(TerminalNode t : ctx.packageDeclaration().Identifier()){
        this.pkgName = this.pkgName + "." + t.toString();
      }
      this.pkgName = this.pkgName.substring(1);
    }
  }
  
  public void getNames(Java8Parser.CompilationUnitContext ctx){
    if(this.pkgName == "") this.setPackageName(ctx);
    String nameString = "";
    for (Java8Parser.TypeDeclarationContext typeDecl : ctx.typeDeclaration()){
      if(typeDecl.interfaceDeclaration() != null){
        if(typeDecl.interfaceDeclaration().normalInterfaceDeclaration() != null){
          if(this.pkgName != ""){
            nameString = this.pkgName + "." + typeDecl.interfaceDeclaration().normalInterfaceDeclaration().Identifier().toString();
          }
          else{
            nameString = typeDecl.interfaceDeclaration().normalInterfaceDeclaration().Identifier().toString();
          }
          //Die Generic TypeParameter Definitionen Nicht! an die JavaClassName-Registry anfügen:
          if(typeDecl.classDeclaration().normalClassDeclaration().typeParameters() != null){
            for(Java8Parser.TypeParameterContext tp : typeDecl.classDeclaration().normalClassDeclaration().typeParameters().typeParameterList().typeParameter()){
              //this.reg.add(tp.Identifier().toString());
            }
          }
          this.reg.add(nameString);
        }
      }
      else{
        if(typeDecl.classDeclaration().normalClassDeclaration() != null){
          if(this.pkgName != ""){
            nameString = this.pkgName + "." + typeDecl.classDeclaration().normalClassDeclaration().Identifier().toString();
          }
          else{
            nameString = typeDecl.classDeclaration().normalClassDeclaration().Identifier().toString();
          }
          //Die Generic TypeParameter Definitionen ebenfalls an die JavaClassName-Registry anfügen:
          if(typeDecl.classDeclaration().normalClassDeclaration().typeParameters() != null){
            for(Java8Parser.TypeParameterContext tp : typeDecl.classDeclaration().normalClassDeclaration().typeParameters().typeParameterList().typeParameter()){
              this.reg.add(tp.Identifier().toString());
            }
          }
          this.reg.add(nameString);
        }
      }
    }
  }

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

  // Converts type name to String.
  public String convertTypeName(Java8Parser.TypeNameContext ctx){
    String ret;
    if(ctx.packageOrTypeName() == null){
      ret = ctx.Identifier().toString();
    }
    else{
      ret = convertPackageOrTypeName(ctx.packageOrTypeName()) + "." + ctx.Identifier().toString();
    }
    return ret;
  }
  
  // Converts PackageOrTypeName to String.
  public String convertPackageOrTypeName(Java8Parser.PackageOrTypeNameContext ctx){
    String ret;
    if(ctx.packageOrTypeName() == null){
      ret = ctx.Identifier().toString();
    }
    else{
      ret = convertPackageOrTypeName(ctx.packageOrTypeName()) + "." + ctx.Identifier().toString();
    }
    return ret;
  }
  
  public void setImports(Java8Parser.CompilationUnitContext ctx) throws InvalidClassNameException {
    List<JavaClassName> newImports = new ArrayList();
    for(Java8Parser.ImportDeclarationContext importDeclCtx : ctx.importDeclaration()){
      if(importDeclCtx.singleTypeImportDeclaration() != null){
        newImports.add(convertSingleTypeImportDeclaration(importDeclCtx.singleTypeImportDeclaration()));
      }
      else if(importDeclCtx.typeImportOnDemandDeclaration() != null){
        newImports.add(convertTypeImportOnDemandDeclaration(importDeclCtx.typeImportOnDemandDeclaration()));
      }
      else if(importDeclCtx.singleStaticImportDeclaration() != null){
        newImports.add(convertSingleStaticImportDeclaration(importDeclCtx.singleStaticImportDeclaration()));
      }
      else{
        newImports.add(convertStaticImportOnDemandDeclaration(importDeclCtx.staticImportOnDemandDeclaration()));
      }
    }
    this.imports.addAll(newImports);
  }
  
  private JavaClassName convertSingleTypeImportDeclaration(Java8Parser.SingleTypeImportDeclarationContext ctx) throws InvalidClassNameException{
    String typeName = convertTypeName(ctx.typeName());
    String packageName = getPackageFromClass(typeName);
    List<JavaClassName> classes = PackageCrawler.getClassNames(packageName);
    reg.add(typeName);
    JavaClassName ret = reg.getName(typeName);
    if(classes.contains(ret)){
      return ret;
    }
    else{
      throw new InvalidClassNameException();
    }
  }
  
  private JavaClassName convertTypeImportOnDemandDeclaration(Java8Parser.TypeImportOnDemandDeclarationContext ctx){
    return null;
  }
  
  private JavaClassName convertSingleStaticImportDeclaration(Java8Parser.SingleStaticImportDeclarationContext ctx){
    return null;
  }
  
  private JavaClassName convertStaticImportOnDemandDeclaration(Java8Parser.StaticImportOnDemandDeclarationContext ctx){
    return null;
  }
  
  private String getPackageFromClass(String cls){
    String ret = "";
    String[] parts = cls.split("\\.");
    for(int i = 0; i < parts.length - 1; i++){
      ret = ret + "." + parts[i];
    }
    ret = ret.substring(1);
    return ret;
  }
  
  public SourceFile convert(Java8Parser.CompilationUnitContext ctx){
    List<ClassOrInterface> classes = new ArrayList<>();
    this.getNames(ctx);
    for(Java8Parser.TypeDeclarationContext typeDecl : ctx.typeDeclaration()){
      ClassOrInterface newClass;
      if(typeDecl.classDeclaration() != null){
        newClass = convertClass(typeDecl.classDeclaration());
      }
      else{
        newClass = convertInterface(typeDecl.interfaceDeclaration());
      }
      classes.add(newClass);
    }
    return new SourceFile(this.pkgName, classes, this.imports);
  }

  private ClassOrInterface convertClass(Java8Parser.ClassDeclarationContext ctx) {
    ClassOrInterface newClass;
    if(ctx.normalClassDeclaration() != null){
      newClass = convertNormal(ctx.normalClassDeclaration());
    }
    else{
      newClass = convertEnum(ctx.enumDeclaration());
    }
    return newClass;
  }
  
  private ClassOrInterface convertNormal(Java8Parser.NormalClassDeclarationContext ctx){
    int modifiers = 0;
    if(ctx.classModifier() != null){
      for(Java8Parser.ClassModifierContext mod : ctx.classModifier()){
        int newModifier = convert(mod);
        modifiers += newModifier;
      }
    }
    JavaClassName name = reg.getName(ctx.Identifier().getText());
    GenericDeclarationList genericClassParameters = TypeGenerator.convert(ctx.typeParameters(), name, "",reg, generics);
    List<Field> fielddecl = convertFields(ctx.classBody());
    List<Method> methods = convertMethods(ctx.classBody(), name);

    Token offset = ctx.getStart();
    RefType superClass ;
    if(ctx.superclass() != null){
      superClass = convert(ctx.superclass());
    }else{
      superClass = new RefType(new JavaClassName("java.lang.Object"), new NullToken());
    }
    Boolean isInterface = false;
    List<RefTypeOrTPHOrWildcardOrGeneric> implementedInterfaces = null;
    return new ClassOrInterface(modifiers, name, fielddecl, methods, genericClassParameters, superClass, isInterface, implementedInterfaces, offset);
  }

  private RefType convert(Java8Parser.SuperclassContext superclass) {
    throw new NotImplementedException();
  }

  private List<Method> convertMethods(Java8Parser.ClassBodyContext classBodyContext, JavaClassName parentClass) {
    List<Method> ret = new ArrayList<>();
    for(Java8Parser.ClassBodyDeclarationContext classMember : classBodyContext.classBodyDeclaration()){
      if(classMember.classMemberDeclaration() != null){
        Java8Parser.ClassMemberDeclarationContext classMemberDeclarationContext = classMember.classMemberDeclaration();
        if(classMemberDeclarationContext.fieldDeclaration() != null){
          //Do nothing!
        }else if(classMemberDeclarationContext.methodDeclaration()!= null){
          StatementGenerator stmtGen = new StatementGenerator(reg, generics);
          ret.add(stmtGen.convert(classMemberDeclarationContext.methodDeclaration(), parentClass));
        }
      }
    }
    return ret;
  }

  private List<Field> convertFields(Java8Parser.ClassBodyContext classBodyContext) {
    List<Field> ret = new ArrayList<>();
    for(Java8Parser.ClassBodyDeclarationContext classMember : classBodyContext.classBodyDeclaration()){
      if(classMember.classMemberDeclaration() != null){
        Java8Parser.ClassMemberDeclarationContext classMemberDeclarationContext = classMember.classMemberDeclaration();
        if(classMemberDeclarationContext.fieldDeclaration() != null){
          ret.addAll(convert(classMember.classMemberDeclaration().fieldDeclaration()));
        }else if(classMemberDeclarationContext.methodDeclaration()!= null){
          //Do nothing!
        }
      }
    }
    return ret;
  }

  public static int convert(List<Java8Parser.MethodModifierContext> methodModifierContexts) {
    int ret = 0;
    for(Java8Parser.MethodModifierContext mod : methodModifierContexts){
      if(mod.annotation() == null)convertModifier(mod.getText());
    }
    return ret;
  }

  private List<? extends Field> convert(Java8Parser.FieldDeclarationContext fieldDeclarationContext) {
    List<Field> ret = new ArrayList<>();
    int modifiers = 0;
    for(Java8Parser.FieldModifierContext fieldModifierContext : fieldDeclarationContext.fieldModifier()){
      modifiers+=(convert(fieldModifierContext));
    }
    RefTypeOrTPHOrWildcardOrGeneric fieldType;
    if(fieldDeclarationContext.unannType() != null){
      fieldType = TypeGenerator.convert(fieldDeclarationContext.unannType(), reg, generics);
    }else{
      fieldType = TypePlaceholder.fresh(fieldDeclarationContext.getStart());
    }
    for(Java8Parser.VariableDeclaratorContext varCtx : fieldDeclarationContext.variableDeclaratorList().variableDeclarator()){
      String fieldName = convert(varCtx.variableDeclaratorId());
      if(varCtx.variableInitializer() != null){
        initializeField(fieldDeclarationContext);
      }
      else{
        ret.add(new Field(fieldName,fieldType,modifiers,varCtx.getStart()));
      }
    }
    return ret;
  }

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

  // Initialize a field by creating implicit constructor.
  private void initializeField(Java8Parser.FieldDeclarationContext ctx){
    //TODO
  }

  public static int convertModifier(String modifier){
    HashMap<String, Integer> modifiers = new HashMap<>();
    modifiers.put(Modifier.toString(Modifier.PUBLIC), Modifier.PUBLIC);
    modifiers.put(Modifier.toString(Modifier.PRIVATE), Modifier.PRIVATE);
    modifiers.put(Modifier.toString(Modifier.PROTECTED), Modifier.PROTECTED);
    modifiers.put(Modifier.toString(Modifier.ABSTRACT), Modifier.ABSTRACT);
    modifiers.put(Modifier.toString(Modifier.STATIC), Modifier.STATIC);
    modifiers.put(Modifier.toString(Modifier.STRICT), Modifier.STRICT);
    modifiers.put(Modifier.toString(Modifier.FINAL), Modifier.FINAL);
    modifiers.put(Modifier.toString(Modifier.TRANSIENT), Modifier.TRANSIENT);
    modifiers.put(Modifier.toString(Modifier.VOLATILE), Modifier.VOLATILE);
    modifiers.put(Modifier.toString(Modifier.SYNCHRONIZED), Modifier.SYNCHRONIZED);
    modifiers.put(Modifier.toString(Modifier.NATIVE), Modifier.NATIVE);
    modifiers.put(Modifier.toString(Modifier.INTERFACE), Modifier.INTERFACE);
    int ret = 0;
    for(String m : modifiers.keySet()){
      if(modifier.startsWith(m))ret+=modifiers.get(m);
    }
    return ret;
  }

  private int convert(Java8Parser.ClassModifierContext ctx){
    if(ctx.annotation() != null)return 0;
    return convertModifier(ctx.getText());
  }
  
  private int convert(Java8Parser.FieldModifierContext ctx){
    if(ctx.annotation() != null)return 0;
    return convertModifier(ctx.getText());
  }
  
  private ClassOrInterface convertEnum(Java8Parser.EnumDeclarationContext ctx){
    return null;
  }
  
  private ClassOrInterface convertInterface(Java8Parser.InterfaceDeclarationContext ctx){
    return null;
  }

}
