package de.dhbwstuttgart.parser.SyntaxTreeGenerator;

import de.dhbwstuttgart.environment.PackageCrawler;
import de.dhbwstuttgart.exceptions.NotImplementedException;
import java.lang.ClassNotFoundException;

import de.dhbwstuttgart.exceptions.TypeinferenceException;
import de.dhbwstuttgart.parser.NullToken;
import de.dhbwstuttgart.parser.antlr.Java8Parser;
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.*;
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 java.lang.reflect.Modifier;
import java.sql.Ref;
import java.util.*;
import java.util.stream.Collectors;

//import jdk.internal.dynalink.support.TypeConverterFactory;
import org.antlr.v4.runtime.CommonToken;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.TerminalNode;

public class SyntaxTreeGenerator{
  private JavaClassRegistry reg;
  private final GenericsRegistry globalGenerics;
  private String pkgName = "";
  Set<JavaClassName> imports = new HashSet();
  private Map<String, RefTypeOrTPHOrWildcardOrGeneric> fields = new HashMap<>(); //PL 2018-11-01 fields eingefuegt, damit die fields immer die gleiche TPH bekommen

  List<Statement> fieldInitializations = new ArrayList<>();

  public SyntaxTreeGenerator(JavaClassRegistry reg, GenericsRegistry globalGenerics){
    //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;
  }
  

  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 SourceFile convert(Java8Parser.CompilationUnitContext ctx, PackageCrawler packageCrawler) throws ClassNotFoundException{
    if(ctx.packageDeclaration()!=null)this.pkgName = convert(ctx.packageDeclaration());
    List<ClassOrInterface> classes = new ArrayList<>();
    Map<String, Integer> imports = GatherNames.getImports(ctx, packageCrawler);
    this.imports = imports.keySet().stream().map(name -> reg.getName(name)).collect(Collectors.toSet());
    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 String convert(Java8Parser.PackageDeclarationContext packageDeclarationContext) {
    String ret = "";
    for(TerminalNode identifier : packageDeclarationContext.Identifier()){
      ret += identifier.getText()+".";
    }
    ret = ret.substring(0, ret.length()-1);
    return ret;
  }

  public Method convert(Java8Parser.MethodDeclarationContext methodDeclarationContext, JavaClassName parentClass, RefType superClass, GenericsRegistry generics) {
    Java8Parser.MethodHeaderContext header = methodDeclarationContext.methodHeader();
    int modifiers = SyntaxTreeGenerator.convert(methodDeclarationContext.methodModifier());
    GenericsRegistry localGenerics = createGenerics(methodDeclarationContext.methodHeader().typeParameters(),
            parentClass, header.methodDeclarator().Identifier().getText(), reg, generics);
    localGenerics.putAll(generics);
    return convert(modifiers, header, methodDeclarationContext.methodBody(),parentClass, superClass, localGenerics);
  }

  public Method convert(Java8Parser.InterfaceMethodDeclarationContext ctx, JavaClassName parentClass, RefType superClass, GenericsRegistry generics) {
    Java8Parser.MethodHeaderContext header = ctx.methodHeader();
    int modifiers = SyntaxTreeGenerator.convertInterfaceModifier(ctx.interfaceMethodModifier());

    GenericsRegistry localGenerics = createGenerics(header.typeParameters(), parentClass, header.methodDeclarator().Identifier().getText(), reg, generics);
    localGenerics.putAll(generics);

    return convert(modifiers, header, ctx.methodBody(),parentClass, superClass, localGenerics);
  }

  private Method convert(int modifiers, Java8Parser.MethodHeaderContext header, Java8Parser.MethodBodyContext body,
                         JavaClassName parentClass, RefType superClass, GenericsRegistry localGenerics) {

    StatementGenerator stmtGen = new StatementGenerator(reg, localGenerics, fields, new HashMap<>());

    String name = header.methodDeclarator().Identifier().getText();
    GenericDeclarationList gtvDeclarations = new GenericDeclarationList(new ArrayList<>(), header.getStart());
    if(header.typeParameters() != null){
      gtvDeclarations = TypeGenerator.convert(header.typeParameters(), parentClass, name, reg, localGenerics);
    }else{
      gtvDeclarations = new GenericDeclarationList(new ArrayList<>(), header.getStart());
    }
    RefTypeOrTPHOrWildcardOrGeneric retType;
    if(header.result() != null){
      if(header.result().unannType() != null){
        retType = TypeGenerator.convert(header.result().unannType(), reg, localGenerics);
      }
      else retType = new de.dhbwstuttgart.syntaxtree.type.Void(header.result().getStart());
    }else{
      retType = TypePlaceholder.fresh(header.getStart());
    }
    ParameterList parameterList = stmtGen.convert(header.methodDeclarator().formalParameterList());
    Block block = null;
    if(body.block() == null){
      if(! Modifier.isAbstract(modifiers)){
        //TODO: Error! Abstrakte Methode ohne abstrakt Keyword
      }
    }else{
      block = stmtGen.convert(body.block(),true);
    }
    if(parentClass.equals(new JavaClassName(name))){
      return new Constructor(modifiers, name, retType, parameterList, block, gtvDeclarations, header.getStart() /*, fieldInitializations geloescht PL 2018-11-24 */);
    }else{
      return new Method(modifiers, name, retType, parameterList,block, gtvDeclarations, header.getStart());
    }
  }

  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;
      }
    }
    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.typeParameters(), name, "", reg, new GenericsRegistry(globalGenerics));
    Token offset = ctx.getStart();
    GenericDeclarationList genericClassParameters;
    if(ctx.typeParameters() == null){
      genericClassParameters = createEmptyGenericDeclarationList(ctx.Identifier());
    }else{
      genericClassParameters = TypeGenerator.convert(ctx.typeParameters(), name, "",reg, generics);
    }
    RefType superClass ;
    if(ctx.superclass() != null){
      superClass = convert(ctx.superclass());
    }else{
      superClass = new RefType(ASTFactory.createObjectClass().getClassName(), ctx.getStart());
    }
    List<Field> fielddecl = convertFields(ctx.classBody(), generics);
    //fieldInitializations = generateFieldInitializations(ctx.classBody(), generics);
    List<Method> methodsAndConstructors = convertMethods(ctx.classBody(), name, superClass, generics);
    List<Method> methods = new ArrayList<>();
    List<Constructor> konstruktoren = new ArrayList<>();
    //int noOfMethods = methods.size();
    for(int i = 0; i < methodsAndConstructors.size(); i++){
      Method m = methodsAndConstructors.get(i);
      if(m instanceof Constructor){
        konstruktoren.add((Constructor) m);
      }
      else {
    	  methods.add(m);
      }
    }
    if(konstruktoren.size()<1){//Standardkonstruktor anfügen:
      konstruktoren.add(
              generateStandardConstructor(
                      ctx.Identifier().getText(), name, superClass,
                      genericClassParameters, offset)
          );
    }

    Boolean isInterface = false;
    List<RefType> implementedInterfaces = convert(ctx.superinterfaces(), generics);
    
    return new ClassOrInterface(modifiers, name, fielddecl, 
    		Optional.of(this.generatePseudoConstructor(ctx.Identifier().getText(), name, superClass, genericClassParameters, offset)), 
    		methods, konstruktoren, genericClassParameters, superClass,
            isInterface, implementedInterfaces, offset);
  }

  /*
  private List<Statement> generateFieldInitializations(Java8Parser.ClassBodyContext classBodyContext, GenericsRegistry generics) {
    List<Statement> ret = new ArrayList<>();
    for(Java8Parser.ClassBodyDeclarationContext classMember : classBodyContext.classBodyDeclaration()){
      if(classMember.classMemberDeclaration() != null){
        Java8Parser.ClassMemberDeclarationContext classMemberDeclarationContext = classMember.classMemberDeclaration();
        if(classMemberDeclarationContext.fieldDeclaration() != null
                && classMemberDeclarationContext.fieldDeclaration().variableDeclaratorList() != null){
          for(Java8Parser.VariableDeclaratorContext ctx : classMemberDeclarationContext.fieldDeclaration().variableDeclaratorList().variableDeclarator()) {
            String fieldName = ctx.variableDeclaratorId().Identifier().getText();
            if(ctx.variableDeclaratorId().dims() != null)throw new NotImplementedException();
            Token offset = ctx.getStart();
            RefTypeOrTPHOrWildcardOrGeneric fieldType;
            for(Field f : fields)
            AssignToField leftSide = new AssignToField(new FieldVar(new This(offset), fieldName, ));
            ret.addAll();
          }
        }else if(classMemberDeclarationContext.methodDeclaration()!= null){
          //Do nothing!
        }
      }
    }
    return ret;
  }
*/

  private List<RefType> convert(Java8Parser.SuperinterfacesContext ctx, GenericsRegistry generics) {
    if(ctx == null)return new ArrayList<>();
    return convert(ctx.interfaceTypeList(), generics);
  }

  private List<RefType> convert(Java8Parser.InterfaceTypeListContext ctx, GenericsRegistry generics) {
    List<RefType> ret = new ArrayList<>();
    for(Java8Parser.InterfaceTypeContext interfaceType : ctx.interfaceType()){
      ret.add((RefType) TypeGenerator.convert(interfaceType.classType(), reg, generics));
    }
    return ret;
  }

  /**
   * 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);
    Block block = new Block(new ArrayList<>(), offset);
    return new Constructor(Modifier.PUBLIC, className, classType, params, block, classGenerics, offset /*, fieldInitializations geloescht PL 2018-11-24 */);
  }
  
  /* fieldInitializations werden in einem Psedokonstruktor in der abstrakten Syntax gespeichert */
  private Constructor generatePseudoConstructor(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); 
	    Block block = new Block(new ArrayList<>(fieldInitializations), offset);
	    return new Constructor(Modifier.PUBLIC, className, classType, params, block, classGenerics, offset /*, fieldInitializations geloescht PL 2018-11-24 */);
	  }

  private RefType convert(Java8Parser.SuperclassContext superclass) {
    if(superclass.classType().classOrInterfaceType() != null){
      throw new NotImplementedException();
    }else{
      RefTypeOrTPHOrWildcardOrGeneric ret = TypeGenerator.convertTypeName(superclass.classType().Identifier().getText(), superclass.classType().typeArguments(),
              superclass.getStart(), reg, globalGenerics);
      if(ret instanceof RefType){
        return (RefType) ret;
      }else{
        throw new TypeinferenceException(superclass.getText() + " ist kein gültiger Supertyp", superclass.getStart());
      }
    }
  }

  private List<Method> convertMethods(Java8Parser.ClassBodyContext classBodyContext,
                                      JavaClassName parentClass, RefType superClass, GenericsRegistry generics) {
    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){

          ret.add(this.convert(classMemberDeclarationContext.methodDeclaration(), parentClass, superClass, generics));
        }
      }
    }
    return ret;
  }

  private List<Field> convertFields(Java8Parser.ClassBodyContext classBodyContext, GenericsRegistry generics) {
    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(), generics));
        }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;
  }

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

  private List<? extends Field> convert(Java8Parser.FieldDeclarationContext fieldDeclarationContext, GenericsRegistry generics) {
    List<Field> ret = new ArrayList<>();
    int modifiers = 0;
    for(Java8Parser.FieldModifierContext fieldModifierContext : fieldDeclarationContext.fieldModifier()){
      modifiers+=(convert(fieldModifierContext));
    }
    RefTypeOrTPHOrWildcardOrGeneric fieldType;
    if(fieldDeclarationContext.unannTypeOrAuto() != null
            && fieldDeclarationContext.unannTypeOrAuto().unannType() != null){
      fieldType = TypeGenerator.convert(fieldDeclarationContext.unannTypeOrAuto().unannType(), reg, generics);
    }else{
      fieldType = TypePlaceholder.fresh(fieldDeclarationContext.getStart());
    }
    for(Java8Parser.VariableDeclaratorContext varCtx : fieldDeclarationContext.variableDeclaratorList().variableDeclarator()){
      String fieldName = convert(varCtx.variableDeclaratorId());
      fields.put(fieldName, fieldType);
      if(varCtx.variableInitializer() != null){
        initializeField(varCtx, fieldType, generics);
      }
      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.VariableDeclaratorContext ctx, RefTypeOrTPHOrWildcardOrGeneric typeOfField, GenericsRegistry generics){
    StatementGenerator statementGenerator = new StatementGenerator(reg, generics, fields, new HashMap<>());
    fieldInitializations.add(statementGenerator.generateFieldAssignment(ctx, typeOfField));
  }

  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.contains(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 int convert(Java8Parser.InterfaceModifierContext 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){
    if(ctx.normalInterfaceDeclaration() != null){
      return convertNormal(ctx.normalInterfaceDeclaration());
    }else{
      throw new NotImplementedException();
    }
  }

  private ClassOrInterface convertNormal(Java8Parser.NormalInterfaceDeclarationContext ctx) {
    int modifiers = 0;
    if(ctx.interfaceModifier() != null){
      for( Java8Parser.InterfaceModifierContext mod : ctx.interfaceModifier()){
        int newModifier = convert(mod);
        modifiers += newModifier;
      }
    }
    if(!Modifier.isInterface(modifiers))modifiers += Modifier.INTERFACE;
    
    JavaClassName name = reg.getName(ctx.Identifier().getText());

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

    GenericDeclarationList genericParams;
    if(ctx.typeParameters() != null){
      genericParams = TypeGenerator.convert(ctx.typeParameters(), name, "",reg, generics);
    }else{
      genericParams = createEmptyGenericDeclarationList(ctx.Identifier());
    }
    RefType superClass = ASTFactory.createObjectType();

    List<Field> fields = convertFields(ctx.interfaceBody());
    List<Method> methods = convertMethods(ctx.interfaceBody(), name, superClass, generics);

    List<RefType> extendedInterfaces = convert(ctx.extendsInterfaces(), generics);

    return new ClassOrInterface(modifiers, name, fields, Optional.empty(), methods, new ArrayList<>(),
            genericParams, superClass, true, extendedInterfaces, ctx.getStart());
  }

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

  private GenericsRegistry createGenerics(Java8Parser.TypeParametersContext ctx, JavaClassName parentClass, String parentMethod, JavaClassRegistry reg, GenericsRegistry generics) {
    GenericsRegistry ret = new GenericsRegistry(this.globalGenerics);
    ret.putAll(generics);
    if(ctx == null || ctx.typeParameterList() == null)return ret;
    for(Java8Parser.TypeParameterContext tp : ctx.typeParameterList().typeParameter()){
      ret.put(tp.Identifier().getText(), new GenericContext(parentClass, parentMethod));
    }
    for(Java8Parser.TypeParameterContext tp : ctx.typeParameterList().typeParameter()){
      TypeGenerator.convert(tp, parentClass, parentMethod, reg, ret);
    }
    return ret;
  }

  private List<RefType> convert(Java8Parser.ExtendsInterfacesContext extendsInterfacesContext, GenericsRegistry generics) {
    if(extendsInterfacesContext == null)return new ArrayList<>();
    return convert(extendsInterfacesContext.interfaceTypeList(), generics);
  }

  private List<Method> convertMethods(Java8Parser.InterfaceBodyContext interfaceBodyContext,
                                      JavaClassName parentClass, RefType superClass, GenericsRegistry generics) {
    List<Method> ret = new ArrayList<>();
    for(Java8Parser.InterfaceMemberDeclarationContext member : interfaceBodyContext.interfaceMemberDeclaration()){
      if(member.interfaceMethodDeclaration() != null){
        ret.add(this.convert(member.interfaceMethodDeclaration(), parentClass, superClass, generics));
        //new Method(name, type, modifier, params, null, genericDecls, member.interfaceMethodDeclaration().getStart());
      }else{
        throw new NotImplementedException();
      }
    }
    return ret;
  }

  private List<Field> convertFields(Java8Parser.InterfaceBodyContext interfaceBodyContext) {
    List<Field> ret = new ArrayList<>();
    for(Java8Parser.InterfaceMemberDeclarationContext member : interfaceBodyContext.interfaceMemberDeclaration()){
      if(member.constantDeclaration() != null){
        //TODO: Erstelle hier ein Feld!
        throw new NotImplementedException();
      }
    }
    return ret;
  }


}
