"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = require("fs");
const path_1 = require("path");
const os_1 = require("os");
const utils = require("../utils/utils");
const fileUtils = require("./file-utils");
const types_1 = require("@deepstream/types");
const distributed_cluster_registry_1 = require("../services/cluster-registry/distributed-cluster-registry");
const single_cluster_node_1 = require("../services/cluster-node/single-cluster-node");
const default_subscription_registry_factory_1 = require("../services/subscription-registry/default-subscription-registry-factory");
const connection_endpoint_1 = require("../connection-endpoint/http/connection-endpoint");
const combine_authentication_1 = require("../services/authentication/combine/combine-authentication");
const open_authentication_1 = require("../services/authentication/open/open-authentication");
const config_permission_1 = require("../services/permission/valve/config-permission");
const open_permission_1 = require("../services/permission/open/open-permission");
const connection_endpoint_2 = require("../connection-endpoint/websocket/binary/connection-endpoint");
const connection_endpoint_3 = require("../connection-endpoint/websocket/text/connection-endpoint");
const connection_endpoint_4 = require("../connection-endpoint/websocket/json/connection-endpoint");
const connection_endpoint_5 = require("../connection-endpoint/mqtt/connection-endpoint");
const file_based_authentication_1 = require("../services/authentication/file/file-based-authentication");
const storage_based_authentication_1 = require("../services/authentication/storage/storage-based-authentication");
const http_authentication_1 = require("../services/authentication/http/http-authentication");
const noop_storage_1 = require("../services/storage/noop-storage");
const local_cache_1 = require("../services/cache/local-cache");
const std_out_logger_1 = require("../services/logger/std/std-out-logger");
const pino_logger_1 = require("../services/logger/pino/pino-logger");
const noop_monitoring_1 = require("../services/monitoring/noop-monitoring");
const distributed_lock_registry_1 = require("../services/lock/distributed-lock-registry");
const distributed_state_registry_factory_1 = require("../services/cluster-state/distributed-state-registry-factory");
const default_options_1 = require("../default-options");
const node_http_1 = require("../services/http/node/node-http");
const monitoring_http_1 = require("../services/monitoring/http/monitoring-http");
const monitoring_log_1 = require("../services/monitoring/log/monitoring-log");
const configValidator = require("./config-validator");
const heap_snapshot_1 = require("../plugins/heap-snapshot/heap-snapshot");
let commandLineArguments;
const customPlugins = new Map();
const defaultPlugins = new Map([
    ['cache', local_cache_1.LocalCache],
    ['storage', noop_storage_1.NoopStorage],
    ['logger', std_out_logger_1.StdOutLogger],
    ['locks', distributed_lock_registry_1.DistributedLockRegistry],
    ['subscriptions', default_subscription_registry_factory_1.DefaultSubscriptionRegistryFactory],
    ['clusterRegistry', distributed_cluster_registry_1.DistributedClusterRegistry],
    ['clusterStates', distributed_state_registry_factory_1.DistributedStateRegistryFactory],
    ['clusterNode', single_cluster_node_1.SingleClusterNode],
    ['httpService', node_http_1.NodeHTTP],
]);
exports.mergeConnectionOptions = function (config) {
    if (config && config.connectionEndpoints) {
        const defaultConfig = default_options_1.get();
        for (const connectionEndpoint of config.connectionEndpoints) {
            const defaultPlugin = defaultConfig.connectionEndpoints.find((defaultEndpoint) => defaultEndpoint.type === connectionEndpoint.type);
            if (defaultPlugin) {
                connectionEndpoint.options = utils.merge(defaultPlugin.options, connectionEndpoint.options);
            }
        }
    }
};
/**
 * Registers plugins by name. Useful when wanting to include
 * custom plugins in a binary
 */
exports.registerPlugin = function (name, construct) {
    customPlugins.set(name, construct);
};
/**
 * Takes a configuration object and instantiates functional properties.
 * CLI arguments will be considered.
 */
