import * as _ from '../../utils/collection.util';

import { MiscellaneousData } from '../interfaces/miscellaneous-data.interface';
import { ParsedData } from '../interfaces/parsed-data.interface';
import { RouteInterface } from '../interfaces/routes.interface';

import AngularApiUtil from '../../utils/angular-api.util';
import { IApiSourceResult } from '../../utils/api-source-result.interface';
import { getNamesCompareFn } from '../../utils/utils';

import {
    IEnumDecDep,
    IFunctionDecDep,
    IGuardDep,
    IInjectableDep,
    IInterceptorDep,
    IInterfaceDep,
    IPipeDep,
    ITypeAliasDecDep
} from '../compiler/angular/dependencies.interfaces';

import { IComponentDep } from '../compiler/angular/deps/component-dep.factory';
import { IControllerDep } from '../compiler/angular/deps/controller-dep.factory';
import { IDirectiveDep } from '../compiler/angular/deps/directive-dep.factory';
import { IModuleDep } from '../compiler/angular/deps/module-dep.factory';
import { InterfaceDeclarationMerger } from './dependencies/interface-declaration-merger';

const traverse = require('neotraverse/legacy');

export class DependenciesEngine {
    private static readonly angularApiAliases: { [name: string]: string[] } = {
        NgFor: ['NgForOf']
    };

    public rawData: ParsedData;
    public modules: Object[];
    public rawModules: Object[];
    public rawModulesForOverview: Object[];
    public components: Object[];
    public controllers: Object[];
    public entities: Object[];
    public directives: Object[];
    public injectables: Object[];
    public interceptors: Object[];
    public guards: Object[];
    public interfaces: IInterfaceDep[];
    public routes: RouteInterface;
    public pipes: Object[];
    public classes: Object[];
    public miscellaneous: MiscellaneousData = {
        variables: [],
        functions: [],
        typealiases: [],
        enumerations: [],
        groupedVariables: [],
        groupedFunctions: [],
        groupedEnumerations: [],
        groupedTypeAliases: []
    };
    private relationshipsCache: { [name: string]: { incoming: any[]; outgoing: any[] } } = {};
    private interfaceDeclarationMerger = new InterfaceDeclarationMerger();

    private static instance: DependenciesEngine;
    private constructor() {}
    public static getInstance() {
        if (!DependenciesEngine.instance) {
            DependenciesEngine.instance = new DependenciesEngine();
        }
        return DependenciesEngine.instance;
    }

    private updateModulesDeclarationsExportsTypes() {
        const mergeTypes = entry => {
            const directive = this.findInCompodocDependencies(
                entry.name,
                this.directives,
                entry.file
            );
            if (typeof directive.data !== 'undefined') {
                entry.type = 'directive';
                entry.id = directive.data.id;
            }

            const component = this.findInCompodocDependencies(
                entry.name,
                this.components,
                entry.file
            );
            if (typeof component.data !== 'undefined') {
                entry.type = 'component';
                entry.id = component.data.id;
            }

            const pipe = this.findInCompodocDependencies(entry.name, this.pipes, entry.file);
            if (typeof pipe.data !== 'undefined') {
                entry.type = 'pipe';
                entry.id = pipe.data.id;
            }
        };

        this.modules.forEach((module: any) => {
            module.declarations.forEach(declaration => {
                mergeTypes(declaration);
            });
            module.exports.forEach(expt => {
                mergeTypes(expt);
            });
            module.entryComponents.forEach(ent => {
                mergeTypes(ent);
            });
        });
    }

