modules/core/main.js

!function CoreModuleWrapper() {
  let core; const modules = {interfaces: {}}; const globals = {}; const fs = require('fs');
  let log = function() {}; let config; let pkg = {}; const basePaths = {};
  const quickDelete = require('./base/delete.js');

  /**
   * Display Console Banner
   *
   * @private
   * @memberof Server.Modules.Core
   * @name displayConsoleBanner
   * @ignore
   * @function
   * @param {Server.Modules.Core} displayCore - Core Object
   *
   * @description
   * This is an internal method that is only available from within the Core module. It renders a welcome
   * banner to the console's stdout early on in server startup.
   *
   * @example
   * // Scope: Being executed from within Core Module Internals
   * displayConsoleBanner();
   * // Output: The console banner with the IndustrySwarm logo is rendered in to the console
   */
  const displayConsoleBanner = function CoreDisplayConsoleBanner(displayCore) {
    let alertLineOne = ''; let alertLineTwo = '';
    const currentDate = new Date(); const fullYear = currentDate.getFullYear();
    if (displayCore.status === 'Error') {
      alertLineOne = 'Error Initialising:'; alertLineTwo = displayCore.reason;
    }
    console.log(`\n\n\n
================================================================================================

                    *                   
              ,%%      ,%#              
          &%    /%%%%%%#    %%          
      %%    #%%%%%%%%%%%%%(     #%,     
  %%     %%%%%%%%%%%%%%%%           /%  
 %        %%%%%%%%%%%%%%%             %        IndustrySwarm
 %       #%%%%%%%%%%%%%%%%            %        Blackrock Application Server
 %  ,%% #%%%%%%%%%%%%%%%,*%%%     %&  %        Copyright ` + fullYear + `, Darren Smith.
 %  ,%%% %%%%%%%%%%%%# %%%%%%%%%%%%&  % 
 %  ,%%%%.%%%%%%%%# %%%%%%%%%%%%%%%&  %        Server Name:
 %  ,%%%%    (%% %%%%%%%%%%%%%%%%%%&  %        ` + pkg.name + ` v` + pkg.version + `
 %  ,%%         %%%%%%%%%%%%%%%%%%%&  % 
 ##  %           %%%%%%%%%%%%%%%%%%  .%        ` + alertLineOne + `
   %%           %%%%%%%%%%%%%%%    &%          ` + alertLineTwo + `
       %%     %%%%%%%%%%%%.    %%       
           .%#    %%%%    ,%(           
                %%    %%                 

------------------------------------------------------------------------------------------------\n
  `);
  };


  /**
   * Calculate Base Path Set
   *
   * @private
   * @memberof Server.Modules.Core
   * @name calculateBasePathSet
   * @ignore
   * @function
   *
   * @description
   * This is an internal method that is only available from within the Core module. It calculates the initial set of
   * base paths that allow the system to orient itself as to where it's config resides as well as the folders that
   * hold the Apps and the Cache.
   *
   * @example
   * // Scope: Being executed from within Core Module Internals
   * calculateBasePathSet();
   * console.log('Base Path Set', basePaths);
   * // Output: 'Base Path Set , { apps: '/path/to/apps', config: '/path/to/config', cache: '/path/to/cache' }
   */
  const calculateBasePathSet = function CoreCalcBasePathSet() {
    let dirName = __dirname; dirName = dirName.split('/'); dirName.pop(); dirName.pop();
    basePaths.module = dirName.join('/'); dirName = basePaths.module.split('/'); dirName.pop(); dirName.pop(); dirName = dirName.join('/');
    basePaths.apps = ''; basePaths.config = ''; basePaths.cache = ''; basePaths.root = basePaths.module;
    if (fs.existsSync(dirName + '/apps')) {
      basePaths.apps = dirName + '/apps';
      basePaths.root = dirName;
    }
    if (fs.existsSync(dirName + '/config/is-blackrock.json')) {
      basePaths.config = dirName + '/config/is-blackrock.json';
    }
    if (fs.existsSync(dirName + '/cache')) {
      basePaths.cache = dirName + '/cache';
    }
  };
  calculateBasePathSet();

  /**
   * Import Base Class Into Module
   *
   * @private
   * @memberof Server.Modules.Core
   * @name loadBaseClass
   * @ignore
   * @function
   * @return {Server.Modules.Core.Base} Base - Base Class
   *
   * @description
   * This is an internal method that is only available from within the Core module. This method imports the Base Class,
   * from which all other first-party classes within the system are derived. The base classes most primitive structure
   * is the Augment Class Inheritance Library. On top of this we have added a fully featured Event Emitter object and
   * attached a couple of libraries, including - RxJS (The Reactive Extensions) with a few custom operators and support
   * methods, and the Day.JS Date/Time Management Library.
   *
   * @example
   * // Scope: Being executed from within Core Module Internals
   * const Base = loadBase();
   * console.log('Base Class', Base);
   * // Output: 'Base Class , Base()'
   */
  const loadBase = function CoreLoadBase() {
    let Base;
    try {
      Base = require('./base/index.js');
    } catch (err) {
      console.log(err);
      let currentDate = new Date();
      currentDate = currentDate.toISOString();
      console.log(currentDate +
        '(fatal) Core > Missing Critical System File (./base/index.js) - Terminating');
      process.exit();
    }
    return Base;
  };
  const Base = loadBase();


  /**
   * Setup External Module Methods (Log)
   *
   * @private
   * @memberof Server.Modules.Core
   * @name setupExternalModuleMethods
   * @ignore
   * @function
   * @param {Server.Modules.Core} coreObj - Core Object
   *
   * @description
   * This is an internal method that is only accessible from within the Core module itself. It creates a faux
   * log method that checks whether the Logger Module is loaded every time it receives a log event, and if so -
   * proxies that log event through to the log method on the Logger Module. This is done so that we can ensure that
   * a log method exists as early on in application server startup as possible, simply disposing of log messages that
   * it receives before the Logger Module becomes available.
   *
   * @example
   * // Scope: Being executed from within Core Module Internals
   * console.log('Log Function', log);  // Output: Log Function, undefined
   * setupExternalModuleMethods(core);  // Run this method
   * console.log('Log Function', log);  // Output: Log Function, Function...
   */
  const setupExternalModuleMethods = function CoreSetupExtModMethods(coreObj) {
    log = function CoreLog(level, logMsg, attrObj, evtName) {
      // noinspection JSUnresolvedFunction
      const logger = coreObj.module('logger');
      if (logger && logger.log) logger.log(level, logMsg, attrObj, evtName);
    };
  };


  /**
   * Blackrock Core Module
   *
   * @public
   * @class Server.Modules.Core
   * @augments Server.Modules.Core.Base
   * @author Darren Smith
   * @copyright Copyright (c) 2021 Darren Smith
   * @license Licensed under the LGPL license.
   *
   * @description This is the Core Module of the Blackrock Application Server.
   * It is responsible for loading all remaining modules and interfaces within Blackrock.
   * This module exports the core object (an instantiated version of the Core class
   * contained within) to the parent Server namespace. You do not need to instantiate this
   * module's class (by calling the constructor), as this is done automatically for you.
   *
   * @example
   * // Get Core Object (Calling Blackrock As A Dependency):
   * require('is-blackrock').init()
   *   .then(function(core) {
   *     const log = core.module('logger).log;
   *     log('debug', 'My App - The Status of Core is ' + core.status, {}, 'CORE_STATUS');
   *     // Output: 'The Status of Core is Active' (if server has finished initialising)
   *   });
   *
   * @example
   * // Get Core Object (From App Controller):
   * const ctrl = function() {};
   * ctrl.get = function(req, res) {
   *   const log = req.core.module('logger).log;
   *   log('debug', 'My App - The Status of Core is ' + core.status, {}, 'CORE_STATUS');
   *   // Output: 'The Status of Core is Active' (if server has finished initialising)
   * }
   */
  let corePrototype = {
    constructor: function CoreModule() {
      if(core) return;
      this.status = 'Inactive';
      this.reason = '';
      this.stopActivation = true;
    },

    /**
     * Void Method
     *
     * @public
     * @memberof Server.Modules.Core
     * @name void
     * @function
     *
     * @description
     * This method does not do anything. It exists in order to provide a work-around for when some method must be
     * called on the core application server instance, but you do not actually need the method to do anything or to
     * return a result.
     *
     * @example
     * // Voiding (Calling Blackrock As A Dependency):
     * require('is-blackrock').init()
     *   .then(function(core) {
     *     core.void();
     *     // Output: Does Nothing. I mean... What did you expect?
     *   });
     *
     * @example
     * // Voiding (From App Controller):
     * const ctrl = function() {};
     * ctrl.get = function(req, res) {
     *   req.core.void();
     *   // Output: Yep. Still does nothing,
     * }
     */
    void: function CoreModuleVoid() {},

    /**
     * Initialise Core Module
     *
     * @public
     * @memberof Server.Modules.Core
     * @name init
     * @function
     *
     * @description
     * This method initialises the application server. It is called automatically when you are running the server
     * in stand-alone mode. If you are running the server as a dependency for another application, then you must
     * call this method on the object returned by require in order to start the server. The server will remain in
     * an inactive state up until you call this method.
     *
     * @param {object} initialConfig - Initial Configuration Data
     * @param {object} initialConfig.silent - True disables console logging (when embedding in your own app), Else False
     * @param {object} initialConfig.test - True enables test mode, Else False
     * @param {object} initialConfig.config - Configuration Block. See {@tutorial server-configuration}
     * @param {function} callbackFn - The Callback Function
     * @return {Server.Modules.Core} core - The Core Module
     *
     * @example
     * // Initialise the Application Server (Not Available to App Controllers):
     * require('is-blackrock').init()
     *   .then(function(core) {
     *     console.log(core.status);
     *     // Output: 'Active' (if server has finished initialising)
     *   });
     */
    init: function CoreInit(initialConfig, callbackFn) {
      const self = this;
      // eslint-disable-next-line no-undef
      const myPromise = new Promise(function CoreInitPromise(resolve, reject) {
        const resolvePromise = function CoreInitResolvePromise() {
          if (callbackFn && typeof callbackFn === 'function') callbackFn(self);
          if (self.status === 'Error') reject(self.reason);
          else if (self.status === 'Active') resolve(self);
          setTimeout(function() {
            const welcomeMsg = {
              'welcome': 'Blackrock Application Server',
              'name': pkg.name,
              'version': pkg.version,
              'status': self.status,
              'reason': self.reason,
            };
            core.emit('log', welcomeMsg);
            core.emit('Blackrock Core', welcomeMsg);
            core.emit('CORE_WELCOME', welcomeMsg);
          }, 10);
        };
        if (self.status !== 'Inactive') {
          if (callbackFn && typeof callbackFn === 'function') callbackFn(self);
          const interval = setInterval(function CoreInitPromiseInterval() {
            if (self.status === 'Error') {
              clearInterval(interval);
              reject(self.reason);
            } else if (self.status === 'Active') {
              clearInterval(interval);
              resolve(self);
            }
          }, 10);
          return;
        }
        self.status = 'Preboot'; pkg.name = 'Blackrock'; pkg.version = '0.00';
        // noinspection JSUnresolvedVariable
        if (initialConfig && initialConfig.silent) self.globals.set('silent', 'true');
        // noinspection JSUnresolvedVariable
        if (initialConfig && initialConfig.test) self.globals.set('test', 'true');
        if (!initialConfig || !initialConfig.config) {
          try {
            config = require(basePaths.config);
          } catch (err) {
            self.status = 'Error';
            self.reason = 'No config provided';
          }
        }
        if (!initialConfig && !config) {
          try {
            config = require(basePaths.module + '/../../config/config.json');     // Legacy Config Filename
          } catch (err) {
            self.status = 'Error';
            self.reason = 'No config provided';
          }
        } else if (initialConfig && initialConfig.config) config = initialConfig.config;
        // noinspection JSUnresolvedVariable
        if (!initialConfig || !initialConfig.package) {
          try {
            pkg = require(basePaths.root + '/package.json');
          } catch (err) {
            self.status = 'Error';
            self.reason = 'No package provided';
          }
        } else if (initialConfig.package) pkg = initialConfig.package;
        if (!config) {
          if (fs.existsSync('/etc/is-blackrock.json')) {
            self.status = 'Preboot'; self.reason = '';
            basePaths.config = '/etc/is-blackrock.json';
            try {
              config = require('/etc/is-blackrock.json');
            } catch(err) {
              self.status = 'Error'; self.reason += 'Malformed JSON in the Config File';
            }
          } else {
            config = require(basePaths.module + '/_definitions/config-templates/is-blackrock-default.json');
            basePaths.config = '/_definitions/config-templates/is-blackrock-default.json';
            const canWrite = function CanWrite(path, callback) {
              fs.access(path, fs.W_OK, function(err) {
                callback(null, !err);
              });
            };
            canWrite('/etc', function(err, isWritable) {
              if (!isWritable) {
                console.log('(error) No config file available, Please run again with sudo to copy a default across\n');
              } else {
                fs.createReadStream(
                    basePaths.module + '/_definitions/config-templates/is-blackrock-default.json'
                        .pipe(fs.createWriteStream('/etc/is-blackrock.json')));
              }
            });
          }
        }
        if (config && (!basePaths.apps || !basePaths.cache)) {
          if (config && config.core && config.core.locations &&
            config.core.locations.apps && fs.existsSync(config.core.locations.apps)) {
            basePaths.apps = config.core.locations.apps;
          }
          if (config && config.core && config.core.locations &&
            config.core.locations.cache && fs.existsSync(config.core.locations.cache)) {
            basePaths.cache = config.core.locations.cache;
          }
        }
        if (!basePaths || !basePaths.module || !basePaths.root ||
          !basePaths.apps || !basePaths.cache || !basePaths.config) {
          if(!self.reason) self.status = 'Error'; self.reason = 'Could not establish valid base paths';
        }
        if (config && !config.core) {
          if(!self.reason) self.status = 'Error'; self.reason = 'No core module section in config provided';
        }
        if (config && config.core && !config.core.modules) {
          if(!self.reason) self.status = 'Error'; self.reason = 'No modules listed in config';
        }
        // noinspection JSUnresolvedVariable
        if (config && config.core && !config.core.startupModules) {
          if(!self.reason) self.status = 'Error'; self.reason = 'No startup modules listed in config';
        }
        if (config && config.core && !config.core.timeouts) {
          if(!self.reason) self.status = 'Error'; self.reason = 'No timeouts listed in config';
        }
        // noinspection JSUnresolvedVariable
        if (config && config.core && config.core.timeouts && !config.core.timeouts.loadDependencies) {
          if(!self.reason) self.status = 'Error'; self.reason = 'No loadDependencies timeout listed in config';
        }
        // noinspection JSUnresolvedVariable
        if (config && config.core && config.core.timeouts && !config.core.timeouts.closeModules) {
          if(!self.reason) self.status = 'Error'; self.reason = 'No closeModules timeout listed in config';
        }
        modules.core = self;
        // noinspection JSUnresolvedVariable
        if ((initialConfig && !initialConfig.silent) || !initialConfig) displayConsoleBanner(self);
        if (self.status !== 'Preboot' || self.status === 'Error') {
          resolvePromise(); return;
        }
        self.status = 'Starting';
        setupExternalModuleMethods(self);
        // noinspection JSUnresolvedVariable
        if (config && config.core && config.core.startupModules && config.core.startupModules.length > 0) {
          for (let i = 0; i < config.core.startupModules.length; i++) {
            self.module.load('module', config.core.startupModules[i]);
          }
        } else {
          self.shutdown('No Startup Modules Defined'); return;
        }
        self.on('CORE_LOAD_DEPENDENCIES', function CoreLoadDepCb() {
          if (process.stdin.isTTY) {
            const stdin = process.openStdin();
            stdin.setRawMode(true);
            stdin.setEncoding('utf8');
            stdin.on('data', function CoreLoadDepCbStdInDataIn(chunk) {
              if (chunk === 'e') self.shutdown('User Terminated Server');
            });
          }
          const fs = require('fs');
          fs.readdirSync(basePaths.module + '/modules').forEach(function CoreLoadModsReadDirCb(file) {
            if (!modules[file]) self.module.load('module', file);
          });
          fs.readdirSync(basePaths.module + '/interfaces').forEach(function CoreLoadInterfacesReadDirCb(file) {
            if (!modules.interfaces[file]) self.module.load('interface', file);
          });
          self.status = 'Finalising';
          core.emit('CORE_FINALISING');
        });
        let counter = 0; let timeout;
        // noinspection JSUnresolvedVariable
        if (config.core.timeouts.loadDependencies) timeout = config.core.timeouts.loadDependencies;
        else timeout = 5000;
        const interval = setInterval(function CoreDepLoadIntervalCb() {
          if (self.status === 'Finalising') {
            clearInterval(interval); self.startEventLoop(function CoreReqToStartEvtLoop() {
              resolvePromise();
            });
          }
          if (counter >= timeout) {
            log('error',
                'Core > Timed out initiating startup. Terminating application server.',
                {}, 'CORE_STARTUP_TIMEOUT');
            clearInterval(interval);
            self.shutdown('Timed Out Loading Dependencies');
          }
          counter += 50;
        }, 50);
        core.emit('CORE_LOAD_DEPENDENCIES');
      });
      if (!callbackFn) return myPromise;
      else {
        myPromise.then(function CoreInitCallPromiseThen(pRes) {}).catch(function CoreInitCallPromiseCatch(pErr) {});
        return self;
      }
    },

    /**
     * Return Configuration
     *
     * @public
     * @function cfg
     * @memberof Server.Modules.Core
     * @property {function} cfg.update - Updates the Configuration
     * @return {object} config - The Config Object. See {@tutorial server-configuration}
     *
     * @description
     * This method can be accessed when calling Blackrock As A Dependency, and also from within the app
     * controllers (if enabled in config: config['app-engine'].allow.cfg = true). It returns the application server's
     * current configuration state. This state, of course, is originally read from the config file on the filesystem
     * when the application server starts up. And can also be passed through or modified via the Init Object
     * (the first parameter that you pass to Blackrock's Init Method.
     *
     * @example
     * // Return Application Server Configuration Object (Calling Blackrock As A Dependency):
     * const initObject = { config: amendedConfigData}
     * require('is-blackrock').init(initObject)
     *   .then(function(core) {
     *     const log = req.core.module('logger').log;
     *     const config = core.cfg();
     *     log('debug', 'My App > Message - ' + config.core.banner, {}, 'MY_APP_SENDING_CONFIG_BANNER');
     *     // Output: 'Blackrock Application Server (Default)' (In Console)
     *   });
     *
     * @example
     * // Updates the live configuration of the application server:
     * require('is-blackrock').init()
     *   .then(function(core) {
     *     const result = core.cfg.update({'test': 'value'});
     *     if(result) console.log('Updated');
     *     else console.log('Update Failed');
     *   });
     */
    cfg: function CoreGetCfg() {
      return config;
    },

    /**
     * Get Package JSON File
     *
     * @public
     * @memberof Server.Modules.Core
     * @name pkg
     * @function
     * @return {object} pkg - The Parsed Package File
     *
     * @description
     * This method returns a Javascript object that contains the parsed content of the package.json file. If the
     * server is running in stand-alone mode, then this will be the package.json file of the actual is-blackrock
     * module. Otherwise, it will be the package.json file of the host application that is accessing Blackrock
     * as a dependency.
     *
     * @example
     * // Return Application Server's Parsed Package.json file (calling a dependency):
     * require('is-blackrock').init()
     *   .then(function(core) {
     *     const log = core.module('logger).log
     *     const pkg = core.pkg();
     *     log('debug', 'My App > What's in the package? It's a - ' + pkg.name, {}, 'MY_APP_THE_PACKAGE');
     *     // Output: "Blackrock Application Server"
     *   });
     *
     * @example
     * // Return Application Server's Parsed Package.json file (From App Controller)
     * const ctrl = function() {};
     * ctrl.get = function(req, res) {
     *   const log = req,core.module('logger).log
     *   const pkg = req.core.pkg();
     *   log('debug', 'My App > What's in the package? It's a - ' + pkg.name, {}, 'MY_APP_THEIR_PACKAGE');
     *   // Output: "Blackrock Application Server". But take note - you must enable this within the server
     *   // config (config['app-engine].allow.pkg = true).
     * }
     */
    pkg: function CoreGetPkg() {
      return pkg;
    },

    /**
     * Exit Process (Undocumented)
     *
     * @public
     * @memberof Server.Modules.Core
     * @name exitProcess
     * @ignore
     * @function
     *
     * @description
     * This is an internal method that is only accessible within the Blackrock Module / Application Server Internals.
     * It renders 'Shutdown Complete' to the logs and immediately terminates the application server process. It does
     * not gracefully close anything down. It really must not be used. Instead, use core.shutdown()
     *
     * @example
     * // Scope: Being executed from within Core Module Internals
     * exitProcess();
     * // Output in Logs: '20201201 00:00:00 (shutdown) Shutdown Complete' (and process immediately terminates)
     */
    exitProcess: function CoreExitProcess() {
      const currentDate = new Date().toISOString();
      if (!core.globals.get('silent')) console.log(currentDate + ' (shutdown) Core > Shutdown Complete');
      process.exit();
    },

    /**
     * Fetch Base Path
     *
     * @public
     * @memberof Server.Modules.Core
     * @name fetchBasePath
     * @function
     * @param {string} type - The Base Path Type
     * @return {string} basePath - The Requested Base Path
     *
     * @description
     * This method can be used when accessing core where Blackrock is a dependency of your own project, and can also
     * be used from within a app controller (where enabled in config). It fetches whichever base path type you
     * specify by name when calling it. Options include - 'apps', 'module', 'config', 'cache', 'root'.
     *
     * @example
     * // Return Application Server Configuration Object (Calling Blackrock As A Dependency):
     * require('is-blackrock').init()
     *   .then(function(core) {
     *     const log = core.module('logger).log;
     *     const appBasePath = core.fetchBasePath('apps');
     *     log('debug', 'My App > App Base Path Is ' + appBasePath, {}, 'MY_APP_BASE_PATH');
     *     // Output: 'App Base Path Is /home/alice/my-local-swarm/apps' (In Console)
     *   });
     *
     * @example
     * // Return Application Server Configuration Object (From App Controller. In - Say, a Stand-Alone Setup):
     * const ctrl = function() {};
     * ctrl.get = function(req, res) {
     *   const log = req.core.module('logger).log;
     *   const appBasePath = req.core.fetchBasePath('apps');
     *   log('debug', 'My App > App Base Path Is ' + appBasePath, {}, 'MY_APP_BASE_PATH');
     *   // Output: 'App Base Path Is /opt/blackrock/apps' (In Console, Stand-Alone Application Server Mode)
     * }
     */
    fetchBasePath: function CoreFetchBasePath(type) {
      if (basePaths[type]) return basePaths[type];
      else return '';
    },

    /**
     * Globals Object
     *
     * @public
     * @memberof Server.Modules.Core
     * @property {function} globals.set - Sets global variable value
     * @property {function} globals.get - Gets global variable value
     *
     * @description
     * This object contains methods to get and set methods that are globally accessible across the
     * Application Server. It does NOT need to be instantiated and can/should not be called directly.
     * You can read more about the get method here - {@link Server.Modules.Core#globals.get}.
     * And the set method here - {@link Server.Modules.Core#globals.set}
     *
     * @example
     * // Set a global variable:
     * core.globals.set('test', 'value');     // Returns: true
     *
     * @example
     * // Get a global variable:
     * core.globals.get('test');              // Returns: 'value'
     */
    globals: {

      /**
       * Set Global Property
       *
       * @public
       * @alias globals.set
       * @memberof! Server.Modules.Core#
       * @function globals.set
       * @param {string} name - Global Property Name
       * @param {string|object|array} [value] - Global Property Value - Will clear current key if left out
       * @return {boolean} result - Set Result
       *
       * @description
       * This method allows you to set a global property. It must be enabled within the server configuration, in
       * "config['apps'].allow" to be accessed from within the context of an app. These properties (when set) are
       * shared across all apps running on the server. It is a global key-value store.
       *
       * @example
       * // For either 'Blackrock as a Dependency or from a App Controller:
       * console.log(core.globals.set('ping', 'pong!'));     // Returns: true
       */
      set: function CoreSetGlobal(name, value) {
        if (!name) return false;
        globals[name] = value; return true;
      },

      /**
       * Get Global Property
       *
       * @public
       * @alias globals.get
       * @memberof! Server.Modules.Core#
       * @function globals.get
       * @param {string} name - Global Property Name
       * @return {string|object|array} value - Global Property Value
       *
       * @description
       * This method allows you to get a global property. It must be enabled within the server configuration, in
       * "config['app-engine'].allow" to be accessed from within the context of an app. These properties (when set)
       * are shared across all apps running on the server. It is a global key-value store.
       *
       * @example
       * // For either 'Blackrock as a Dependency or from a App Controller:
       * console.log(core.globals.get('ping'));     // Returns: pong!
       */
      get: function CoreGetGlobal(name) {
        if (!globals[name]) return '';
        return globals[name];
      },
    },

    /**
     * Get Module Singleton
     *
     * @public
     * @memberof Server.Modules.Core
     * @function module
     * @property {function} module.count - Returns number of loaded modules and/or interfaces
     * @property {function} module.load - Loads a module and instantiates it's Singleton
     * @property {function} module.isLoaded - Waits until module loads and then fulfills a Promise
     * @property {function} module.closeAll - Closes all active modules
     * @param {string} name - Module Name
     * @param {string} [type] - Module Type (Leave blank for standard module, or enter 'interface' for an interface
     * @return {Server.Modules.Core.Module} module - The Requested Module
     *
     * @description
     * This method (when provided with a module name), returns the singleton for that module to the caller.
     * Module singletons are instantiated at server startup - with this being configurable within the server config.
     * Note: To use this method from an app controller, you must enable the modules you will have access to
     * within the server config, along with the method names within.
     * Eg; config['app-engine'].allow.modules[moduleName] = methodName   (See {@tutorial server-configuration})
     *
     * @example
     * // module() - Return the Module Singleton:
     * const logger = core.module('logger');
     * logger.log('debug', 'This is a test message');
     * // Output: 'This is a test message' will appear in the logs (incl. console if enabled)
     *
     * @example
     * // module.count() - Return a Count of Loaded Modules:
     * const moduleCount = core.module.count('modules');
     * log('debug', 'My App > How many modules are loaded? It's - ' + moduleCount, {}, 'MY_APP_COUNT_DEM_MODS');
     * // Output: 10 (or however many modules have been loaded)
     *
     * @example
     * // module.load() - Loads a module:
     * core.module.load('module', 'logger', function(err, loggerModule){
     *     loggerModule.log('debug', 'My App > Hello!', {}, 'LOADED_MOD');
     *     // Output: "My App > Hello!"
     * });
     *
     * @example
     *  // module.isLoaded() - Waits until module is loaded and then fulfills a Promise:
     *  core.module.isLoaded('cli').then(function IsLoadedPromiseThen(cliMod) {
     *    cliMod.register([
     *      {'cmd': 'drop-acid', 'params': 'mindset=happy', 'info': 'Takes you on a wild trip', 'fn': function (params) {
     *        core.emit('OH_SHIT_HE_DID_IT', {'command': 'start-trip', 'params': params});
     *      }},
     *    ]);
     *  }).catch(function IsLoadedPromiseFail(err) {
     *    log('error',
     *        'My App > He Failed. He's now in the psych ward',
     *        err, 'DROP_ACID_FAIL');
     *  });
     *
     * @example
     * // module.closeAll() - Scope is that it's being executed from within Core Module Internals
     * core.module.closeAll();
     * // Output in Logs: '20201201 00:00:00 (shutdown) All Modules Have Been Terminated'
     */
    module: function CoreGetMod(name, type) {
      if (type && type === 'interface') {
        if (modules.interfaces[name]) {
          return modules.interfaces[name];
        }
      } else if (name !== 'interface') {
        if (modules[name]) {
          return modules[name];
        }
      }
    },

    /**
     * Is Server Ready?
     *
     * @memberof Server.Modules.Core
     * @name ready
     * @function
     * @param {function} callbackFn - Callback Function
     * @return {Promise} promise - A Promise
     *
     * @description
     * This method waits until the server becomes Active and then executes upon a Promise, providing the Core
     * Module / Application Server Singleton as the sole parameter within the Promise's Then function. If the server
     * is already active it will immediately resolve the promise.
     *
     * @example
     * // Check if server is ready (Only required when calling Blackrock as a dependency):
     * require('is-blackrock').init()
     *   .then(function(core) {
     *     core.ready().then(function(newCore) {
     *       const log = newCore.module('logger).log
     *       log('debug', 'My App > Let's get ready to rumble!!!', {}, 'MY_APP_RUMBLING_ALL_OVER_THE_SHOP');
     *       // Output: "My App > Let's get ready to rumble!!!" (In Console)
     *     });
     *   });
     *
     */
    ready: function CoreReady(callbackFn) {
      const self = this;
      // eslint-disable-next-line no-undef
      const myPromise = new Promise(function CoreReadyPromiseCb(resolve, reject) {
        const timeout = 1000; let timer = 0;
        const interval = setInterval(function CoreReadyPromiseInterval() {
          if (self.status === 'Active') {
            clearInterval(interval);
            if (callbackFn && typeof callbackFn === 'function') callbackFn(null, self);
            resolve(self);
          }
          if (timer >= timeout) {
            clearInterval(interval);
            if (callbackFn && typeof callbackFn === 'function') callbackFn(self, null);
            reject(self.reason);
          }
          timer += 10;
        }, 10);
      });
      if (!callbackFn) {
        return myPromise;
      } else {
        myPromise.then(function CoreReadyPromiseThen(pRes) {}).catch(function CoreReadyPromiseCatch(pErr) {});
      }
    },

    /**
     * Shutdown Server
     *
     * @memberof Server.Modules.Core
     * @function shutdown
     * @param {string} shutdownMsg - Shutdown Message
     * @function
     *
     * @description
     * This method will trigger a system shutdown. It is always available from a parent application
     * (where Blackrock has been included as a dependency). It is only available from app code
     * (initialisation method or route handlers) if it is enabled within within the server configuration file:
     * config['app-engine'].allow.shutdown = true
     *
     * @example
     * // Shutdown the server:
     * require('is-blackrock').init()
     *   .then(function(core) {
     *     core.shutdown();
     *     // Note: The server will begin shutting down now...
     *   });
     */
    shutdown: function CoreShutdown(shutdownMsg) {
      const self = this;
      if (self.status === 'Shutting Down' || self.status === 'Terminated') return;
      console.log('Blackrock Core > Initiating System Shutdown (' + shutdownMsg + ').');
      self.status = 'Shutting Down';
      self.module.closeAll(function CoreCloseModsCb() {
        self.exitProcess();
      });
    },

    /**
     * Start Event Loop
     *
     * @memberof Server.Modules.Core
     * @name startEventLoop
     * @private
     * @ignore
     * @function
     * @param {function} cb - Callback Function
     * @return {boolean} result - Result (True | False)
     * @todo Add a confirmation screen if a console user clicks the 'e' key before triggering a shutdown
     *
     * @description
     * This method is internal to the Core Module and can only be executed from within it. It initiates the
     * Application Server Event Loop (effectively preventing the Server process from terminating until it is
     * explicitly shutdown. When Blackrock is being accessed from the command-line as a blocking process, you
     * can trigger the shutdown of the Application Server by clicking the letter 'e' on your keyboard.
     *
     * NOTE: There is no confirmation or warning - the server will immediately begin to shutdown and you will not
     * be able to prevent it from doing so. Keep note of this. Ideally, run the server on a VM or in Docker where you're
     * not dealing directly with the shell anyway - or as a system daemon - See the Daemon Module.
     *
     * @example
     * core.startEventLoop(function(err, res) {
     *   if(!err) console.log('Event Loop Started');
     * });
     */
    startEventLoop: function CoreStartEvtLoop(cb) {
      const self = this;
      if (self.status === 'Shutting Down' || self.status === 'Terminated' || self.status === 'Active') {
        if (cb)  cb({'error': 'Invalid State', 'state': self.status}, null);
      } else {
        setTimeout(function CoreStartLoopTimeout() {
          // noinspection JSIncompatibleTypesComparison
          if (!self.status === 'Shutting Down' && !self.status === 'Terminated' && !self.status === 'Active') {
            log('startup',
                'Blackrock Core > System Loaded, Event Loop Executing. Press \'e\' key to shutdown.',
                {}, 'CORE_SYSTEM_LOADED');
          }
        }, 1000);
        if (!self.stopActivation) self.status = 'Active';
        if (cb)  cb(null, {'success': true});
        setInterval(function CoreStartLoopInterval() {}, 1000);
      }
      return true;
    },

    /**
     * Get Core Proxy Instance
     *
     * @memberof Server.Modules.Core
     * @name getCoreProxy
     * @function
     *
     * @param {object} myConfig - Configuration Object
     * @param {boolean} [myConfig.cfg] - Make the cfg() method available? True | False
     * @param {boolean} [myConfig.pkg] - Make the pkg() method available? True | False
     * @param {boolean} [myConfig.fetchBasePath] - Make the fetchBasePath() method available? True | False
     * @param {boolean} [myConfig.shutdown] - Make the shutdown() method available? True | False
     * @param {boolean} [myConfig.globals] - Make the Core Globals Singleton available? True | False
     * @param {object} myConfig.modules - An object of modules to make available
     * @param {array} [myConfig.modules.cli] - An array of method names to enable for the CLI Module
     * @param {array} [myConfig.modules.daemon] - An array of method names to enable for the Daemon Module
     * @param {array} [myConfig.modules.errorhandler] - An array of method names to enable for the ErrorHandler Module
     * @param {array} [myConfig.modules.farm] - An array of method names to enable for the Farm Module
     * @param {array} [myConfig.modules.generator] - An array of method names to enable for the Generator Module
     * @param {array} [myConfig.modules.i18n] - An array of method names to enable for the i18n Module
     * @param {array} [myConfig.modules.installer] - An array of method names to enable for the Installer Module
     * @param {array} [myConfig.modules.jobs] - An array of method names to enable for the Jobs Module
     * @param {array} [myConfig.modules.logger] - An array of method names to enable for the Logger Module
     * @param {array} [myConfig.modules.router] - An array of method names to enable for the Router Module
     * @param {array} [myConfig.modules.sandbox] - An array of method names to enable for the Sandbox Module
     * @param {array} [myConfig.modules.utilities] - An array of method names to enable for the Utilities Module
     * @param {array} [myConfig.modules.validate] - An array of method names to enable for the Validate Module
     * @param {array} [myConfig.modules.http] - An array of method names to enable for the HTTP Interface
     * @param {array} [myConfig.modules.websockets] - An array of method names to enable for the WebSockets Module
     * @param {array} [myConfig.modules.axon] - An array of method names to enable for the Axon Module
     * @param {array} [myConfig.modules.nanomsg] - An array of method names to enable for the NanoMSG Module
     * @param {array} [myConfig.modules.ssh] - An array of method names to enable for the SSH Module
     * @param {array} [myConfig.modules.zeromq] - An array of method names to enable for the ZeroMQ Module
     * @param {Server.Modules.AppEngine.App} app - App Instance
     * @return {Server.Modules.Core.CoreProxy} coreProxyInstance - Core Proxy Instance
     *
     * @description
     * This method provides an instance of the CoreProxy Class that acts as a proxy between the Core Module Singleton
     * and app code, to ensure that the app only has limited access to the methods contained within the Core Module
     * This includes limited access to modules that are accessible via the child module() method and apps.
     * The method names defines within the myConfig[module] array can take one of three forms - a plain method name
     * (eg; "log"), a nested method name (eg; "csv.parse") or a method name with a constraint placed on the input
     * parameter (eg; "app(appName)").
     *
     * @example
     * const config = core.cfg();
     * const coreProxyInstance = core.getCoreProxy(config['app-engine'].allow);
     */
    getCoreProxy: function CoreGetCoreProxy(myConfig, app) {
      // noinspection JSValidateTypes,JSUnfilteredForInLoop
      /**
       * Blackrock Core Proxy Class
       *
       * @class Server.Modules.Core.CoreProxy
       * @augments Server.Modules.Core.Base
       *
       * @description
       * This is the Core Proxy Class. An instance of this class can be retrieved by calling
       * core.getCoreProxy() - passing in a Configuration Object describing what subset of the full Core
       * will be present within the CoreProxy, and the app Object for the app that the CoreProxy
       * will be provisioned to.
       *
       * @example
       * const config = core.cfg();
       * const coreProxyInstance = core.getCoreProxy(config['app-engine'].allow);
       */
      const CoreProxy = new Base().extend({
        constructor: function CoreProxy(cfg) {
          const self = this;

          /**
           * Optional Base Class
           *
           * @memberof Server.Modules.Core.CoreProxy
           * @name Base
           * @type {Server.Modules.Core.Base}
           */
          if (cfg.Base) self.Base = core.Base;

          /**
           * Optional Globals Singleton
           *
           * @memberof Server.Modules.Core.CoreProxy
           * @name globals
           * @type {Server.Modules.Core.globals}
           */
          if (cfg.globals) self.globals = core.globals;

          /**
           * Optional Shutdown() Method
           *
           * @memberof Server.Modules.Core.CoreProxy
           * @name shutdown
           * @function
           */
          if (cfg.shutdown) self.shutdown = core.shutdown;

          /**
           * Optional Cfg() Method
           *
           * @memberof Server.Modules.Core.CoreProxy
           * @name cfg
           * @function
           */
          if (cfg.cfg) self.cfg = core.cfg;

          /**
           * Optional Pkg() Method
           *
           * @memberof Server.Modules.Core.CoreProxy
           * @name pkg
           * @function
           */
          if (cfg.pkg) self.pkg = core.pkg;

          /**
           * Optional fetchBasePath() Method
           *
           * @memberof Server.Modules.Core.CoreProxy
           * @name fetchBasePath
           * @function
           */
          if (cfg.fetchBasePath) self.fetchBasePath = core.fetchBasePath;

          self.module()

          return self;
        },

        /**
         * Get Module Proxy
         *
         * @memberof Server.Modules.Core.CoreProxy
         * @name module
         * @function
         * @param {string} name - Module Name
         * @param {string} [myInterface=interface] - Set to 'interface' if requesting Interface, Else leave out
         * @return {Server.Modules.Core.Module|object} moduleProxyInstance - A proxy instance providing restricted access to the module
         *
         * @description
         * This method returns a Proxy Instance that provides configurable access to a module's methods.
         *
         * @example
         * const config = core.cfg();
         * const coreProxyInstance = core.getCoreProxy(config['app-engine'].allow);
         * const loggerModuleProxy = coreProxyInstance.module('logger');
         */
        module: function CoreProxyGetModule(name, myInterface) {
          let mods; let util = core.module('utilities');
          if (util.prop(config, 'app-engine.allow.modules')) mods = config['app-engine'].allow.modules;
          else mods = {};
          if (!mods) return {};
          const methods = mods[name]; const loadedMethods = {}; const fnNames = {};
          if (!methods) return {};
          for (let i = 0; i < methods.length; i++) {
            if (core.module(name, myInterface) && core.module(name, myInterface)[methods[i]]) {
              loadedMethods[methods[i]] = core.module(name, myInterface)[methods[i]];
            } else if (core.module(name, myInterface) && methods[i].includes('.')) {
              const methodSplit = methods[i].split('.');
              util.assign(loadedMethods, methodSplit, util.prop(core.module(name, myInterface), methods[i]));
            } else if (core.module(name, myInterface) && methods[i].includes('(')) {
              const splitOne = methods[i].split('('); const splitTwo = splitOne[1].split(')');
              const methodName = splitOne[0]; const fnName = splitTwo[0];
              loadedMethods[methodName] = util.prop(core.module(name, myInterface), methods[i]);
              fnNames[methodName] = fnName;
            }
          }
          if (loadedMethods && !fnNames) return loadedMethods;
          else {
            const filteredMethods = loadedMethods;
            for (const methodName in loadedMethods) {
              if (fnNames[methodName] === 'appName') {
                const newApp = {}; const myApp = core.module(name, myInterface)[methodName](app);
                // eslint-disable-next-line guard-for-in
                for (const subMethod in myApp) {
                  // noinspection JSUnfilteredForInLoop
                  newApp[subMethod] = myApp[subMethod];
                }
                filteredMethods[methodName] = function CoreProxyModuleMethodHandler(appName) {
                  if (util.prop(config, 'app-engine.runtime.apps.allowExternalAppAccess') && appName) {
                    return core.module('app-engine').app(appName);
                  } else return newApp;
                };
              } else filteredMethods[methodName] = loadedMethods[methodName];
            }
            return filteredMethods;
          }
        },
      });
      return new CoreProxy(myConfig);
    }
  };

  /**
   * Get Module Count
   *
   * @public
   * @memberof Server.Modules.Core
   * @function module.count
   * @param {string} type - Module Type
   * @return {number} count - The Module Count
   *
   * @description
   * This is a simple method that tells you the number of modules or interfaces that are currently loaded.
   *
   * @example
   * // Return a Count of Loaded Modules (Calling a Dependency)
   * require('is-blackrock').init()
   *   .then(function(core) {
   *     const log = core.module('logger).log
   *     const moduleCount = core.module.count('modules');
   *     log('debug', 'My App > How many modules are loaded? It's - ' + moduleCount, {}, 'MY_APP_COUNT_DEM_MODS');
   *     // Output: 10 (or however many modules have been loaded)
   *   });
   *
   * @example
   * // Return the Module Singleton (From App Controller)
   * const ctrl = function() {};
   * ctrl.get = function(req, res) {
   *   const log = req,core.module('logger).log
   *   const moduleCount = req.core.module.count('modules');
   *   log('debug', 'My App > How many modules are loaded? It's - ' + moduleCount, {}, 'MY_APP_COUNT_DEM_MODS');
   *   // Output: 10 (or however many modules have been loaded). But take note - you must enable this within the server
   *   // config (config.appengine.allow.moduleCount = true).
   * }
   */
  corePrototype.module.count = function CoreCountMods(type) {
    if (type && type === 'interfaces') {
      return Object.keys(modules.interfaces).length;
    } else if (type && type === 'modules') {
      return Object.keys(modules).length - 1;
    } else {
      return 0;
    }
  };

  /**
   * Load Module
   *
   * @public
   * @memberof Server.Modules.Core
   * @function module.load
   * @param {string} type - Module Type
   * @param {string} moduleName - Module Name
   * @param {function} [cb] - Callback Function
   * @return {object} output - Method Response
   *
   * @description
   * This method can only be called when accessing Blackrock as a Dependency or from within the Application Server
   * internals. It's sole purpose is to load the module that is specified into memory and to notify the callback
   * function when this has been completed - passing it the singleton for the module that was just loaded.
   *
   * @example
   * core.module.load('module', 'logger', function(err, loggerModule){
   *     loggerModule.log('debug', 'My App > Hello!', {}, 'LOADED_MOD');
   *     // Output: "My App > Hello!"
   * });
   */
  corePrototype.module.load = function CoreLoadMod(type, moduleName, cb) {
    if (core.status === 'Shutting Down' || core.status === 'Terminated') {
      return;
    }
    if (type === 'module' && config.core.modules && config.core.modules.length > 0 &&
        !config.core.modules.includes(moduleName)) {
      return;
    }
    if (type === 'interface' && config.core.interfaces && config.core.interfaces.length > 0 &&
        !config.core.interfaces.includes(moduleName)) {
      return;
    }
    if (moduleName.startsWith('.'))  return;
    try {
      if (type === 'module') {
        modules[moduleName] = require(basePaths.module + '/' + type + 's/' + moduleName + '/main.js')(core);
      } else if (type === 'interface') {
        modules.interfaces[moduleName] = require(
            basePaths.module + '/' + type + 's/' + moduleName + '/main.js')(core);
      }
    } catch (err) {
      const error = {success: false, message: 'Error Loading \'' +
            moduleName + '\' Module (Type: ' + type + ')', error: err};
      log('debug', 'Core > ' + error.message, error.error, 'CORE_ERR_LOADING_MOD');
      if (cb) cb(error, null);
      return error;
    }
    const output = {success: true, message: '\'' + moduleName + '\' Module (Type: ' + type + ') Loaded Successfully'};
    if (type === 'module') output.module = modules[moduleName];
    if (type === 'interface') output.module = modules.interfaces[moduleName];
    log('debug', 'Core > ' + output.message, {}, 'CORE_MOD_LOADED');
    if (cb) cb(null, output);
    return output;
  };

  /**
   * Is Module Loaded?
   *
   * @public
   * @memberof Server.Modules.Core
   * @function module.isLoaded
   * @param {string} moduleName - Module Name
   * @param {function} [callbackFn] - Callback Function
   * @return {Promise} promise - Promise to Return
   * @todo Allow timeouts to be sourced from config for core.isLoaded
   *
   * @description
   * Calls callbackFn immediately, passing it the module singleton defined by moduleName where moduleName references a valid
   * module. Or if it cannot find the module, it waits until the module becomes available and calls callbackFn at that
   * point in time (with the requested module singleton).
   *
   * @example
   *  // Valid for when you're calling Blackrock as a dependency, or even as an app controller...
   *  core.module.isLoaded('cli').then(function IsLoadedPromiseThen(cliMod) {
   *    cliMod.register([
   *      {'cmd': 'drop-acid', 'params': 'mindset=happy', 'info': 'Takes you on a wild trip', 'fn': function (params) {
   *        core.emit('OH_SHIT_HE_DID_IT', {'command': 'start-trip', 'params': params});
   *      }},
   *    ]);
   *  }).catch(function IsLoadedPromiseFail(err) {
   *    log('error',
   *        'My App > He Failed. He's now in the psych ward',
   *        err, 'DROP_ACID_FAIL');
   *  });
   */
  corePrototype.module.isLoaded = function CoreIsModLoaded(moduleName, callbackFn) {
    // eslint-disable-next-line no-undef
    const myPromise = new Promise(function CoreIsModLoadedPromise(resolve, reject) {
      if (core.module(moduleName)) {
        if (callbackFn) callbackFn(null, core.module(moduleName));
        resolve(core.module(moduleName));
        return;
      }
      const timeout = 2000; let timer = 0;
      const interval = setInterval(function() {
        if (core.module(moduleName)) {
          clearInterval(interval);
          if (callbackFn) callbackFn(null, core.module(moduleName));
          resolve(core.module(moduleName));
        } else if (timer >= timeout) {
          clearInterval(interval);
          if (callbackFn) callbackFn({'error': 'Timed Out Checking That ' + moduleName + ' Was Loaded'}, null);
          // eslint-disable-next-line prefer-promise-reject-errors
          reject('Timed Out Checking That ' + moduleName + ' Was Loaded');
        }
        timer += 1;
      }, 1);
    });
    if (!callbackFn) return myPromise;
    else myPromise.then(function CoreIsModLoadedPromiseThen(pRes) {}).catch(function CoreIsModLoadedPromiseCatch(pErr) {});
  };

  /**
   * Close Modules (Undocumented)
   *
   * @public
   * @memberof Server.Modules.Core
   * @function module.closeAll
   * @ignore
   * @param {function} cb - Callback Function
   *
   * @description
   * This method can only be accessed when accessing Blackrock as a dependency or from within the application server
   * internals (through modules and interfaces). When you call this method, it immediately triggers a graceful closure
   * of all modules and interfaces. This is one of the first steps that is called when you call core.shutdown()
   *
   * @example
   * // Scope: Being executed from within Core Module Internals
   * core.module.closeAll();
   * // Output in Logs: '20201201 00:00:00 (shutdown) All Modules Have Been Terminated'
   */
  corePrototype.module.closeAll = function CoreCloseMods(cb) {
    log('shutdown', 'Core > Attempting to Close All Open Modules.', {}, 'CORE_CLOSING_MODULES');
    let modCount = 0; let stdModCount = 0; let interfaceModCount = 0; let counter = 0; let timeoutTimer = 0;
    let timeout;
    // noinspection JSUnresolvedVariable
    if (config.core.timeouts.closeModules) timeout = config.core.timeouts.closeModules;
    else timeout = 2000;
    stdModCount += Object.keys(modules).length;
    stdModCount = stdModCount - 1;
    interfaceModCount += Object.keys(modules.interfaces).length;
    modCount = stdModCount + interfaceModCount;
    const interval = setInterval(function CoreCloseModsIntervalCb() {
      if (counter >= (modCount - 1)) {
        log('shutdown',
            'Core > Modules All Closed Successfully ('+counter+'/'+(modCount - 1)+').',
            {module: 'core'}, 'CORE_MODS_CLOSED');
        clearInterval(interval);
        cb(null, {success: true, message: 'Modules All Closed Successfully'});
        return;
      }
      if (timeoutTimer > timeout) {
        log('shutdown',
            'Blackrock Core > Module Closure Timed Out ('+counter+'/'+(modCount - 1)+' Closed Successfully).',
            {module: 'core'}, 'CORE_TIMEOUT_CLOSING_MODS');
        clearInterval(interval);
        cb({message: 'Module Shutdown Timed Out'}, null);
        return;
      }
      timeoutTimer += 500;
    }, 500);
    process.nextTick(function CoreCloseModsNextTickCb() {
      core.on('module-shut-down', function CoreCloseModsOnModShutdownCb() {
        counter ++;
      });
      core.emit('shutdown', 'All Modules Have Been Terminated');
    });
  };

  /**
   * Update Server Configuration
   *
   * @memberof Server.Modules.Core
   * @function cfg.update
   * @param {object} cfg - Configuration Object
   * @return {boolean} result - Result of Configuration Update
   *
   * @description
   * This method updates the server's live Configuration Object with a new set of data, replacing the data that
   * existed prior to the update within the live Configuration Object. It can only be called from modules within
   * the Application Server, or where Blackrock is being used as a dependency.
   *
   * @example
   * // Updates the live configuration of the application server:
   * require('is-blackrock').init()
   *   .then(function(core) {
   *     const result = core.cfg.update({'test': 'value'});
   *     if(result) console.log('Updated');
   *     else console.log('Update Failed');
   *   });
   */
  corePrototype.cfg.update = function CoreUpdateCfg(cfg) {
    if (!cfg) return false;
    config = cfg;
    return true;
  };

  // noinspection JSValidateTypeså
  const Core = new Base().extend(corePrototype);


  /**
   * @class Server.Modules.Core.Module
   * @augments Server.Modules.Core
   * @param {string} name - The name of the module being instantiated
   *
   * @description
   * This class provides a base class for all modules to inherit from. It contains common methods that all
   * modules inherit. It is only accessible from within the Application Server, or where Blackrock is accessed
   * as a Dependency.
   *
   * @example
   * const superMod = new core.Mod('SuperMod');
   */
  const Mod = new Core().extend({

    /**
     * Module Constructor
     *
     * @memberof Server.Modules.Core.Module
     * @private
     * @ignore
     * @function
     * @param {string} name - Name of Module
     *
     * @description
     * This is the constructor for the Module Class. It takes one parameter - the module name.
     *
     * @example
     * const superMod = new core.Mod('SuperMod');
     */
    constructor: function Module(name) {
      const self = this;
      if (name) {
        self.name = name;
        self.uber.on('shutdown', function ModuleOnShutdownCb() {
          self.unload();
        });
      }
    },

    /**
     * Unload Module
     *
     * @memberof Server.Modules.Core.Module
     * @name unload
     * @private
     * @ignore
     * @function
     *
     * @description
     * This method (when called) will unload the module - causing it to 'self destruct'.
     *
     * @example
     * const superMod = new core.Mod('SuperMod');
     * superMod.unload();
     */
    unload: function ModuleUnload() {
      const self = this;
      log('debug', self.name + ' Module > Module Unloaded', {}, 'CORE_MOD_UNLOADED');
      self.uber.emit('module-shut-down', self.name);
      quickDelete(self);
    },

  });


  /**
   * @class Server.Modules.Core.Interface
   * @augments Server.Modules.Core.Module
   * @param {string} name - The name of the interface being instantiated
   *
   * @description
   * This class provides a base class for all interfaces to inherit from. It contains common methods that all
   * interfaces inherit. It is only accessible from within the Application Server, or where Blackrock is accessed
   * as a Dependency.
   *
   * Interfaces allow Blackrock to "talk" in the language of difference communications protocols. Amongst these are
   * HTTP(S), WebSockets, Axon, NanoMSG, SSH and ZeroMQ. This gives you the freedom to design the best service
   * architecture for your business - whether that be a single monolith, or a collection of microservices - which
   * you may own all of, or may own a few - perhaps subscribing to cloud versions of some of these services (Identity
   * would be a good example).
   *
   * @example
   * const superInterface = new core.Mod('SuperInterface');
   */
  const Interface = new Mod().extend({

    /**
     * Interface Constructor
     *
     * @memberof Server.Modules.Core.Interface
     * @private
     * @ignore
     * @function
     * @param {string} name - Name of Module
     *
     * @description
     * This is the constructor for the Interface Class. It takes one parameter - the interface name.
     *
     * @example
     * const superInterface = new core.Mod('SuperInterface');
     */
    constructor: function Interface(name) {
      const self = this;
      self.name = name;
      self.uber.uber.on('shutdown', function InterfaceShutdownCb() {
        self.unload();
      });
    },

    /**
     * Instance Repository
     * @ignore
     */
    instances: {},

    /**
     * Start Interface Instances
     *
     * @memberof Server.Modules.Core.Interface
     * @name startInstances
     * @private
     * @ignore
     * @function
     *
     * @description
     * This method starts an interface's instances. It is an internal method that is normally only called from
     * within the Interface itself. The reason why we might want multiple instances of an interface active is
     * because Blackrock gives you the capability the launch more than one instance - where each listens to a
     * different port. This opens up all of the ports to you to use. Combined with the highly configurable Router
     * Module, you are able to route as many interface instances as you would like, listening on as many different
     * ports as you would like - with some being routed to the same app (like an HTTP + an SSH instance) and others
     * being routed to completely different apps running on the one Blackrock node.
     *
     * You could even drop a reverse proxy like haProxy (which has a great web UI when installed into the pfSense
     * firewall) in front of the Blackrock Server node and route different domains or even paths to the different
     * ports that are open, each pointing to a completely different app.
     *
     * @example
     * superInterface.startInstances();
     */
    startInstances: function InterfaceStartInstances() {
      const self = this;
      process.nextTick(function InterfaceStartInstancesNextTickCb() {
        const myName = self.name.toLowerCase();
        if (!core.cfg().interfaces || !core.cfg().interfaces[myName]) {
          log('debug',
              self.name + ' Interface > No interfaces defined in System Configuration File.',
              {name: self.name}, 'INTERFACE_NONE_DEFINED'); return;
        }
        if (!core.cfg().router || !core.cfg().router.instances) {
          log('error',
              self.name + ' Interface > Cannot start interface instances as there are no routers defined.',
              {name: self.name}, 'INTERFACE_NO_ROUTERS');
          return;
        }
        // eslint-disable-next-line guard-for-in
        for (const intfc in core.cfg().interfaces[myName]) {
          // noinspection JSUnfilteredForInLoop
          const cfg = core.cfg().interfaces[myName][intfc];
          // noinspection JSUnfilteredForInLoop
          if (self.instances[intfc]) {
            log('error',
                self.name +
                ' Interface > Attempting to load an interface instance that has already been loaded (' + intfc + ').',
                {name: self.name}, 'INTERFACE_INSTANCE_ALREADY_LOADED');
          } else if (!cfg.enabled || cfg.enabled !== true) {
            log('warning',
                self.name +
                // eslint-disable-next-line max-len
                ' Interface Module > Attempting to load an interface instance that is not enabled in the system configuration (' + intfc + ').',
                {name: self.name}, 'INTERFACE_INSTANCE_NOT_DEFINED');
          } else {
            // noinspection JSUnfilteredForInLoop
            self.startInstance(intfc);
          }
        }
      });
    },

    /**
     * Get List of Interface Instances
     *
     * @memberof Server.Modules.Core.Interface
     * @name list
     * @function
     * @return {array} instances - A List of Instances
     *
     * @description
     * This method returns a list of available instances for an interface.
     *
     * @example
     * // Returns a list of interface instances:
     * require('is-blackrock').init()
     *   .then(function(core) {
     *     const httpInterface = core.module('http', 'interface');
     *     const instanceList = httpInterface.list();
     *     console.log(instanceList)
     *     // Output: ['instance1', 'instance2', 'instance3']
     *   });
     */
    list: function InterfaceListInstances() {
      const self = this;
      return Object.keys(self.instances);
    },

    /**
     * Get Interface Instance
     *
     * @memberof Server.Modules.Core.Interface
     * @name get
     * @function
     * @param {string} name - Instance Name
     * @return {object} instance - The Requested Instance
     *
     * @description
     * This method returns the requested instance from an interface.
     *
     * @example
     * // Returns an instance from an interface:
     * require('is-blackrock').init()
     *   .then(function(core) {
     *     const httpInterface = core.module('http', 'interface');
     *     const instance = httpInterface.get('default');
     *   });
     */
    get: function InterfaceGetInstance(name) {
      const self = this;
      if (!self.instances[name]) return false;
      else return self.instances[name];
    },

    /**
     * Unload Interface Instances
     *
     * @memberof Server.Modules.Core.Interface
     * @name unload
     * @private
     * @ignore
     * @function
     *
     * @description
     * This method calls the other Interface class method - closeInstances(). And then upon the success of this
     * method, it alerts the Application Server that the Interface has shutdown. And then it deletes itself.
     *
     * @example
     * const httpInterface = core.module('http', 'interface');
     * httpInterface.unload();
     * // The interface now shuts down and unloads itself
     */
    unload: function InterfaceUnload() {
      const self = this;
      self.closeInstances(function InterfaceUnloadCloseInstancesCb() {
        log('debug',
            self.name + ' Interface > Closing interface instances... Succeeded.',
            {name: self.name}, 'INTERFACE_INSTANCES_CLOSED');
        self.uber.emit('module-shut-down', self.name);
        quickDelete(self);
      });
    },

    /**
     * Close Interface Instances
     *
     * @memberof Server.Modules.Core.Interface
     * @name closeInstances
     * @private
     * @ignore
     * @function
     * @param {function} cb - Callback Function
     *
     * @description
     * This method forces the interface to close all active instances that are running within it.
     *
     * @example
     * superInterface.closeInstances(function(err, res){
     *     if(res) console.log('All Instances Closed');
     * })
     */
    closeInstances: function InterfaceCloseInstances(cb) {
      let totalInterfaces = 0; let interfacesClosed = 0;
      totalInterfaces += Object.keys(this.instances).length;
      for (const name in this.instances) {
        if (this.instances[name].server && this.instances[name].server.close) {
          this.instances[name].server.close(function InterfaceCloseInstancesCloseCb(err) {
            if (!err) interfacesClosed ++;
          });
        }
      }
      let counter = 0; const timeout = 5000;
      const interval = setInterval(function InterfaceCloseInstancesTimeout() {
        if (interfacesClosed >= totalInterfaces) {
          clearInterval(interval);
          cb(null, {success: true}); return;
        }
        if (counter >= timeout) {
          clearInterval(interval);
          cb({success: false}, null); return;
        }
        counter += 500;
      }, 500);
    },

  });

  /**
   * Core Module Singleton
   *
   * @type Server.Modules.Core
   * @instance
   *
   * @description
   * This is the core module singleton that is exported as the sole variable from the Core Module's main.js file. Where
   * Blackrock is being accessed as a dependency - this is the interface through which the host application will
   * communicate with the Blackrock Application Server - passing instructions and listening to events.
   *
   * @example
   * // This example is from the perspective of a host application:
   * const blackrock = require('is-blackrock');
   * blackrock.init().then(function(core){
   *   console.log('Server Status - ' + core.status);
   * }).catch(function(err){
   *   console.log('ERROR!');
   * });
   */
  core = module.exports = new Core();

  /**
   * Base Class Pointer Within Core Object
   *
   * @type Server.Modules.Core.Base
   */
  core.Base = Base;

  /**
   * Core Class Pointer Within Core Object
   *
   * @type Server.Modules.Core
   */
  core.Core = Core;

  /**
   * Mod Class Pointer Within Core Object
   *
   * @type Server.Modules.Core.Module
   */
  core.Mod = Mod;

  /**
   * Interface Class Pointer Within Core Object
   *
   * @type Server.Modules.Core.Interface
   */
  core.Interface = Interface;

}();