exports.initialize = function (deepstream, config, initialLogs = []) {
    configValidator.validate(config);
    if (config.showLogo === true) {
        const logo = fs_1.readFileSync(path_1.join(__dirname, '..', '..', '/ascii-logo.txt'), 'utf8');
        process.stdout.write(logo);
        process.stdout.write(`${os_1.EOL}=====================   starting   =====================${os_1.EOL}`);
    }
    // @ts-ignore
    commandLineArguments = global.deepstreamCLI || {};
    handleUUIDProperty(config);
    exports.mergeConnectionOptions(config);
    const services = {};
    services.notifyFatalException = () => {
        if (config.exitOnFatalError) {
            process.exit(1);
        }
        else {
            deepstream.emit(types_1.EVENT.FATAL_EXCEPTION);
        }
    };
    services.logger = handleLogger(config, services);
    services.monitoring = handleMonitoring(config, services);
    initialLogs.forEach((log) => {
        switch (log.level) {
            case types_1.LOG_LEVEL.DEBUG:
                services.logger.debug(log.event, log.message, log.meta);
                break;
            case types_1.LOG_LEVEL.ERROR:
                services.logger.error(log.event, log.message, log.meta);
                break;
            case types_1.LOG_LEVEL.INFO:
                services.logger.info(log.event, log.message, log.meta);
                break;
            case types_1.LOG_LEVEL.WARN:
                services.logger.warn(log.event, log.message, log.meta);
                break;
            case types_1.LOG_LEVEL.FATAL:
                services.logger.fatal(log.event, log.message, log.meta);
                break;
        }
    });
    services.subscriptions = new (resolvePluginClass(config.subscriptions, 'subscriptions', services.logger))(config.subscriptions.options, services, config);
    services.storage = new (resolvePluginClass(config.storage, 'storage', services.logger))(config.storage.options, services, config);
    services.cache = new (resolvePluginClass(config.cache, 'cache', services.logger))(config.cache.options, services, config);
    services.authentication = handleAuthStrategies(config, services);
    services.permission = handlePermissionStrategies(config, services);
    services.connectionEndpoints = handleConnectionEndpoints(config, services);
    services.locks = new (resolvePluginClass(config.locks, 'locks', services.logger))(config.locks.options, services, config);
    services.clusterNode = new (resolvePluginClass(config.clusterNode, 'clusterNode', services.logger))(config.clusterNode.options, services, config);
    services.clusterRegistry = new (resolvePluginClass(config.clusterRegistry, 'clusterRegistry', services.logger))(config.clusterRegistry.options, services, config);
    services.clusterStates = new (resolvePluginClass(config.clusterStates, 'clusterStates', services.logger))(config.clusterStates.options, services, config);
    services.httpService = handleHTTPServer(config, services);
    handleCustomPlugins(config, services);
    return { config, services };
};
/**
 * Transform the UUID string config to a UUID in the config object.
 */
function handleUUIDProperty(config) {
    if (config.serverName === 'UUID') {
        config.serverName = utils.getUid();
    }
}
/**
 * Initialize the logger and overwrite the root logLevel if it's set
 * CLI arguments will be considered.
 */