    public init(data: ParsedData) {
        traverse(data).forEach(function (node) {
            if (node) {
                if (node.parent) {
                    delete node.parent;
                }
                if (node.initializer) {
                    delete node.initializer;
                }
            }
        });
        this.rawData = data;
        this.modules = _.sortBy(this.rawData.modules, [el => el.name.toLowerCase()]);
        this.rawModulesForOverview = _.sortBy(data.modulesForGraph, [el => el.name.toLowerCase()]);
        this.rawModules = _.sortBy(data.modulesForGraph, [el => el.name.toLowerCase()]);
        this.components = _.sortBy(this.rawData.components, [el => el.name.toLowerCase()]);
        this.controllers = _.sortBy(this.rawData.controllers, [el => el.name.toLowerCase()]);
        this.entities = _.sortBy(this.rawData.entities, [el => el.name.toLowerCase()]);
        this.directives = _.sortBy(this.rawData.directives, [el => el.name.toLowerCase()]);
        this.injectables = _.sortBy(this.rawData.injectables, [el => el.name.toLowerCase()]);
        this.interceptors = _.sortBy(this.rawData.interceptors, [el => el.name.toLowerCase()]);
        this.guards = _.sortBy(this.rawData.guards, [el => el.name.toLowerCase()]);
        this.interfaces = this.interfaceDeclarationMerger.merge(this.rawData.interfaces);
        this.interfaces = _.sortBy(this.interfaces, [el => el.name.toLowerCase()]);
        this.pipes = _.sortBy(this.rawData.pipes, [el => el.name.toLowerCase()]);
        this.classes = _.sortBy(this.rawData.classes, [el => el.name.toLowerCase()]);
        this.miscellaneous = this.rawData.miscellaneous;
        this.prepareMiscellaneous();
        this.updateModulesDeclarationsExportsTypes();
        this.routes = this.rawData.routesTree;
        this.manageDuplicatesName();
        this.cleanRawModulesNames();
        this.relationshipsCache = {};
    }

    private cleanRawModulesNames() {
        this.rawModulesForOverview = this.rawModulesForOverview.map((module: any) => {
            module.name = module.name.replace('$', '');
            return module;
        });
    }

    private findInCompodocDependencies(name, data, file?): IApiSourceResult<any> {
        let _result = {
            source: 'internal',
            data: undefined,
            score: 0
        };
        let nameFoundCounter = 0;
        if (data && data.length > 0) {
            for (let i = 0; i < data.length; i++) {
                if (typeof name !== 'undefined') {
                    if (typeof file !== 'undefined') {
                        if (
                            name === data[i].name &&
                            file.replace(/\\/g, '/').indexOf(data[i].file) !== -1
                        ) {
                            nameFoundCounter += 1;
                            _result.data = data[i];
                            _result.score = 2;
                        } else if (
                            name.indexOf(data[i].name) !== -1 &&
                            file.replace(/\\/g, '/').indexOf(data[i].file) !== -1
                        ) {
                            nameFoundCounter += 1;
                            _result.data = data[i];
                            _result.score = 1;
                        }
                    } else {
                        if (name === data[i].name) {
                            nameFoundCounter += 1;
                            _result.data = data[i];
                            _result.score = 2;
                        } else if (name.indexOf(data[i].name) !== -1) {
                            nameFoundCounter += 1;
                            _result.data = data[i];
                            _result.score = 1;
                        }
                    }
                }
            }

            // Prevent wrong matching like MultiSelectOptionDirective with SelectOptionDirective, or QueryParamGroupService with QueryParamGroup
            if (nameFoundCounter > 1) {
                let found = false;
                for (let i = 0; i < data.length; i++) {
                    if (typeof name !== 'undefined') {
                        if (typeof file !== 'undefined') {
                            if (name === data[i].name) {
                                found = true;
                                _result.data = data[i];
                                _result.score = 2;
                            }
                        } else {
                            if (name === data[i].name) {
                                found = true;
                                _result.data = data[i];
                                _result.score = 2;
                            }
                        }
                    }
                }
                if (!found) {
                    _result = {
                        source: 'internal',
                        data: undefined,
                        score: 0
                    };
                }
            }
        }
        return _result;
    }

    private manageDuplicatesName() {
        const processDuplicates = (element, index, array) => {
            const elementsWithSameName = _.filter(array, { name: element.name });
            if (elementsWithSameName.length > 1) {
                // First element is the reference for duplicates
                for (let i = 1; i < elementsWithSameName.length; i++) {
                    let elementToEdit = elementsWithSameName[i];
                    if (typeof elementToEdit.isDuplicate === 'undefined') {
                        elementToEdit.isDuplicate = true;
                        elementToEdit.duplicateId = i;
                        elementToEdit.duplicateName =
                            elementToEdit.name + '-' + elementToEdit.duplicateId;
                        elementToEdit.id = elementToEdit.id + '-' + elementToEdit.duplicateId;
                    }
                }
            }
            return element;
        };
        this.classes = this.classes.map(processDuplicates);
        this.interfaces = this.interfaces.map(processDuplicates);
        this.injectables = this.injectables.map(processDuplicates);
        this.pipes = this.pipes.map(processDuplicates);
        this.interceptors = this.interceptors.map(processDuplicates);
        this.guards = this.guards.map(processDuplicates);
        this.modules = this.modules.map(processDuplicates);
        this.components = this.components.map(processDuplicates);
        this.controllers = this.controllers.map(processDuplicates);
        this.entities = this.entities.map(processDuplicates);
        this.directives = this.directives.map(processDuplicates);
    }

