# Source: lib/init/config.js

    'use strict';
    const path = require('path');
    const fs = require('fs-extra');
    const capitalize = require('capitalize');
    const lowkie = require('lowkie');
    const semver = require('semver');
    const mongoose = require('mongoose');
    const Promisie = require('promisie');
    const Sequelize = require('sequelize');
    const CoreControllerModule = require('periodicjs.core.controller');
    const CoreMailerModule = require('periodicjs.core.mailer');
    const defaultSettings = require('../defaults');
    const schemas = require('../schemas');
    const flatten = require('flat');
    const schemeDefaults = require('../defaults/schema');
    mongoose.Promise = Promisie;
    const fullModelFilePath = '/___FULLPATH___';

    /**
     * configureLowkie creates a loki connection to store DB based configurations
     *
     * @returns {Promise} connects to configuration db
     */
    function configureLowkie() {
      return new Promise((resolve, reject) => {
        try {
          const __CONFIG_DB = path.resolve(this.config.app_root, this.config.configuration.options.dbpath);
          lowkie.connect(__CONFIG_DB)
            .then((db) => {
              const configurationModel = lowkie.model('configuration', schemas.config.lowkie, {});
              const extensionModel = lowkie.model('extension', schemas.extension.lowkie, { unique: ['name', ], });
              const CoreConfigDataAdapter = this.core.data.create(
                Object.assign({
                  adapter: 'loki',
                  model: configurationModel,
                  db_connection:db,
                }, schemeDefaults.coreDataOptions.configuration)
              );
              const CoreExtensionDataAdapter = this.core.data.create(
                Object.assign({
                  adapter: 'loki',
                  model: extensionModel,
                  db_connection:db,
                }, schemeDefaults.coreDataOptions.extension)
              );
              this.dbs.set('configuration', db);
              this.datas.set('configuration', CoreConfigDataAdapter);
              this.datas.set('extension', CoreExtensionDataAdapter);
              resolve(true);
            })
            .catch(reject);
          lowkie.connection.on('connectionError', (e) => reject);
        } catch (e) {
          reject(e);
        }
      });
    }

    /**
     * configureMongoose creates a mongo connection to store DB based configurations
     *
     * @returns {Promise} connects to configuration db
     */
    function configureMongoose() {
      return new Promise((resolve, reject) => {
        try {
          //Basic usage (mongodb)
          const mongooseConfig = this.config.configuration.options;
          // console.log({ mongooseConfig },'mongooseConfig.mongoose_options',mongooseConfig.mongoose_options)
          const configurationMongoose = mongoose.createConnection(mongooseConfig.url, mongooseConfig.mongoose_options);
          const configurationModel = configurationMongoose.model('configuration', schemas.config.mongoose);
          const extensionModel = configurationMongoose.model('extension', schemas.extension.mongoose);
          const CoreConfigDataAdapter = this.core.data.create(
            Object.assign({
              adapter: 'mongo',
              db_connection:configurationMongoose,
              model: configurationModel,
            }, schemeDefaults.coreDataOptions.configuration)
          );
          const CoreExtensionDataAdapter = this.core.data.create(
            Object.assign({
              adapter: 'mongo',
              db_connection:configurationMongoose,
              model: extensionModel,
            }, schemeDefaults.coreDataOptions.extension)
          );
          // console.log({ CoreConfigDataAdapter, configurationMongoose });
          configurationMongoose.on('error', reject);
          configurationMongoose.on('connected', () => {
            // console.log('mongoose connected', mongooseConfig);
            this.datas.set('configuration', CoreConfigDataAdapter);
            this.datas.set('extension', CoreExtensionDataAdapter);
            this.dbs.set('configuration', configurationMongoose);
            resolve(true);
          });
          // When the connection is disconnected
          configurationMongoose.on('disconnected', () => {
            this.logger.error('Mongoose default connection disconnected');
          });

          if (this.config.debug) {
            mongoose.set('debug', true);
          }
          // // If the Node process ends, close the Mongoose connection
          // process.on('SIGINT', () => {
          //   configurationMongoose.close(() =>{
          //     this.logger.silly('Mongoose default connection disconnected through app termination');
          //     process.exit(0);
          //   });
          // });
        } catch (e) {
          reject(e);
        }
      });
    }

    /**
     * configureSequelize creates a mongo connection to store DB based configurations
     *
     * @returns {Promise} connects to configuration db
     */
    function configureSequelize() {
      return new Promise((resolve, reject) => {
        try {
          // Basic usage (sql)
          const sqlConfig = this.config.configuration.options;
          sqlConfig.connection_options = Object.assign({}, {
            logging: this.config.debug, }, sqlConfig.connection_options);

          const configurationSQLdB = new Sequelize(sqlConfig.database, sqlConfig.username, sqlConfig.password, sqlConfig.connection_options);
          const configurationModel = configurationSQLdB.define('configuration', schemas.config.sequelize.scheme, schemas.config.sequelize.options);
          const extensionModel = configurationSQLdB.define('extension', schemas.extension.sequelize.scheme, schemas.extension.sequelize.options);
          // configurationSQLdB.sync({force:true})
          configurationSQLdB.sync({})
            .then(_connection => _connection.authenticate())
            .then(() => {
              const CoreConfigDataAdapter = this.core.data.create(
                Object.assign({
                  adapter: 'sql',
                  model: configurationModel,
                  db_connection: configurationSQLdB,
                  docid: '_id',
                }, schemeDefaults.coreDataOptions.configuration)
              );
              const CoreExtensionDataAdapter = this.core.data.create(
                Object.assign({
                  adapter: 'sql',
                  model: extensionModel,
                  db_connection: configurationSQLdB,
                  docid: '_id',
                }, schemeDefaults.coreDataOptions.extension)
              );
              this.datas.set('configuration', CoreConfigDataAdapter);
              this.datas.set('extension', CoreExtensionDataAdapter);
              this.dbs.set('configuration', configurationSQLdB);
              resolve(true);
            })
            .catch(reject);
        } catch (e) {
          reject(e);
        }
      });
    }

    /**
     * reads content/config/(config.json||config.js) for  the configuration database
     *
     * @returns Promise loadConfiguration sets up application config db
     */
    function loadConfiguration(customConfig) {
      return new Promise((resolve, reject) => {
        try {
          const __CONFIG_DIR = path.resolve(this.config.app_root, 'content/config');
          // const __CONFIG_JSON_PATH = path.join(__CONFIG_DIR, 'config.json');
          const app_configuration = require(path.join(__CONFIG_DIR, 'config'));
          // console.log({ app_configuration })
          // resolve(this);
          // fs.readJson(__CONFIG_JSON_PATH)
          Promise.resolve()
            .then(() => {
              if (typeof app_configuration === 'function') {
                return app_configuration.call(this);
              } else {
                return Promise.resolve(app_configuration);
              }
            })
            .then(app_configuration_settings => {
              this.config.configuration = (typeof customConfig === 'object' && Object.keys(customConfig).length) ? customConfig : app_configuration_settings.configuration;
              this.settings = Object.assign({}, app_configuration_settings.settings);
              if (this.config.configuration.type === 'db') {
                switch (this.config.configuration.db) {
                case 'lowkie':
                  return configureLowkie.call(this);
                case 'mongoose':
                  return configureMongoose.call(this);
                case 'sequelize':
                  return configureSequelize.call(this);
                default:
                  reject(new Error('invalid configuration db'));
                  break;
                }
              } else {
                reject(new Error('invalid configuration type'));
              }
            })
            .then(configdb => {
              resolve(configdb);
            })
            .catch(reject);
        } catch (e) {
          reject(e);
        }
      });
    }

    /**
     * loadAppSettings will configure periodic.settings by merging default settings, environment settings and override settings
     * default settings are in periodicjs/lib/defaults/environment.js
     * environment settings are set in the configuration db in content/config/environment/{name-of-env}.json
     * override settings are stored in content/config/config.json on the 'settings' property
     * @memberof {config}{{}}
     * @returns Promise loadAppSettings config db
     */
    function loadAppSettings() {
      const envConfigFilePath = `content/config/environment/${this.config.process.runtime}.json`;
      // load default settings
      // load environment settings
      // load override settings
      // assign to periodic.config.settings
      return new Promise((resolve, reject) => {
        try {
          this.configuration.load({
            docid: 'filepath',
            query: envConfigFilePath,
          }).then(dbenvconfig => {

            const envconfigJSON = (dbenvconfig && dbenvconfig.toJSON) ?
              dbenvconfig.toJSON() :
              dbenvconfig || {};
            const envconfig = envconfigJSON.config;
            const updatedSettings = flatten.unflatten(
              Object.assign({},
                flatten(defaultSettings.environment),
                flatten(envconfig || {}),
                flatten(this.settings || {}),
                flatten(this.config.settings || {})
              )
            );
            this.settings = updatedSettings;
            this.status.emit('ready', true);

            resolve(updatedSettings);
          }).catch(reject);
        } catch (e) {
          reject(e);
        }
      });
    }

    function assignControllers(options) {
      const { dboptions, controller_name, modelName, CoreConfigDataAdapter, periodic_db_name, } = options;
      // console.log('this.core.controller',this.core.controller)
      const CoreController = new CoreControllerModule(this, {
        compatibility: false,
        skip_responder: true,
        skip_db: true,
        skip_protocol: true,
      });
      // console.log('this.transfor ms',this.transforms)
      CoreController.initialize_responder(dboptions.controller[ controller_name ].responder);
      CoreController.initialize_protocol(dboptions.controller[ controller_name ].protocol);
      CoreController.db[ modelName ] = CoreConfigDataAdapter;

      this.controllers.core.set(`${periodic_db_name}_${controller_name}_${modelName}`, CoreController);
      this.routers.set(`${periodic_db_name}_${controller_name}_${modelName}`, {
        type: 'data',
        router_base: `${periodic_db_name}_${controller_name}`,
        router: CoreController.protocol.api.implement({
          model_name: modelName,
          dirname: path.join(this.config.app_root, 'app/views'),
        }).router,
      });

      if (controller_name === 'default') {
        let controller_router = this.routers.get(`${periodic_db_name}_${controller_name}_${modelName}`);
        this.controllers.core.set(`${periodic_db_name}_${modelName}`, this.controllers.core.get(`${periodic_db_name}_${controller_name}_${modelName}`));
        this.routers.set(`${periodic_db_name}_${modelName}`, {
          type: controller_router.type,
          router_base: periodic_db_name,
          router: controller_router.router,
        });
      }

      // CoreController.initialize_responder({ adapter: 'json' });
    }

    function assignLowkieModels(options) {
      const { modelDirPath, periodic_db_name, db, resolve, dboptions, reject, } = options;
      let { modelFiles, } = options;
      try {
        if (periodic_db_name === 'standard' && this.resources.standard_models.length) {
          modelFiles = modelFiles.concat(this.resources.standard_models);
        }
        const modelpaths = modelFiles.filter(model => model.indexOf('lowkie') !== -1);

        modelpaths.forEach(modelFilePath => {
          const modelName = path.basename(modelFilePath.replace(fullModelFilePath, '')).split('.')[ 0 ];
          const modelModule = (modelFilePath.indexOf(fullModelFilePath) !== -1) ?
            require(modelFilePath.replace(fullModelFilePath, '')) :
            require(path.join(modelDirPath, modelFilePath));
          const lowkieModel = lowkie.model(
            modelName, //modelname
            lowkie.Schema(Object.assign({}, modelModule.scheme, schemeDefaults.lowkie(modelName)), periodic_db_name), //schema
            modelModule.options,
            periodic_db_name);
          const CoreConfigDataAdapter = this.core.data.create(
            Object.assign({
              adapter: 'loki',
              model: lowkieModel,
              db_connection:db,
            }, modelModule.coreDataOptions));
          if (dboptions.controller) {
            Object.keys(dboptions.controller).forEach(controller_name => assignControllers.call(this, { dboptions, controller_name, modelName, CoreConfigDataAdapter, periodic_db_name, }));
          }
          this.datas.set(`${periodic_db_name}_${modelName}`, CoreConfigDataAdapter);
          this.models.set(`${periodic_db_name}_${modelName}`, modelModule);
        });
        this.dbs.set(periodic_db_name, db);
        resolve(true);
      } catch (e) {
        reject(e);
      }
    }

    function assignMongooseModels(options) {
      const { modelDirPath, periodic_db_name, db, resolve, reject, dboptions, } = options;
      let { modelFiles, } = options;

      try {
        if (periodic_db_name === 'standard' && this.resources.standard_models.length) {
          modelFiles = modelFiles.concat(this.resources.standard_models);
        }
        const modelpaths = modelFiles.filter(model => model.indexOf('mongoose') !== -1);
        modelpaths.forEach(modelFilePath => {
          const modelName = path.basename(modelFilePath.replace(fullModelFilePath, '')).split('.')[ 0 ];
          const modelModule = (modelFilePath.indexOf(fullModelFilePath) !== -1) ?
            require(modelFilePath.replace(fullModelFilePath, '')) :
            require(path.join(modelDirPath, modelFilePath));
          const modelSchema = new mongoose.Schema(
            Object.assign({}, modelModule.scheme, schemeDefaults.mongoose(modelName)),
            modelModule.options
          );
          if (modelModule.coreDataOptions && modelModule.coreDataOptions.uniqueCompound) {
            modelSchema.index(modelModule.coreDataOptions.uniqueCompound, { unique: true, });
          }
          if (modelModule.indexes) {
            modelModule.indexes.forEach(newIndex => {
              modelSchema.index(newIndex.keys, newIndex.options);
            });
          }
          const mongooseModel = db.model(
            capitalize(modelName), //modelname
            modelSchema);

          const CoreConfigDataAdapter = this.core.data.create(
            Object.assign({
              adapter: 'mongo',
              model: mongooseModel,
              db_connection:db,
            }, modelModule.coreDataOptions));
          if (dboptions.controller) Object.keys(dboptions.controller).forEach(controller_name => assignControllers.call(this, { dboptions, controller_name, modelName, CoreConfigDataAdapter, periodic_db_name, }));

          this.datas.set(`${periodic_db_name}_${modelName}`, CoreConfigDataAdapter);
          this.models.set(`${periodic_db_name}_${modelName}`, modelModule);
        });

        this.dbs.set(periodic_db_name, db);
        resolve(true);
      } catch (e) {
        reject(e);
      }
    }

    function assignSequelizeModels(options) {
      const { modelDirPath, periodic_db_name, db, resolve, reject, dboptions, } = options;
      let { modelFiles, } = options;
      if (periodic_db_name === 'standard' && this.resources.standard_models.length) {
        modelFiles = modelFiles.concat(this.resources.standard_models);
      }
      const modelpaths = modelFiles.filter(model => model.indexOf('sequelize') !== -1);
      const modelAssociations = [];
      const models = modelpaths.map(modelFilePath => {
        try {
          const modelName = path.basename(modelFilePath.replace(fullModelFilePath, '')).split('.')[ 0 ];
          const modelModule = (modelFilePath.indexOf(fullModelFilePath) !== -1) ?
            require(modelFilePath.replace(fullModelFilePath, '')) :
            require(path.join(modelDirPath, modelFilePath));

          const modelSchema = Object.assign({},
            modelModule.scheme,
            schemeDefaults.sequelize(modelName)
          );
          const modelDefinition = db.define(
            modelName,
            modelSchema,
            modelModule.options
          );
          modelAssociations.push(...modelModule.associations);
          return {
            name: modelName,
            data: modelDefinition,
            associations: modelModule.associations,
            coreDataOptions: modelModule.coreDataOptions,
            modelModule,
          };
        } catch (e) {
          reject(e);
        }
      });
      const modelObj = models.reduce((result, key, i) => {
        result[ key.name ] = models[ i ].data;
        return result;
      }, {});
      // console.log({ modelObj, modelAssociations });
      /**
    		*modelObj: { item: item, user: user }
    		* [{
    		source: 'user',
    		association: 'hasOne',
    		target: 'item',
    		options: {
    			as: 'primaryauthor',
    		},
    	},{
    		source: 'item',
    		association: 'hasMany',
    		target: 'user',
    		options: {
    			as: 'authors',
    		},
    	},]

    	User.belongsTo(Company); // Will add company_uuid to user
    		*/
      modelAssociations.forEach(modelAssoc => {
        // console.log({ modelAssoc });
        modelObj[ modelAssoc.source ][ modelAssoc.association ](modelObj[ modelAssoc.target ], modelAssoc.options);
      });

      db.sync(dboptions.options.sync || {
        // alter: true,
      })
        .then(_connection => _connection.authenticate())
        .then(() => {
          // console.log({ _connection });
          models.forEach(model => {
            // console.log('model.data', model.data);
            const CoreConfigDataAdapter = this.core.data.create(Object.assign({
              adapter: 'sql',
              model: model.name || model.data,
              db_connection: db,
              docid: '_id',
            }, model.coreDataOptions));
            if (dboptions.controller) Object.keys(dboptions.controller).forEach(controller_name => assignControllers.call(this, { dboptions, controller_name, modelName: model.name, CoreConfigDataAdapter, periodic_db_name, }));

            this.datas.set(`${periodic_db_name}_${model.name}`, CoreConfigDataAdapter);
            this.models.set(`${periodic_db_name}_${model.name}`, model.modelModule);
          });
          this.dbs.set(periodic_db_name, db);
          resolve(true);
        })
        .catch(e => {
          reject(e);
        });
    }

    function getContentDBModelDir(periodic_db_name) {
      return path.join(this.config.app_root, `content/config/databases/${periodic_db_name}/models`);
    }

    function getExtensionDBModelDir(options) {
      const { periodic_db_name, db_ext_name, } = options;
      return path.join(this.config.app_root, `node_modules/${db_ext_name}/config/databases/${periodic_db_name}/models`);
    }

    function getContainerDBModelDir(options) {
      const { periodic_db_name, db_container_name, db_container_type, } = options;
      if (db_container_type === 'local') {
        return path.join(this.config.app_root, `content/container/${db_container_name}/config/databases/${periodic_db_name}/models`);
      } else {
        return path.join(this.config.app_root, `node_modules/${db_container_name}/config/databases/${periodic_db_name}/models`);
      }
    }

    function getDBModelDir(options) {
      const { db_config_type, periodic_db_name, db_ext_name, db_container_name, db_container_type, } = options;
      // console.log('getDBModelDir',{ options });
      switch (db_config_type) {
      case 'container':
        return getContainerDBModelDir.call(this, { db_container_name, db_container_type, periodic_db_name, });
      case 'extension':
        return getExtensionDBModelDir.call(this, { db_ext_name, periodic_db_name, });
      case 'content':
      default:
        return getContentDBModelDir.call(this, periodic_db_name);
      }
    }

    function connectLowkieDB(options) {
      return new Promise((resolve, reject) => {
        try {
          const { periodic_db_name, db_config_type, } = options;
          const dboptions = options.options;
          const __LOWKIE_DB_PATH = (dboptions.use_absolute_dbpath) ? dboptions.dbpath : path.resolve(this.config.app_root, dboptions.dbpath);
          lowkie.connect(__LOWKIE_DB_PATH, dboptions.dboptions, {}, periodic_db_name)
            .then((db) => {
              let modelDirPath = getDBModelDir.call(this, {
                db_ext_name: options.extension,
                db_container_name: options.container,
                db_container_type: options.container_type,
                db_config_type,
                periodic_db_name,
              });
              fs.readdir(modelDirPath)
                .then(modelFiles => assignLowkieModels.call(this, {
                  modelFiles,
                  modelDirPath,
                  periodic_db_name,
                  db,
                  resolve,
                  reject,
                  dboptions: options,
                }))
                .catch(reject);
            })
            .catch(reject);
          lowkie.connection.on('connectionError', (e) => reject);
        } catch (e) {
          reject(e);
        }
      });
    }

    function connectMongooseDB(options) {
      return new Promise((resolve, reject) => {
        try {
          const { periodic_db_name, db_config_type, } = options;
          const dboptions = options.options;
          dboptions.mongoose_options = Object.assign({ config: { autoIndex: true, }, }, dboptions.mongoose_options);
          const mongooseDB = mongoose.createConnection(dboptions.url, dboptions.mongoose_options);
          mongooseDB.on('connected', () => {
            let modelDirPath = getDBModelDir.call(this, {
              db_ext_name: options.extension,
              db_container_name: options.container,
              db_container_type: options.container_type,
              db_config_type,
              periodic_db_name,
            });
            fs.readdir(modelDirPath)
              .then(modelFiles => assignMongooseModels.call(this, {
                modelFiles,
                modelDirPath,
                periodic_db_name,
                db: mongooseDB,
                resolve,
                reject,
                dboptions: options,
              }))
              .catch(reject);
          });
          mongooseDB.on('error', reject);
        } catch (e) {
          reject(e);
        }
      });
    }

    function connectSequelizeDB(options) {
      return new Promise((resolve, reject) => {
        try {
          const { periodic_db_name, db_config_type, } = options;
          const dboptions = options.options;
          dboptions.connection_options = Object.assign({}, { logging: this.config.debug, }, dboptions.connection_options);
          const sequelizeDB = new Sequelize(dboptions.database, dboptions.username, dboptions.password, dboptions.connection_options);
          const modelDirPath = getDBModelDir.call(this, {
            db_ext_name: options.extension,
            db_container_name: options.container,
            db_container_type: options.container_type,
            db_config_type,
            periodic_db_name,
          });
          fs.readdir(modelDirPath)
            .then(modelFiles => assignSequelizeModels.call(this, {
              modelFiles,
              modelDirPath,
              periodic_db_name,
              db: sequelizeDB,
              dboptions: options,
              resolve,
              reject,
            }))
            .catch(reject);
        } catch (e) {
          reject(e);
        }
      });
    }

    function connectDB(dbsettings) {
      return new Promise((resolve, reject) => {
        try {
          switch (dbsettings.db) {
          case 'lowkie':
            resolve(connectLowkieDB.call(this, dbsettings));
            break;
          case 'mongoose':
            resolve(connectMongooseDB.call(this, dbsettings));
            break;
          case 'sequelize':
            resolve(connectSequelizeDB.call(this, dbsettings));
            break;
          default:
            resolve(true);
            break;
          }
        } catch (e) {
          reject(e);
        }
      });
    }

    function loadDatabases() {
      return new Promise((resolve, reject) => {
        try {
          const databases = Object.keys(this.settings.databases).map(db => {
            return Object.assign({
              periodic_db_name: db,
              db_config_type: 'content',
            },
            this.settings.databases[ db ]);
          }) || [];
          const extension_databases = Object.keys(this.resources.databases.extensions).map(db => {
            return Object.assign({
              periodic_db_name: db,
              db_config_type: 'extension',
            },
            this.resources.databases.extensions[ db ]);
          }) || [];
          const container_databases = Object.keys(this.resources.databases.container).map(db => {
            return Object.assign({
              periodic_db_name: db,
              db_config_type: 'container',
            },
            this.resources.databases.container[ db ]);
          }) || [];
          databases.push(...extension_databases);
          databases.push(...container_databases);
          const connectSettingsDB = connectDB.bind(this);
          if (databases.length) {
            resolve(Promisie.each(databases, 5, connectSettingsDB));
          } else {
            resolve(true);
          }
        } catch (e) {
          reject(e);
        }
      });
    }

    function filterRequiredDependencies(extDep) {
      return extDep.optional !== true;
    }

    function mapForExtensionDependencyName(extDep) {
      return extDep.extname;
    }

    function checkForRequiredExtensions(options) {
      const { reqExt, errors, ext, } = options;
      if (this.extensions.has(reqExt) !== true) {
        errors.push(Error(`Extension (${ext.name}), requires Extension ${reqExt}`));
      }
    }

    function checkExtensionDependencies(options) {
      const { errors, ext, } = options;
      const requiredExtDependencies = (ext.periodic_dependencies && ext.periodic_dependencies.length) ?
        ext.periodic_dependencies.filter(filterRequiredDependencies).map(mapForExtensionDependencyName) : [];

      requiredExtDependencies.forEach(reqExt => checkForRequiredExtensions.call(this, { errors, ext, reqExt, }));

      // console.log(ext.name, { requiredExtDependencies }, requiredExtDependencies.size);
      if (semver.lt(this.settings.application.version, ext.periodic_compatibility)) {
        errors.push(Error(`Extension (${ext.name}), requires Periodic ${ext.periodic_compatibility}, and is not compabitility with ${this.settings.application.version}`));
      } else {
        // console.log('adding ext.name', ext.name);
        this.extensions.set(ext.name, ext);
      }
    }

    function loadExtensions() {
      return new Promise((resolve, reject) => {
        try {
          const errors = [];
          this.crud.ext.list()
            .then(extensions => {
              // const extensionNames = new Set(extensions.map(ext => ext.name));
              // console.log({ extensionNames });
              this.extensions = new Map();
              extensions.forEach(ext => checkExtensionDependencies.call(this, { errors, ext, }));
              if (errors.length) {
                errors.forEach(error => this.logger.error(error));
              }
              if (this.settings.application.exit_on_invalid_extensions && errors.length) {
                reject(new Error('Invalid extension configuration'));
              } else {
                resolve(true);
              }
            })
            .catch(reject);
        } catch (e) {
          reject(e);
        }
      });
    }

    function loadExternalRouters() {
      return new Promise((resolve, reject) => {
        try {
          if (this.config.process.skip_external_resources) return resolve(true);
          Array.from(this.extensions.values()).forEach(extension => {
            try {
              this.routers.set(`extension_${extension.name}`, {
                type: 'extension',
                router: require(`${extension.name}/routers/index`),
              });
            } catch (e) {
              if (this.config.debug) {
                this.logger.warn(`${extension.name} is missing a default router`, e);
              }
            }
          });
          if (this.settings.container && this.settings.container.name) {
            try {
              this.routers.set(`container_${this.settings.container.name}`, {
                type: 'container',
                router: (this.settings.container.type === 'local') ?
                  require(`${this.config.app_root}/content/container/${this.settings.container.name}/routers/index`) : require(`${this.settings.container.name}/routers/index`),
              });
            } catch (e) {
              if (this.config.debug) {
                this.logger.warn(`${this.settings.container.name} is missing a default router`, e);
              }
            }
          }
          resolve(true);
        } catch (e) {
          reject(e);
        }
      });
    }

    function setupGenericCoreController() {
      return new Promise((resolve, reject) => {
        try {
          const StandardCoreController = new CoreControllerModule(this, {
            compatibility: false,
            responder_configuration: { adapter: 'html', },
            // skip_responder: true,
            skip_db: true,
            // skip_protocol: true,
          });
          // StandardCoreController.initialize_responder({ adapter:'html'});
          // StandardCoreController.initialize_protocol({});
          this.core.controller = StandardCoreController;
          resolve(true);
        } catch (e) {
          reject(e);
        }
      });
    }

    function setupGenericCoreMailer() {
      return new Promise((resolve, reject) => {
        try {
          const StandardCoreMailer = new CoreMailerModule({
            config: {
              transportConfig: this.settings.core.mailer.transport_config,
            },
          });
          // StandardCoreMailer.initialize_responder({ adapter:'html'});
          // StandardCoreMailer.initialize_protocol({});
          this.core.mailer = StandardCoreMailer;
          resolve(true);
        } catch (e) {
          reject(e);
        }
      });
    }

    module.exports = {
      configureLowkie,
      configureMongoose,
      configureSequelize,
      loadConfiguration,
      loadAppSettings,
      loadExtensions,
      loadDatabases,
      loadExternalRouters,
      connectDB,
      connectLowkieDB,
      connectSequelizeDB,
      connectMongooseDB,
      getDBModelDir,
      assignSequelizeModels,
      assignMongooseModels,
      assignLowkieModels,
      getContentDBModelDir,
      getExtensionDBModelDir,
      getContainerDBModelDir,
      checkExtensionDependencies,
      checkForRequiredExtensions,
      mapForExtensionDependencyName,
      filterRequiredDependencies,
      assignControllers,
      setupGenericCoreController,
      setupGenericCoreMailer,
    };