function handleLogger(config, services) {
    const configOptions = (config.logger || {}).options;
    if (commandLineArguments.colors !== undefined) {
        configOptions.colors = commandLineArguments.colors;
    }
    let LoggerClass = defaultPlugins.get('logger');
    if (config.logger.name === 'pino') {
        LoggerClass = pino_logger_1.PinoLogger;
    }
    else if (config.logger.name || config.logger.path) {
        LoggerClass = resolvePluginClass(config.logger, 'logger', services.logger);
        if (!LoggerClass) {
            throw new Error(`unable to resolve plugin ${config.logger.name || config.logger.path}`);
        }
    }
    const logger = new LoggerClass(configOptions, services, config);
    if (logger.log) {
        logger.debug = logger.debug || logger.log.bind(logger, types_1.LOG_LEVEL.DEBUG);
        logger.info = logger.info || logger.log.bind(logger, types_1.LOG_LEVEL.INFO);
        logger.warn = logger.warn || logger.log.bind(logger, types_1.LOG_LEVEL.WARN);
        logger.error = logger.error || logger.log.bind(logger, types_1.LOG_LEVEL.ERROR);
    }
    if (types_1.LOG_LEVEL[config.logLevel] !== undefined) {
        if (typeof config.logLevel === 'string') {
            logger.setLogLevel(types_1.LOG_LEVEL[config.logLevel]);
        }
        else {
            logger.setLogLevel(config.logLevel);
        }
    }
    else if (config.logLevel) {
        throw new Error(`Unknown logLevel ${types_1.LOG_LEVEL[config.logLevel]}`);
    }
    return logger;
}
/**
 * Handle the plugins property in the config object the connectors.
 * Plugins can be passed either as a __path__ property or as a __name__ property with
 * a naming convention: *{cache: {name: 'redis'}}* will be resolved to the
 * npm module *@deepstream/cache-redis*
 * Options to the constructor of the plugin can be passed as *options* object.
 *
 * CLI arguments will be considered.
 */
function handleCustomPlugins(config, services) {
    services.plugins = {};
    if (config.plugins == null) {
        return;
    }
    const plugins = { ...config.plugins };
    for (const key in plugins) {
        const plugin = plugins[key];
        if (plugin.name === 'heap-snapshot') {
            services.plugins[key] = new heap_snapshot_1.default(plugin.options || {}, services);
        }
        else {
            const PluginConstructor = resolvePluginClass(plugin, 'plugin', services.logger);
            services.plugins[key] = new PluginConstructor(plugin.options || {}, services, config);
        }
    }
}
/**
 * Handle connection endpoint plugin config.
 * The type is typically the protocol e.g. ws
 * Plugins can be passed either as a __path__ property or as a __name__ property with
 * a naming convetion: *{amqp: {name: 'my-plugin'}}* will be resolved to the
 * npm module *deepstream.io/connection-my-plugin*
 * Exception: the name *uws* will be resolved to deepstream.io's internal uWebSockets plugin
 * Options to the constructor of the plugin can be passed as *options* object.
 *
 * CLI arguments will be considered.
 */
function handleConnectionEndpoints(config, services) {
    // delete any endpoints that have been set to `null`
    for (const type in config.connectionEndpoints) {
        if (config.connectionEndpoints[type] === null) {
            delete config.connectionEndpoints[type];
        }
    }
    if (!config.connectionEndpoints || Object.keys(config.connectionEndpoints).length === 0) {
        throw new Error('No connection endpoints configured');
    }
    const connectionEndpoints = [];
    for (const plugin of config.connectionEndpoints) {
        plugin.options = plugin.options || {};
        let PluginConstructor;
        if (plugin.type === 'ws-binary') {
            PluginConstructor = connection_endpoint_2.WSBinaryConnectionEndpoint;
        }
        else if (plugin.type === 'ws-text') {
            PluginConstructor = connection_endpoint_3.WSTextConnectionEndpoint;
        }
        else if (plugin.type === 'ws-json') {
            PluginConstructor = connection_endpoint_4.WSJSONConnectionEndpoint;
        }
        else if (plugin.type === 'mqtt') {
            PluginConstructor = connection_endpoint_5.MQTTConnectionEndpoint;
        }
        else if (plugin.type === 'http') {
            PluginConstructor = connection_endpoint_1.HTTPConnectionEndpoint;
        }
        else {
            PluginConstructor = resolvePluginClass(plugin, 'connection', services.logger);
        }
        connectionEndpoints.push(new PluginConstructor(plugin.options, services, config));
    }
    return connectionEndpoints;
}
/**
 * Instantiate the given plugin, which either needs a path property or a name
 * property which fits to the npm module name convention. Options will be passed
 * to the constructor.
 *
 * CLI arguments will be considered.
 */