    public find(name: string): IApiSourceResult<any> | undefined {
        const normalizedName = typeof name === 'string' ? name.trim() : name;
        const namesToResolve = [normalizedName];
        if (DependenciesEngine.angularApiAliases[normalizedName]) {
            namesToResolve.push(...DependenciesEngine.angularApiAliases[normalizedName]);
        }

        let bestScore = 0;
        let bestResult = undefined;

        for (const resolvedName of namesToResolve) {
            const searchFunctions: Array<() => IApiSourceResult<any>> = [
                () => this.findInCompodocDependencies(resolvedName, this.modules),
                () => this.findInCompodocDependencies(resolvedName, this.injectables),
                () => this.findInCompodocDependencies(resolvedName, this.interceptors),
                () => this.findInCompodocDependencies(resolvedName, this.guards),
                () => this.findInCompodocDependencies(resolvedName, this.interfaces),
                () => this.findInCompodocDependencies(resolvedName, this.classes),
                () => this.findInCompodocDependencies(resolvedName, this.components),
                () => this.findInCompodocDependencies(resolvedName, this.controllers),
                () => this.findInCompodocDependencies(resolvedName, this.entities),
                () => this.findInCompodocDependencies(resolvedName, this.directives),
                () => this.findInCompodocDependencies(resolvedName, this.miscellaneous.variables),
                () => this.findInCompodocDependencies(resolvedName, this.miscellaneous.functions),
                () => this.findInCompodocDependencies(resolvedName, this.miscellaneous.typealiases),
                () => this.findInCompodocDependencies(resolvedName, this.miscellaneous.enumerations),
                () => AngularApiUtil.findApi(resolvedName)
            ];

            for (let searchFunction of searchFunctions) {
                const result = searchFunction();
                if (result.data && result.score > bestScore) {
                    bestScore = result.score;
                    bestResult = result;
                }
            }
        }

        return bestResult;
    }

    public update(updatedData): void {
        if (updatedData.modules.length > 0) {
            _.forEach(updatedData.modules, (module: IModuleDep) => {
                const _index = _.findIndex(this.modules, { name: module.name } as any);
                this.modules[_index] = module;
            });
        }
        if (updatedData.components.length > 0) {
            _.forEach(updatedData.components, (component: IComponentDep) => {
                const _index = _.findIndex(this.components, { name: component.name } as any);
                this.components[_index] = component;
            });
        }
        if (updatedData.controllers.length > 0) {
            _.forEach(updatedData.controllers, (controller: IControllerDep) => {
                const _index = _.findIndex(this.controllers, { name: controller.name } as any);
                this.controllers[_index] = controller;
            });
        }
        if (updatedData.entities.length > 0) {
            _.forEach(updatedData.entities, (entity: IControllerDep) => {
                const _index = _.findIndex(this.entities, { name: entity.name } as any);
                this.entities[_index] = entity;
            });
        }
        if (updatedData.directives.length > 0) {
            _.forEach(updatedData.directives, (directive: IDirectiveDep) => {
                const _index = _.findIndex(this.directives, { name: directive.name } as any);
                this.directives[_index] = directive;
            });
        }
        if (updatedData.injectables.length > 0) {
            _.forEach(updatedData.injectables, (injectable: IInjectableDep) => {
                const _index = _.findIndex(this.injectables, { name: injectable.name } as any);
                this.injectables[_index] = injectable;
            });
        }
        if (updatedData.interceptors.length > 0) {
            _.forEach(updatedData.interceptors, (interceptor: IInterceptorDep) => {
                const _index = _.findIndex(this.interceptors, { name: interceptor.name } as any);
                this.interceptors[_index] = interceptor;
            });
        }
        if (updatedData.guards.length > 0) {
            _.forEach(updatedData.guards, (guard: IGuardDep) => {
                const _index = _.findIndex(this.guards, { name: guard.name } as any);
                this.guards[_index] = guard;
            });
        }
        if (updatedData.interfaces.length > 0) {
            _.forEach(updatedData.interfaces, (int: IInterfaceDep) => {
                const _index = _.findIndex(this.interfaces, { name: int.name } as any);
                this.interfaces[_index] = int;
            });
        }
        if (updatedData.pipes.length > 0) {
            _.forEach(updatedData.pipes, (pipe: IPipeDep) => {
                const _index = _.findIndex(this.pipes, { name: pipe.name } as any);
                this.pipes[_index] = pipe;
            });
        }
        if (updatedData.classes.length > 0) {
            _.forEach(updatedData.classes, (classe: any) => {
                const _index = _.findIndex(this.classes, { name: classe.name } as any);
                this.classes[_index] = classe;
            });
        }
        /**
         * Miscellaneous update
         */
        if (updatedData.miscellaneous.variables.length > 0) {
            _.forEach(updatedData.miscellaneous.variables, (variable: any) => {
                const _index = _.findIndex(this.miscellaneous.variables, {
                    name: variable.name,
                    file: variable.file
                });
                this.miscellaneous.variables[_index] = variable;
            });
        }
        if (updatedData.miscellaneous.functions.length > 0) {
            _.forEach(updatedData.miscellaneous.functions, (func: IFunctionDecDep) => {
                const _index = _.findIndex(this.miscellaneous.functions, {
                    name: func.name,
                    file: func.file
                });
                this.miscellaneous.functions[_index] = func;
            });
        }
        if (updatedData.miscellaneous.typealiases.length > 0) {
            _.forEach(updatedData.miscellaneous.typealiases, (typealias: ITypeAliasDecDep) => {
                const _index = _.findIndex(this.miscellaneous.typealiases, {
                    name: typealias.name,
                    file: typealias.file
                });
                this.miscellaneous.typealiases[_index] = typealias;
            });
        }
        if (updatedData.miscellaneous.enumerations.length > 0) {
            _.forEach(updatedData.miscellaneous.enumerations, (enumeration: IEnumDecDep) => {
                const _index = _.findIndex(this.miscellaneous.enumerations, {
                    name: enumeration.name,
                    file: enumeration.file
                });
                this.miscellaneous.enumerations[_index] = enumeration;
            });
        }
        this.prepareMiscellaneous();
        this.relationshipsCache = {};
    }

