package de.dhbwstuttgart.syntaxtree;

import de.dhbwstuttgart.core.IItemWithOffset;
import de.dhbwstuttgart.exceptions.DebugException;
import de.dhbwstuttgart.parser.scope.JavaClassName;
import de.dhbwstuttgart.syntaxtree.statement.Statement;
import de.dhbwstuttgart.syntaxtree.type.RefType;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;
import de.dhbwstuttgart.syntaxtree.visual.ASTPrinter;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import de.dhbwstuttgart.typeinference.constraints.Constraint;
import de.dhbwstuttgart.typeinference.constraints.ConstraintSet;
import de.dhbwstuttgart.typeinference.assumptions.TypeInferenceInformation;
import org.antlr.v4.runtime.Token;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
 * Stellt jede Art von Klasse dar. Auch abstrakte Klassen und Interfaces
 */
public class ClassOrInterface extends SyntaxTreeNode implements TypeScope{
  protected int modifiers;
  protected JavaClassName name;
  private List<Field> fields = new ArrayList<>();
  private Optional<Constructor> fieldInitializations; //PL 2018-11-24: Noetig, um Bytecode fuer initializators nur einmal zu erzeugen
  private List<Method> methods = new ArrayList<>();
  private GenericDeclarationList genericClassParameters;
  private RefType superClass;
  protected boolean isInterface;
  private List<RefType> implementedInterfaces;
  private List<Constructor> constructors;

  public ClassOrInterface(int modifiers, JavaClassName name, List<Field> fielddecl, Optional<Constructor> fieldInitializations, List<Method> methods, List<Constructor> constructors, GenericDeclarationList genericClassParameters,
                          RefType superClass, Boolean isInterface, List<RefType> implementedInterfaces, Token offset){
    super(offset);
    if(isInterface && !Modifier.isInterface(modifiers))modifiers += Modifier.INTERFACE;
      this.modifiers = modifiers;
      this.name = name;
      this.fields = fielddecl;
      this.fieldInitializations= fieldInitializations;
    this.genericClassParameters = genericClassParameters;
      this.superClass = superClass;
    this.isInterface = isInterface;
      this.implementedInterfaces = implementedInterfaces;
    this.methods = methods;
    this.constructors = constructors;
  }
  
  // Gets class name
  public JavaClassName getClassName(){
    return this.name;
  }
  
  // Get modifiers
  public int getModifiers(){
    return this.modifiers;
  }

  public List<Field> getFieldDecl(){
    return this.fields;
  }
  
  public Optional<Constructor> getfieldInitializations(){
	    return this.fieldInitializations;
	  }
  
  public List<Method> getMethods(){
    return this.methods;
  }

  /*
  public RefType getType() {
    return generateTypeOfClass(this.getClassName(), this.getGenerics(), this.getOffset());
  }
  */
  //TODO: Das hier ist ein Problem. Je nach Kontext wird hier ein anderer Typ benötigt
  public static RefType generateTypeOfClass(JavaClassName name, GenericDeclarationList genericsOfClass ,Token offset){
    //Hier wird immer ein generischer Typ generiert, also mit Type placeholdern
    List<RefTypeOrTPHOrWildcardOrGeneric> params = new ArrayList<>();
    for(GenericTypeVar genericTypeVar : genericsOfClass){
      //params.add(genericTypeVar.getTypePlaceholder());
      params.add(TypePlaceholder.fresh(offset));
    }
    return new RefType(name, params, offset);
  }

  /**
   * Die Superklasse im Kontext dieser ClassOrInterface
   * Das bedeutet, dass generische Variablen als GenericRefTypes dargestellt sind
     */
  public RefType getSuperClass() {
    return superClass;
  }

  public GenericDeclarationList getGenerics() {
    return this.genericClassParameters;
  }

  @Override
  public RefTypeOrTPHOrWildcardOrGeneric getReturnType() {
    return null;
  }

  public List<Constructor> getConstructors() {
    return constructors;
  }

  @Override
  public void accept(ASTVisitor visitor) {
    visitor.visit(this);
  }

  public Collection<RefType> getSuperInterfaces() {
    return implementedInterfaces;
  }
  
  public String toString() {
	  return this.name.toString() + this.genericClassParameters.toString();
  }
}