function resolvePluginClass(plugin, type, logger) {
    if (customPlugins.has(plugin.name)) {
        return customPlugins.get(plugin.name);
    }
    // Required for bundling via nexe
    const req = require;
    let requirePath;
    let pluginConstructor;
    let es6Adaptor;
    if (plugin.path != null) {
        try {
            requirePath = fileUtils.lookupLibRequirePath(plugin.path);
            es6Adaptor = req(requirePath);
            pluginConstructor = es6Adaptor.default ? es6Adaptor.default : es6Adaptor;
        }
        catch (error) {
            logger.fatal(types_1.EVENT.CONFIG_ERROR, `Error loading ${type} plugin via path ${requirePath}`);
            // Throw error due to how tests are written
            throw new Error();
        }
    }
    else if (plugin.name != null && type) {
        try {
            requirePath = fileUtils.lookupLibRequirePath(`@deepstream/${type.toLowerCase()}-${plugin.name.toLowerCase()}`);
            es6Adaptor = req(requirePath);
        }
        catch (firstError) {
            const firstPath = requirePath;
            try {
                requirePath = fileUtils.lookupLibRequirePath(`deepstream.io-${type.toLowerCase()}-${plugin.name.toLowerCase()}`);
                es6Adaptor = req(requirePath);
            }
            catch (secondError) {
                logger.debug(types_1.EVENT.CONFIG_ERROR, `Error loading module ${firstPath}: ${firstError}`);
                logger.debug(types_1.EVENT.CONFIG_ERROR, `Error loading module ${requirePath}: ${secondError}`);
                logger.fatal(types_1.EVENT.CONFIG_ERROR, 'Error loading module, exiting');
                // Throw error due to how tests are written
                throw new Error();
            }
        }
        pluginConstructor = es6Adaptor.default ? es6Adaptor.default : es6Adaptor;
    }
    else if (plugin.name != null) {
        try {
            requirePath = fileUtils.lookupLibRequirePath(plugin.name);
            es6Adaptor = req(requirePath);
            pluginConstructor = es6Adaptor.default ? es6Adaptor.default : es6Adaptor;
        }
        catch (error) {
            logger.fatal(types_1.EVENT.CONFIG_ERROR, `Error loading ${type} plugin via name ${plugin.name}`);
            // Throw error due to how tests are written
            throw new Error();
        }
    }
    else if (plugin.type === 'default' && defaultPlugins.has(type)) {
        pluginConstructor = defaultPlugins.get(type);
    }
    else {
        // This error is used to bubble the event due to how tests are written
        throw new Error(`Neither name nor path property found for ${type} plugin type: ${plugin.type}`);
    }
    return pluginConstructor;
}
/**
 * Instantiates the authentication handlers registered for *config.auth.type*
 *
 * CLI arguments will be considered.
 */
function handleAuthStrategies(config, services) {
    if (commandLineArguments.disableAuth) {
        config.auth = [{
                type: 'none',
                options: {}
            }];
    }
    if (!config.auth) {
        throw new Error('No authentication type specified');
    }
    return new combine_authentication_1.CombineAuthentication(config.auth.map((auth) => handleAuthStrategy(auth, config, services)));
}
/**
 * Instantiates the authentication handler registered for *config.auth.type*
 *
 * CLI arguments will be considered.
 */