    public findInCompodoc(name: string) {
        const mergedData = _.concat(
            [],
            this.modules,
            this.components,
            this.controllers,
            this.entities,
            this.directives,
            this.injectables,
            this.interceptors,
            this.guards,
            this.interfaces,
            this.pipes,
            this.classes,
            this.miscellaneous.enumerations,
            this.miscellaneous.typealiases,
            this.miscellaneous.variables,
            this.miscellaneous.functions
        );
        const result = _.find(mergedData, { name: name } as any);
        return result || false;
    }

    /**
     * Returns entity relationships split in two lists:
     * - incoming: entities that reference/use the current entity
     * - outgoing: entities referenced by the current entity
     */
    public getRelationships(entityRef: any): {
        incoming: Array<{ name: string; type: string; description?: string }>;
        outgoing: Array<{ name: string; type: string; description?: string }>;
    } {
        const target = this.resolveEntityFromReference(entityRef);
        const entityName =
            typeof entityRef === 'string' ? entityRef : entityRef?.name;
        if (!entityName) {
            return { incoming: [], outgoing: [] };
        }
        const cacheKey = target?.id || entityName;

        if (this.relationshipsCache[cacheKey]) {
            return this.relationshipsCache[cacheKey];
        }

        const MAX_ITEMS = 80;
        const incoming: Array<{ name: string; type: string; description?: string }> = [];
        const outgoing: Array<{ name: string; type: string; description?: string }> = [];
        const incomingSet = new Set<string>();
        const outgoingSet = new Set<string>();

        const addIncoming = (dep: any) => {
            if (!dep?.name || dep.name === entityName || incoming.length >= MAX_ITEMS) {
                return;
            }
            const key = `${dep.type || 'unknown'}::${dep.name}`;
            if (incomingSet.has(key)) {
                return;
            }
            incomingSet.add(key);
            incoming.push({
                name: dep.name,
                type: dep.type || 'unknown',
                description: this.extractShortDescription(dep)
            });
        };

        const addOutgoing = (item: any, fallbackType = 'dependency') => {
            if (!item) {
                return;
            }
            const name = typeof item === 'string' ? item : item.name;
            if (!name || name === entityName || outgoing.length >= MAX_ITEMS) {
                return;
            }
            const resolved = this.resolveEntityByName(name);
            const type = resolved?.type || item.type || fallbackType;
            const key = `${type}::${name}`;
            if (outgoingSet.has(key)) {
                return;
            }
            outgoingSet.add(key);
            outgoing.push({
                name,
                type,
                description: this.extractShortDescription(resolved || item)
            });
        };

        const includeIfReferences = (dep: any, references?: Array<any>) => {
            if (!references || references.length === 0) {
                return;
            }
            if (
                references.some((ref) =>
                    this.referencePointsToEntity(ref, target, entityName, dep)
                )
            ) {
                addIncoming(dep);
            }
        };

        const allInternalEntities: any[] = this.getAllInternalEntities();

        allInternalEntities.forEach((dep) => {
            includeIfReferences(dep, dep.imports);
            includeIfReferences(dep, dep.exports);
            includeIfReferences(dep, dep.declarations);
            includeIfReferences(dep, dep.controllers);
            includeIfReferences(dep, dep.providers);
            includeIfReferences(dep, dep.viewProviders);
            includeIfReferences(dep, dep.entryComponents);
            includeIfReferences(dep, dep.bootstrap);
            includeIfReferences(dep, dep.hostDirectives);

            if (dep.extends) {
                const extendsList = Array.isArray(dep.extends) ? dep.extends : [dep.extends];
                if (extendsList.some((item) => item === entityName || item?.name === entityName)) {
                    addIncoming(dep);
                }
            }
            if (dep.implements) {
                const implList = Array.isArray(dep.implements)
                    ? dep.implements
                    : [dep.implements];
                if (implList.some((item) => item === entityName || item?.name === entityName)) {
                    addIncoming(dep);
                }
            }
        });

        if (target) {
            (target.imports || []).forEach((item) => addOutgoing(item, 'module'));
            (target.exports || []).forEach((item) => addOutgoing(item, 'module'));
            (target.declarations || []).forEach((item) => addOutgoing(item));
            (target.controllers || []).forEach((item) => addOutgoing(item, 'controller'));
            (target.providers || []).forEach((item) => addOutgoing(item, 'injectable'));
            (target.viewProviders || []).forEach((item) => addOutgoing(item, 'injectable'));
            (target.entryComponents || []).forEach((item) => addOutgoing(item, 'component'));
            (target.bootstrap || []).forEach((item) => addOutgoing(item, 'component'));
            (target.hostDirectives || []).forEach((item) => addOutgoing(item, 'directive'));

            if (target.extends) {
                const extendsList = Array.isArray(target.extends)
                    ? target.extends
                    : [target.extends];
                extendsList.forEach((item) => addOutgoing(item, 'class'));
            }
            if (target.implements) {
                const implList = Array.isArray(target.implements)
                    ? target.implements
                    : [target.implements];
                implList.forEach((item) => addOutgoing(item, 'interface'));
            }
        }

        const relationships = { incoming, outgoing };
        this.relationshipsCache[cacheKey] = relationships;
        return relationships;
    }

