/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package JavaAnalyser;

import com.github.javaparser.*;
import com.github.javaparser.ast.*;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import com.github.javaparser.resolution.declarations.*;
import com.github.javaparser.resolution.types.ResolvedReferenceType;
import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserFieldDeclaration;
import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import com.github.javaparser.symbolsolver.utils.SymbolSolverCollectionStrategy;
import com.github.javaparser.utils.ProjectRoot;
import com.github.javaparser.utils.SourceRoot;
import com.github.javaparser.resolution.MethodUsage;

import javax.swing.text.html.Option;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

import static java.lang.System.exit;

public class App {

    public static void main(String[] args) {
        //System.out.println("---");

        //System.out.println(System.getProperty("java.version"));

        if(args.length < 2 ) {//|| args.length > 3) {
            System.out.println("Wrong number of arguments: " + args.length + ". Should be 2 or 3.");
        }

        String path = args[0];
        String projectFolderPath = args[1];
        String jdkPath = "/Library/Java/JavaVirtualMachines/openjdk-11.0.2.jdk/";

        if(args.length == 3) {
            jdkPath = args[2];
        }


        /*
        CombinedTypeSolver combinedSolver = new CombinedTypeSolver(
                new JavaParserTypeSolver(jdkPath),
                new JavaParserTypeSolver(new File("/Users/kristiina/.gradle/caches/modules-2/files-2.1/")),
                new JavaParserTypeSolver(new File(projectFolderPath)),
                new ReflectionTypeSolver()

        );

         */

        //ParserConfiguration parserConfiguration = new ParserConfiguration().setSymbolResolver(new JavaSymbolSolver(combinedSolver));

        //SymbolSolverCollectionStrategy symbolSolverCollectionStrategy = new SymbolSolverCollectionStrategy(parserConfiguration);
        SymbolSolverCollectionStrategy symbolSolverCollectionStrategy = new SymbolSolverCollectionStrategy();

        ProjectRoot projectRoot = symbolSolverCollectionStrategy.collect(Paths.get(projectFolderPath));

        String[] split = path.split("/src/main/java/");
        String last = split[split.length - 1];

        ArrayList<Map<String,Object>> objects = new ArrayList<Map<String,Object>>();

        for( SourceRoot sourceRoot : projectRoot.getSourceRoots()) {
            try {

                /*
                sourceRoot.tryToParse();
                List<CompilationUnit> cus = sourceRoot.getCompilationUnits();
                System.out.println("number of cus: " + cus.size());

                for( CompilationUnit cu: cus) {
                    Map<String,Object> object = new HashMap<>();
                    String packageDeclaration;
                    if(cu.getPackageDeclaration().isPresent()) {
                        packageDeclaration = cu.getPackageDeclaration().get().getNameAsString();
                    } else {
                        packageDeclaration = "?";
                    }
                    object.put("'key.package'", "'" + packageDeclaration + "'");
                    object.put("'key.entities'", handleCompilationUnit(cu));


                   // objects.add(object);
                }

                 */

                sourceRoot.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_11);

                CompilationUnit cu = sourceRoot.parse("", last);

                Map<String,Object> object = new HashMap<>();
                String packageDeclaration;
                if(cu.getPackageDeclaration().isPresent()) {
                    packageDeclaration = cu.getPackageDeclaration().get().getNameAsString();
                } else {
                    packageDeclaration = "?";
                }
                object.put("'key.package'", "'" + packageDeclaration + "'");
                object.put("'key.entities'", handleCompilationUnit(cu));


                objects.add(object);
                break;

            } catch (ParseProblemException e) {
               // System.out.println("not found path for sourceRoot: " + sourceRoot);
            } //catch (IOException e) {
              //  e.printStackTrace();
           // }
        }
        System.out.println(objects);
    }

    private static void printNode(Node node, String prefix) {
        String addInfo = "";
        if(node.getClass() == Name.class) {
            addInfo = " -- " + node.toString();
        }

        if(node.getClass() == SimpleName.class) {
            addInfo = " -- " + node.toString();
        }

        System.out.println(prefix + "Node class: " + node.getClass() + addInfo);

        for(Node entity: node.getChildNodes()) {
            printNode(entity, "   " + prefix);
        }
    }

    private static Map<String,Object> handleInstruction(Node node) {
        Map<String,Object> object = new HashMap<>();
        String name = null;
        String usr = null;
        String typeString = null;

        if(node.getClass() == ClassOrInterfaceType.class) {
            ClassOrInterfaceType classOrInterfaceType = (ClassOrInterfaceType)node;
            name = classOrInterfaceType.getNameAsString();
            try {
                usr = classOrInterfaceType.resolve().getQualifiedName();
            } catch (UnsolvedSymbolException exception) {
                usr = "??";
            } catch (UnsupportedOperationException exception) {
                usr  = "??";
            }
        } else if (node.getClass() == MethodCallExpr.class) {
            MethodCallExpr methodCallExpr = (MethodCallExpr) node;
            name = methodCallExpr.getNameAsString();
            try {
                usr = methodCallExpr.resolve().getQualifiedSignature();
            } catch (UnsolvedSymbolException exception) {
                //usr = "??";
                usr = methodCallExpr.getNameAsString();
            } catch (RuntimeException exception) {
                // sometimes if type of a parameter cannot be resolved a RuntimeException is thrown
                usr = methodCallExpr.getNameAsString();
            }

        } else if (node.getClass() == MethodReferenceExpr.class) {
            MethodReferenceExpr methodReferenceExpr = (MethodReferenceExpr)node;

            try {
                name = methodReferenceExpr.resolve().getName();
                usr = methodReferenceExpr.resolve().getQualifiedSignature();
            } catch (UnsolvedSymbolException exception) {
                usr = methodReferenceExpr.toString();
                name = methodReferenceExpr.toString();
            } catch (UnsupportedOperationException expection) {
                usr = methodReferenceExpr.toString();
                name = methodReferenceExpr.toString();
            } catch (RuntimeException exception) {
                // sometimes unsolved type parameters cause a RuntimeException
                usr = methodReferenceExpr.toString();
                name = methodReferenceExpr.toString();
            }
        } else if (node.getClass() == FieldAccessExpr.class) {
            try {
                FieldAccessExpr fieldAccessExpr = (FieldAccessExpr) node;
                name = fieldAccessExpr.getNameAsString();

                /*
                ResolvedValueDeclaration valueDeclaration = fieldAccessExpr.resolve();
                if(valueDeclaration.getClass() == ResolvedFieldDeclaration.class) {
                    ResolvedFieldDeclaration resolvedFieldDeclaration = (ResolvedFieldDeclaration)valueDeclaration;
                    usr = resolvedFieldDeclaration.declaringType().getQualifiedName() + "." + name;
                }

                 */
                Expression fieldScope = fieldAccessExpr.getScope();
                String scope;
                try {
                    scope = fieldScope.calculateResolvedType().asReferenceType().getQualifiedName();
                } catch (UnsolvedSymbolException e) {
                    scope = "?";
                } catch (UnsupportedOperationException e) {
                    scope = "?";
                } catch (RuntimeException e) {
                    scope = "?";
                }

                String fieldName = fieldAccessExpr.getNameAsString();
                String fullName = scope + "." + fieldName;
                usr = fullName;

                ResolvedType resolvedType = fieldAccessExpr.resolve().getType();
                if (resolvedType.isPrimitive()) {
                    typeString = resolvedType.asPrimitive().name();
                } else if (resolvedType.isReferenceType()) {
                    ResolvedReferenceType refType = resolvedType.asReferenceType();
                    typeString = refType.getQualifiedName();
                }

            } catch (UnsolvedSymbolException exception) {
                usr = "??";
            } catch (UnsupportedOperationException exception) {
                typeString = "???";
            }
        } else if(node.getClass() == NameExpr.class) {
            try {
                NameExpr nameExpr = (NameExpr)node;
                ResolvedValueDeclaration valueDeclaration = nameExpr.resolve();

                name = nameExpr.getNameAsString();

                if(valueDeclaration.getClass() == JavaParserFieldDeclaration.class) {
                    JavaParserFieldDeclaration resolvedFieldDeclaration = (JavaParserFieldDeclaration)valueDeclaration;
                    usr = resolvedFieldDeclaration.declaringType().getQualifiedName() + "." + name;
                }

                //usr = valueDeclaration.getClass().toString();

            } catch (UnsolvedSymbolException exception) {
                // no usr
              //  usr = "!!!!";
            } catch (UnsupportedOperationException exception) {
                // no usr
            }
        }

        setStartEnd(node, object);
        object.put("'key.kind'", "'" + node.getClass().toString() + "'");

        if(usr != null) {
            object.put("'key.usr'", "'" + usr + "'");
        }

        if(name != null) {
            object.put("'key.name'", "'" + name + "'");
        }

        if(typeString != null) {
            object.put("'key.type'", "'" + typeString + "'");
        }

        object.put("'key.kind'", "'" + node.getClass().toString() + "'");

        List<Map<String,Object>> entities = new ArrayList<>();

        for(Node child: node.getChildNodes()) {
            entities.add(handleInstruction(child));
        }
        object.put("'key.entities'", entities);

        return object;
    }

    private static Map<String,Object> handleParameter(Parameter node) {
        Map<String,Object> object = new HashMap<>();

        setStartEnd(node, object);

        try {
            Type type = node.getType();
            ResolvedType resolvedType = type.resolve();

            if (resolvedType.isPrimitive()) {
                object.put("'key.type'", "'" + resolvedType.asPrimitive().name().replace('\'', '-') + "'");
            } else if (resolvedType.isReferenceType()) {
                ResolvedReferenceType refType = resolvedType.asReferenceType();
                refType.getQualifiedName();

                object.put("'key.typeUsr'", "'" + refType.getQualifiedName() + "'");
                object.put("'key.type'", "'" + refType.getQualifiedName() + "'");
            } else {
                object.put("'key.type'", "'" + resolvedType.toString().replace('\'', '-') +"'");
            }
        } catch (UnsolvedSymbolException ex) {
            object.put("'key.type'", "'??unresolved'");
        } catch (UnsupportedOperationException ex) {
            object.put("'key.type'", "'??unsupported'");
        }

        object.put("'key.name'", "'" + node.getNameAsString() + "'");

        List<Map<String,Object>> entities = new ArrayList<>();
        for(Node child: node.getChildNodes()) {
            entities.add(handleInstruction(child));
        }
        object.put("'key.entities'", entities);

        return object;
    }

    private static void setStartEnd(Node node, Map<String, Object> object) {
        int start = -1;
        int end = -1;

        Optional<Position> optionalPositionEnd = node.getEnd();
        Optional<Position> optionalPositionBegin = node.getBegin();

        if(optionalPositionBegin.isPresent()) {
            start = optionalPositionBegin.get().line;
        }

        if(optionalPositionEnd.isPresent()) {
            end = optionalPositionEnd.get().line;
        }

        object.put("'key.startLine'", start);
        object.put("'key.endLine'", end);
    }

    private static Map<String, Object> handleConstructor(ConstructorDeclaration constructor) {
        Map<String,Object> object = new HashMap<>();

        setStartEnd(constructor, object);
        object.put("'key.name'", "'" + constructor.getNameAsString() + "'");

        List<Map<String,Object>> annotations = new ArrayList<>();
        for(AnnotationExpr annotation: constructor.getAnnotations()) {
            annotations.add(handleAnnotation(annotation));
        }

        List<Map<String,Object>> modifiers = new ArrayList<>();
        for(Modifier modifier: constructor.getModifiers()) {
            modifiers.add(handleModifier(modifier));
        }

        object.put("'key.modifiers'", modifiers);
        object.put("'key.annotations'", annotations);

        int count = 1;
        List<Map<String,Object>> parameters = new ArrayList<>();
        for(Parameter parameter: constructor.getParameters()) {
            Map<String,Object> handledParameter = handleParameter(parameter);
            handledParameter.put("'key.position'", count);
            parameters.add(handledParameter);
            count++;
        }
        object.put("'key.parameters'", parameters); //TODO: parameters are added, why are they missing? missing from swift??

        //String usr = constructor.getDeclarationAsString();

        String usr;
        try {
            ResolvedConstructorDeclaration resolvedConstructor = constructor.resolve();
            usr = resolvedConstructor.getQualifiedSignature(); //TODO: why does this not always work?
        } catch (UnsolvedSymbolException exception) {
            //usr = resolvedMethodDeclaration.getSignature(); //TODO: add class string?
            usr = constructor.getName().toString();
        }

        object.put("'key.usr'", "'"+usr+"'");

        object.put("'key.usr'", "'"+usr+"'");
        object.put("'key.kind'", "'ConstructorDeclaration'");

        ArrayList<Map<String,Object>> entities = new ArrayList<>();

        entities.add(handleInstruction(constructor.getBody()));
        object.put("'key.entities'", entities);

        return object;
    }

    private static Map<String,Object> handleMethod(MethodDeclaration method) {
        Map<String,Object> object = new HashMap<>();

        /*
        method:
            - name
            - usr
            - /annotations
            - /modifiers
            - ?? overridden methods
            - /return type
            - arguments
            - instructions
         */

        setStartEnd(method, object);

        object.put("'key.name'", "'" + method.getNameAsString() + "'");

        List<Map<String,Object>> annotations = new ArrayList<>();
        for(AnnotationExpr annotation: method.getAnnotations()) {
            annotations.add(handleAnnotation(annotation));
        }

        List<Map<String,Object>> modifiers = new ArrayList<>();
        for(Modifier modifier: method.getModifiers()) {
            modifiers.add(handleModifier(modifier));
        }

        object.put("'key.modifiers'", modifiers);
        object.put("'key.annotations'", annotations);

        method.getDeclarationAsString();
        method.getBody(); // handle instructions

        int count = 1;
        List<Map<String,Object>> parameters = new ArrayList<>();
        for(Parameter parameter: method.getParameters()) {
            Map<String,Object> handledParameter = handleParameter(parameter);
            handledParameter.put("'key.position'", count);
            parameters.add(handledParameter);
            count++;
        }
        object.put("'key.parameters'", parameters); //TODO: parameters are added, why are they missing? missing from swift??

        MethodDeclaration methodDeclaration = (MethodDeclaration)method;
        ResolvedMethodDeclaration resolvedMethodDeclaration = methodDeclaration.resolve();

        String type = "?";

        try {
            ResolvedType resolvedReturnType = resolvedMethodDeclaration.getReturnType();
            String returnType = null;

            if (resolvedReturnType.isPrimitive()) {
                returnType = resolvedReturnType.asPrimitive().name();
            } else if (resolvedReturnType.isReferenceType()) {
                returnType = resolvedReturnType.asReferenceType().getQualifiedName();
            } else {
                returnType = "not defined!";
            }
            type = returnType;
        } catch (UnsolvedSymbolException e) {
            //
            type = "??notdefined";
        }

        //String declaringType = resolvedMethodDeclaration.declaringType().getQualifiedName();

        String usr;

        try {
            usr = resolvedMethodDeclaration.getQualifiedSignature(); //TODO: why does this not always work?
        } catch (UnsolvedSymbolException exception) {
            //usr = resolvedMethodDeclaration.getSignature(); //TODO: add class string?
            usr = resolvedMethodDeclaration.getName();
        }

        object.put("'key.usr'", "'"+usr+"'");
        object.put("'key.type'", "'"+type+"'");

        if(method.isStatic()) {
            object.put("'key.kind'", "'StaticMethodDeclaration'");
        } else {
            object.put("'key.kind'", "'InstanceMethodDeclaration'");
        }

        //TODO: add something else

        ArrayList<Map<String,Object>> entities = new ArrayList<>();

        if(method.getBody().isPresent()) {
            entities.add(handleInstruction(method.getBody().get()));
        }
        object.put("'key.entities'", entities);

        return object;
    }

    private static List<Map<String,Object>> handleVariable(FieldDeclaration field, String classUsr) {
        List<Map<String,Object>> objects = new ArrayList<>();
        NodeList<VariableDeclarator> variables =  field.getVariables();

        List<Map<String,Object>> annotations = new ArrayList<>();
        for(AnnotationExpr annotation: field.getAnnotations()) {
            annotations.add(handleAnnotation(annotation));
        }

        List<Map<String,Object>> modifiers = new ArrayList<>();
        for(Modifier modifier: field.getModifiers()) {
            modifiers.add(handleModifier(modifier));
        }

        for(VariableDeclarator variable: variables) {
            Map<String,Object> object = new HashMap<>();

            try {
                ResolvedValueDeclaration resolvedValueDeclaration = variable.resolve();

                ResolvedType type = resolvedValueDeclaration.getType();
                resolvedValueDeclaration.getName();

                if (type.isPrimitive()) {
                    object.put("'key.type'", "'" + type.asPrimitive().name() + "'");
                } else if (type.isReferenceType()) {
                    ResolvedReferenceType refType = type.asReferenceType();
                    refType.getQualifiedName();

                    object.put("'key.typeUsr'", "'" + refType.getQualifiedName() + "'");
                    object.put("'key.type'", "'" + refType.getQualifiedName() + "'");
                }
            } catch (UnsolvedSymbolException ex) {
                object.put("'key.type'", "'??unresolved'");
            }


            object.put("'key.name'", "'" + variable.getNameAsString() + "'");
            object.put("'key.usr'", "'" + classUsr + "." + variable.getNameAsString() + "'");

            setStartEnd(field, object);

            object.put("'key.modifiers'", modifiers);
            object.put("'key.annotations'", annotations);

            if(field.isStatic()) {
                object.put("'key.kind'", "'StaticVariableDeclaration'");
            } else {
                object.put("'key.kind'", "'InstanceVariableDeclaration'");
            }

            objects.add(object);
        }

        return objects;
    }

    private static Map<String,Object> handleAnnotation(AnnotationExpr node) {
        Map<String,Object> object = new HashMap<>();

        setStartEnd(node, object);

        String name = node.getNameAsString();
        object.put("'key.name'", "'" + name + "'");
        object.put("'key.kind'", "'Annotation'");

        return object;
    }

    private static Map<String,Object> handleModifier(Modifier node) {
        Map<String,Object> object = new HashMap<>();

        setStartEnd(node, object);

        String name = node.getKeyword().name();
        object.put("'key.name'", "'" + name + "'");
        object.put("'key.kind'", "'Modifier'");

        return object;
    }

    private static Map<String,Object> handeParent(ClassOrInterfaceType type) {
        Map<String,Object> object = new HashMap<>();

        setStartEnd(type, object);

        String qualifiedName;
        try {
            ResolvedReferenceType resolvedType = type.resolve();
            qualifiedName = resolvedType.getQualifiedName();
        } catch (UnsolvedSymbolException exception) {
            qualifiedName = type.getNameWithScope();
        }

        String name = type.getNameAsString();

        object.put("'key.name'", "'" + name + "'");
        object.put("'key.usr'", "'" + qualifiedName + "'");
        object.put("'key.kind'", "'ClassReference'"); //TODO: add distinction if it is a class or interface?

        return object;
    }

    private static List<Map<String,Object>>  handleClass(ClassOrInterfaceDeclaration node) {
        List<Map<String,Object>> objects = new ArrayList<>();
        Map<String,Object> object = new HashMap<>();

        setStartEnd(node, object);

        object.put("'key.name'", "'" + node.getNameAsString() + "'");
        if(node.isInterface()) {
            object.put("'key.kind'",  "'InterfaceDeclaration'");
        } else {
            object.put("'key.kind'",  "'ClassDeclaration'");
        }

        if(node.getFullyQualifiedName().isPresent()) {
            object.put("'key.usr'", "'" + node.getFullyQualifiedName().get() + "'");
        }
        object.put("'key.name'", "'" + node.getNameAsString() + "'");

        List<Map<String,Object>> subEntities = new ArrayList<>();


        for(BodyDeclaration member: node.getMembers()) {
            if( member instanceof ConstructorDeclaration) {
                subEntities.add(handleConstructor((ConstructorDeclaration) member));
            }
        }

        for(MethodDeclaration method: node.getMethods()) {
            subEntities.add(handleMethod(method));
        }

        for(FieldDeclaration fieldDeclaration: node.getFields()) {
            subEntities.addAll(handleVariable(fieldDeclaration, node.getFullyQualifiedName().get()));
        }

        try {
            ResolvedReferenceTypeDeclaration resolvedClass = node.resolve();
            for(MethodUsage methodUsage: resolvedClass.getAllMethods()) {
                ResolvedMethodDeclaration method = methodUsage.getDeclaration();

                if(method.accessSpecifier() != AccessSpecifier.PRIVATE && !method.declaringType().equals(resolvedClass.asType())) {
                    // add inherited methods as declarations
                    Map<String, Object> methodObject = new HashMap<>();
                    methodObject.put("'key.name'", "'" + method.getName() + "'");

                    if (method.isStatic()) {
                        methodObject.put("'key.kind'", "'StaticMethodDeclaration'");
                    } else {
                        methodObject.put("'key.kind'", "'InstanceMethodDeclaration'");
                    }

                    methodObject.put("'key.usr'", "'" + method.getQualifiedSignature() + "'");
                    methodObject.put("'key.isMethodReference'", 1);

                    subEntities.add(methodObject);
                }
            }
        } catch (UnsolvedSymbolException exception) {
            // if we cannot resolve class, we cannot find inherited methods, so currently fail silently
        }

        object.put("'key.entities'", subEntities);


        List<Map<String,Object>> annotations = new ArrayList<>();

        for(AnnotationExpr annotationExpr: node.getAnnotations()) {
            annotations.add(handleAnnotation(annotationExpr));
        }

        object.put("'key.annotations'", annotations);

        List<Map<String,Object>> parents = new ArrayList<>();

        for(ClassOrInterfaceType type: node.getExtendedTypes()) {
            parents.add(handeParent(type));
        }
        for(ClassOrInterfaceType type: node.getImplementedTypes()) {
            parents.add(handeParent(type));
        }

        object.put("'key.parents'", parents);

        List<Map<String,Object>> modifiers = new ArrayList<>();

        for(Modifier modifier: node.getModifiers()) {
            modifiers.add(handleModifier(modifier));
        }

        object.put("'key.modifiers'", modifiers);

        objects.add(object);

        for(Node child: node.getChildNodes()) {
            if(child.getClass() == ClassOrInterfaceDeclaration.class) {
                ClassOrInterfaceDeclaration nestedClass = (ClassOrInterfaceDeclaration)child;
                objects.addAll(handleClass(nestedClass));
            }

            if(child.getClass() == EnumDeclaration.class) {
                EnumDeclaration nestedClass = (EnumDeclaration)child;
                objects.addAll(handleEnum(nestedClass));
            }
        }

        return objects;
    }

    public static List<Map<String,Object>> handleEnum(EnumDeclaration node) {
        List<Map<String,Object>> objects = new ArrayList<>();
        Map<String,Object> object = new HashMap<>();

        setStartEnd(node, object);

        object.put("'key.name'", "'" + node.getNameAsString() + "'");
        object.put("'key.kind'",  "'EnumDeclaration'");

        if(node.getFullyQualifiedName().isPresent()) {
            object.put("'key.usr'", "'" + node.getFullyQualifiedName().get() + "'");
        }
        object.put("'key.name'", "'" + node.getNameAsString() + "'");

        List<Map<String,Object>> subEntities = new ArrayList<>();

        for(MethodDeclaration method: node.getMethods()) {
            subEntities.add(handleMethod(method));
        }

        for(FieldDeclaration fieldDeclaration: node.getFields()) {
            subEntities.addAll(handleVariable(fieldDeclaration, node.getFullyQualifiedName().get()));
        }

        object.put("'key.entities'", subEntities);


        List<Map<String,Object>> annotations = new ArrayList<>();

        for(AnnotationExpr annotationExpr: node.getAnnotations()) {
            annotations.add(handleAnnotation(annotationExpr));
        }

        object.put("'key.annotations'", annotations);

        List<Map<String,Object>> parents = new ArrayList<>();

        for(ClassOrInterfaceType type: node.getImplementedTypes()) {
            parents.add(handeParent(type));
        }

        object.put("'key.parents'", parents);

        List<Map<String,Object>> modifiers = new ArrayList<>();

        for(Modifier modifier: node.getModifiers()) {
            modifiers.add(handleModifier(modifier));
        }

        object.put("'key.modifiers'", modifiers);

        objects.add(object);

        for(Node child: node.getChildNodes()) {
            if(child.getClass() == ClassOrInterfaceDeclaration.class) {
                ClassOrInterfaceDeclaration nestedClass = (ClassOrInterfaceDeclaration)child;
                objects.addAll(handleClass(nestedClass));
            }

            if(child.getClass() == EnumDeclaration.class) {
                EnumDeclaration nestedClass = (EnumDeclaration)child;
                objects.addAll(handleEnum(nestedClass));
            }
        }

        return objects;
    }

    private static List<Map<String,Object>> handleCompilationUnit(CompilationUnit cu) {
        List<Map<String,Object>> classes = new ArrayList<>();


        NodeList<TypeDeclaration<?>> types = cu.getTypes();// --> other way of doing it
        for(TypeDeclaration<?> type: types) {
            if(type.isClassOrInterfaceDeclaration()) {
                List<Map<String,Object>> objects = handleClass((ClassOrInterfaceDeclaration) type);
                classes.addAll(objects);

            } else if(type.isEnumDeclaration()) {
                List<Map<String,Object>> objects = handleEnum((EnumDeclaration) type);
                classes.addAll(objects);

            } else if(type.isAnnotationDeclaration()) {
                //TODO: what do we do here?
            }
        }


        /*
        List<Node> children =  cu.getChildNodes();
        for(Node child: children) {
            // if PackageDeclaration --> which package it belongs to
            // if ImportDeclaration --> which classes/libraries are imported
            // if ClassOrInterfaceDeclaration --> class types
            // if AnnotationDeclaration --> annotations?
            // if EnumDeclaration --> enum --> TODO: implement enum

            if(child.getClass() == ClassOrInterfaceDeclaration.class) {
                Map<String,Object> foundClass = handleClass((ClassOrInterfaceDeclaration) child);
                classes.add(foundClass);
            }
        }
         */

        return classes;
    }

    /*
    public static Map<String,Object> handleNode(Node node) {

        Map<String,Object> object = new HashMap<>();
        String kind = node.getClass().toString();

        String name = null; // to be set
        String usr = null; // to be set
        String type = null; // to be set
        ArrayList<Map<String,Object>> entities = new ArrayList<>();

        if(node.getClass() == MethodDeclaration.class) {
            MethodDeclaration method = (MethodDeclaration) node;
            object = handleMethod(method);

        } else if(node.getClass() == FieldDeclaration.class) {
            FieldDeclaration variable = (FieldDeclaration)node;
            object = handleVariable(variable);
        } else {
            //TODO: check what from here needs to stay
            for(Node child: node.getChildNodes()) {
                if (child.getClass() == SimpleName.class) {
                    SimpleName simpleName = (SimpleName) child;
                    name = simpleName.asString();
                } else if (child.getClass() == Name.class) {
                    Name simpleName = (Name) child;
                    name = simpleName.asString();
                } else {
                    Map<String, Object> childObject = handleNode(child);
                    entities.add(childObject);
                }
            }

            if(node.getClass() == NameExpr.class) {
                NameExpr nameExpr = (NameExpr)node;

                try {
                    ResolvedType resolvedType = nameExpr.calculateResolvedType();

                    if (resolvedType.isPrimitive()) {
                        type = resolvedType.asPrimitive().name();
                    } else {
                        if (resolvedType.isReferenceType()) {
                            String qualifiedName = resolvedType.asReferenceType().getQualifiedName();
                            type = qualifiedName;
                        }
                    }
                } catch (UnsolvedSymbolException e) {
                    type = "?";
                } catch (UnsupportedOperationException e) {
                    type = "?";
                } catch (RuntimeException e) {
                    type = "?";
                }

                try {
                    ResolvedValueDeclaration resolvedValueDeclaration = nameExpr.resolve();
                    if(resolvedValueDeclaration.isField()) {
                        ResolvedFieldDeclaration resolvedFieldDeclaration = resolvedValueDeclaration.asField();
                        String declaringTypeName = resolvedFieldDeclaration.declaringType().getQualifiedName();

                        usr = declaringTypeName + "." + nameExpr.getName();
                    }
                } catch (UnsolvedSymbolException exception) {
                    //System.out.println("Cannot solve: " + nameExpr);
                } catch (UnsupportedOperationException e) {
                    //
                }
            }
        }

        //TODO: move this under method handling
        /*
        if(node.getClass() == MethodCallExpr.class) {
            MethodCallExpr methodCallExpr = (MethodCallExpr)node;

            String scope = "";

            try {
                if (methodCallExpr.getScope().isPresent()) {
                    Expression methodScope = methodCallExpr.getScope().get();
                    ResolvedType resolvedType = methodScope.calculateResolvedType();
                    if (resolvedType.isReferenceType()) {
                        scope = resolvedType.asReferenceType().getQualifiedName();
                    } else {
                        scope = resolvedType.toString();
                    }
                }
            } catch (UnsolvedSymbolException e) {
                scope = "?";
            } catch (RuntimeException e) {
                scope = "??";
            }

            String methodName = methodCallExpr.getNameAsString();
            String fullName = scope + "." + methodName;
            usr = fullName;

        } else if(node.getClass() == FieldAccessExpr.class) {
            FieldAccessExpr fieldAccessExpr = (FieldAccessExpr)node;
            Expression fieldScope = fieldAccessExpr.getScope();
            String scope;
            try {
                scope = fieldScope.calculateResolvedType().asReferenceType().getQualifiedName();
            } catch (UnsolvedSymbolException e) {
                scope = "?";
            } catch (UnsupportedOperationException e) {
                scope = "?";
            } catch (RuntimeException e) {
                scope = "?";
            }

            String fieldName = fieldAccessExpr.getNameAsString();
            String fullName = scope + "." + fieldName;
            usr = fullName;
        }

         */
    /*

        int startLine;
        int endLine;

        try {
            Optional<Range> optRange = node.getRange();
            Range range = optRange.get();
            startLine = range.begin.line;
            endLine = range.end.line;
        } catch (NoSuchElementException e) {
            startLine = -1;
            endLine = -1;
        }

        if(usr == null) {
            usr = name;
        }

        object.put("'key.name'", "'"+name+"'");
        object.put("'key.usr'", "'"+usr+"'");
        object.put("'key.type'", "'"+type+"'");
        object.put("'key.kind'", "'"+kind+"'");
        object.put("'key.entities'", entities);
        object.put("'key.startLine'", startLine);
        object.put("'key.endLine'", endLine);

        return object;
    }
    */
}