function handleAuthStrategy(auth, config, services) {
    let AuthenticationHandlerClass;
    const authStrategies = {
        none: open_authentication_1.OpenAuthentication,
        file: file_based_authentication_1.FileBasedAuthentication,
        http: http_authentication_1.HttpAuthentication,
        storage: storage_based_authentication_1.StorageBasedAuthentication
    };
    if (auth.name || auth.path) {
        AuthenticationHandlerClass = resolvePluginClass(auth, 'authentication', services.logger);
        if (!AuthenticationHandlerClass) {
            throw new Error(`unable to resolve authentication handler ${auth.name || auth.path}`);
        }
    }
    else if (auth.type && authStrategies[auth.type]) {
        AuthenticationHandlerClass = authStrategies[auth.type];
    }
    else {
        throw new Error(`Unknown authentication type ${auth.type}`);
    }
    return new AuthenticationHandlerClass(auth.options, services, config);
}
/**
 * Instantiates the permission handler registered for *config.permission.type*
 *
 * CLI arguments will be considered.
 */
function handlePermissionStrategies(config, services) {
    const permission = config.permission;
    if (!config.permission) {
        throw new Error('No permission type specified');
    }
    if (commandLineArguments.disablePermissions) {
        config.permission.type = 'none';
        config.permission.options = {};
    }
    let PermissionHandlerClass;
    const permissionStrategies = {
        config: config_permission_1.ConfigPermission,
        none: open_permission_1.OpenPermission
    };
    if (permission.name || permission.path) {
        PermissionHandlerClass = resolvePluginClass(permission, 'permission', services.logger);
        if (!PermissionHandlerClass) {
            throw new Error(`unable to resolve plugin ${permission.name || permission.path}`);
        }
    }
    else if (permission.type && permissionStrategies[permission.type]) {
        if (config.permission.options && config.permission.options.path) {
            const req = require;
            config.permission.options.permissions = req(fileUtils.lookupConfRequirePath(config.permission.options.path));
        }
        PermissionHandlerClass = permissionStrategies[permission.type];
    }
    else {
        throw new Error(`Unknown permission type ${permission.type}`);
    }
    return new PermissionHandlerClass(permission.options, services, config);
}
function handleMonitoring(config, services) {
    let MonitoringClass;
    const monitoringPlugins = {
        default: noop_monitoring_1.NoopMonitoring,
        none: noop_monitoring_1.NoopMonitoring,
        http: monitoring_http_1.default,
        log: monitoring_log_1.default
    };
    if (config.monitoring.name || config.monitoring.path) {
        return new (resolvePluginClass(config.monitoring, 'monitoring', services.logger))(config.monitoring.options, services, config);
    }
    else if (config.monitoring.type && monitoringPlugins[config.monitoring.type]) {
        MonitoringClass = monitoringPlugins[config.monitoring.type];
    }
    else {
        throw new Error(`Unknown monitoring type ${config.monitoring.type}`);
    }
    return new MonitoringClass(config.monitoring.options, services, config);
}
function handleHTTPServer(config, services) {
    let HttpServerClass;
    const httpPlugins = {
        default: node_http_1.NodeHTTP
    };
    if (commandLineArguments.host) {
        config.httpServer.options.host = commandLineArguments.host;
    }
    if (commandLineArguments.port) {
        config.httpServer.options.port = commandLineArguments.port;
    }
    if (config.httpServer.name || config.httpServer.path) {
        return new (resolvePluginClass(config.httpServer, 'httpServer', services.logger))(config.httpServer.options, services, config);
    }
    else if (config.httpServer.type && httpPlugins[config.httpServer.type]) {
        HttpServerClass = httpPlugins[config.httpServer.type];
    }
    else if (config.httpServer.type === 'uws') {
        try {
            const { UWSHTTP } = require('../services/http/uws/uws-http');
            HttpServerClass = UWSHTTP;
        }
        catch (e) {
            throw new Error('Error loading uws http service, this is most likely due to uWebsocket.js not being supported on this platform');
        }
    }
    else {
        throw new Error(`Unknown httpServer type ${config.httpServer.type}`);
    }
    return new HttpServerClass(config.httpServer.options, services, config);
}
//# sourceMappingURL=config-initialiser.js.map