    private referencePointsToEntity(
        reference: any,
        targetEntity: any,
        fallbackName: string,
        originEntity?: any
    ): boolean {
        if (!reference || !fallbackName) {
            return false;
        }

        if (targetEntity?.id && typeof reference === 'object' && reference.id) {
            return reference.id === targetEntity.id;
        }

        const resolved = this.resolveEntityFromReference(reference, originEntity);
        if (resolved && targetEntity) {
            return resolved.id === targetEntity.id;
        }

        if (typeof reference === 'string') {
            return reference === fallbackName;
        }

        if (reference?.name !== fallbackName) {
            return false;
        }

        const refType = this.normalizeEntityType(reference?.type);
        const targetType = this.normalizeEntityType(targetEntity?.type);
        if (refType && targetType && refType !== targetType) {
            return false;
        }
        return true;
    }

    private normalizeEntityType(type?: string): string | undefined {
        if (!type) {
            return undefined;
        }
        return type === 'classe' ? 'class' : type;
    }

    private normalizeFilePath(filePath?: string): string | undefined {
        return filePath ? String(filePath).replace(/\\/g, '/') : undefined;
    }

    private getAllInternalEntities(): any[] {
        return _.concat(
            [],
            this.modules,
            this.components,
            this.controllers,
            this.entities,
            this.directives,
            this.injectables,
            this.interceptors,
            this.guards,
            this.interfaces,
            this.pipes,
            this.classes
        );
    }

    private resolveEntityByName(name: string): any {
        if (!name) {
            return undefined;
        }
        return this.resolveEntityFromReference({ name });
    }

    private resolveEntityFromReference(reference: any, originEntity?: any): any {
        if (!reference) {
            return undefined;
        }

        const entities = this.getAllInternalEntities();
        if (typeof reference === 'object' && reference.id) {
            const byId = _.find(entities, { id: reference.id } as any);
            if (byId) {
                return byId;
            }
        }

        const refName = typeof reference === 'string' ? reference : reference.name;
        if (!refName) {
            return undefined;
        }

        let candidates = entities.filter(
            (entity: any) => entity.name === refName || entity.duplicateName === refName
        );
        if (candidates.length === 0) {
            return undefined;
        }
        if (candidates.length === 1) {
            return candidates[0];
        }

        const refType = this.normalizeEntityType(
            typeof reference === 'object' ? reference.type : undefined
        );
        if (refType) {
            const typeCandidates = candidates.filter(
                (entity: any) => this.normalizeEntityType(entity.type) === refType
            );
            if (typeCandidates.length === 1) {
                return typeCandidates[0];
            }
            if (typeCandidates.length > 0) {
                candidates = typeCandidates;
            }
        }

        const refFile = this.normalizeFilePath(
            typeof reference === 'object' ? reference.file : undefined
        );
        if (refFile) {
            const fileCandidates = candidates.filter((entity: any) => {
                const entityFile = this.normalizeFilePath(entity.file);
                return !!entityFile && (entityFile === refFile || refFile.indexOf(entityFile) !== -1);
            });
            if (fileCandidates.length === 1) {
                return fileCandidates[0];
            }
            if (fileCandidates.length > 0) {
                candidates = fileCandidates;
            }
        }

        const originFile = this.normalizeFilePath(originEntity?.file);
        if (originFile) {
            const sameFileCandidates = candidates.filter((entity: any) => {
                const entityFile = this.normalizeFilePath(entity.file);
                return !!entityFile && entityFile === originFile;
            });
            if (sameFileCandidates.length === 1) {
                return sameFileCandidates[0];
            }
        }

        const nonDuplicateCandidates = candidates.filter((entity: any) => !entity.isDuplicate);
        if (nonDuplicateCandidates.length === 1) {
            return nonDuplicateCandidates[0];
        }

        // Ambiguous homonym: avoid linking to the wrong entity.
        return undefined;
    }

    private extractShortDescription(entity: any): string | undefined {
        const raw = entity?.rawdescription || entity?.description || '';
        if (!raw) {
            return undefined;
        }
        const cleaned = String(raw)
            .replace(/<[^>]+>/g, ' ')
            .replace(/\s+/g, ' ')
            .trim();
        if (!cleaned) {
            return undefined;
        }
        return cleaned.length > 140 ? `${cleaned.slice(0, 137).trim()}...` : cleaned;
    }

    private prepareMiscellaneous() {
        this.miscellaneous.variables.sort(getNamesCompareFn());
        this.miscellaneous.functions.sort(getNamesCompareFn());
        this.miscellaneous.enumerations.sort(getNamesCompareFn());
        this.miscellaneous.typealiases.sort(getNamesCompareFn());
        // group each subgoup by file
        this.miscellaneous.groupedVariables = _.groupBy(this.miscellaneous.variables, 'file');
        this.miscellaneous.groupedFunctions = _.groupBy(this.miscellaneous.functions, 'file');
        this.miscellaneous.groupedEnumerations = _.groupBy(this.miscellaneous.enumerations, 'file');
        this.miscellaneous.groupedTypeAliases = _.groupBy(this.miscellaneous.typealiases, 'file');
    }

    public getModule(name: string) {
        return _.find(this.modules, ['name', name]);
    }

    public getRawModule(name: string): any {
        return _.find(this.rawModules, ['name', name]);
    }

    public getModules() {
        return this.modules;
    }

    public getComponents() {
        return this.components;
    }

    public getControllers() {
        return this.controllers;
    }

    public getEntities() {
        return this.entities;
    }

    public getDirectives() {
        return this.directives;
    }

    public getInjectables() {
        return this.injectables;
    }

    public getInterceptors() {
        return this.interceptors;
    }

    public getGuards() {
        return this.guards;
    }

    public getInterfaces() {
        return this.interfaces;
    }

    public getRoutes() {
        return this.routes;
    }

    public getPipes() {
        return this.pipes;
    }

    public getClasses() {
        return this.classes;
    }

    public getMiscellaneous() {
        return this.miscellaneous;
    }
}

export default DependenciesEngine.getInstance();
