text: /* jslint node: true */
  'use strict';
  
  //  ENiGMA½
  const Config    = require('./config.js').get;
  const Log       = require('./logger.js').log;
  const {
      Errors,
      ErrorReasons
  }               = require('./enig_error.js');
  
  //  deps
  const fs        = require('graceful-fs');
  const paths     = require('path');
  const _         = require('lodash');
  const assert    = require('assert');
  const async     = require('async');
  const glob      = require('glob');
  
  //  exports
  exports.loadModuleEx            = loadModuleEx;
  exports.loadModule              = loadModule;
  exports.loadModulesForCategory  = loadModulesForCategory;
  exports.getModulePaths          = getModulePaths;
  exports.initializeModules       = initializeModules;
  
  function loadModuleEx(options, cb) {
      assert(_.isObject(options));
      assert(_.isString(options.name));
      assert(_.isString(options.path));
  
      const modConfig = _.isObject(Config[options.category]) ? Config[options.category][options.name] : null;
  
      if(_.isObject(modConfig) && false === modConfig.enabled) {
          return cb(Errors.AccessDenied(`Module "${options.name}" is disabled`, ErrorReasons.Disabled));
      }
  
      //
      //  Modules are allowed to live in /path/to/<moduleName>/<moduleName>.js or
      //  simply in /path/to/<moduleName>.js. This allows for more advanced modules
      //  to have their own containing folder, package.json & dependencies, etc.
      //
      let mod;
      let modPath = paths.join(options.path, `${options.name}.js`);   //  general case first
      try {
          mod = require(modPath);
      } catch(e) {
          if('MODULE_NOT_FOUND' === e.code) {
              modPath = paths.join(options.path, options.name, `${options.name}.js`);
              try {
                  mod = require(modPath);
              } catch(e) {
                  return cb(e);
              }
          } else {
              return cb(e);
          }
      }
  
      if(!_.isObject(mod.moduleInfo)) {
          return cb(Errors.Invalid(`No exported "moduleInfo" block for module ${modPath}!`));
      }
  
      if(!_.isFunction(mod.getModule)) {
          return cb(Errors.Invalid(`No exported "getModule" method for module ${modPath}!`));
      }
  
      return cb(null, mod);
  }
  
  function loadModule(name, category, cb) {
      const path = Config().paths[category];
  
      if(!_.isString(path)) {
          return cb(Errors.DoesNotExist(`Not sure where to look for module "${name}" of category "${category}"`));
      }
  
      loadModuleEx( { name : name, path : path, category : category }, function loaded(err, mod) {
          return cb(err, mod);
      });
  }
  
  function loadModulesForCategory(category, iterator, complete) {
  
      fs.readdir(Config().paths[category], (err, files) => {
          if(err) {
              return iterator(err);
          }
  
          const jsModules = files.filter(file => {
              return '.js' === paths.extname(file);
          });
  
          async.each(jsModules, (file, next) => {
              loadModule(paths.basename(file, '.js'), category, (err, mod) => {
                  if(err) {
                      if(ErrorReasons.Disabled === err.reasonCode) {
                          Log.debug(err.message);
                      } else {
                          Log.info( { err : err }, 'Failed loading module');
                      }
                      return next(null);  //  continue no matter what
                  }
                  return iterator(mod, next);
              });
          }, err => {
              if(complete) {
                  return complete(err);
              }
          });
      });
  }
  
  function getModulePaths() {
      const config = Config();
      return [
          config.paths.mods,
          config.paths.loginServers,
          config.paths.contentServers,
          config.paths.chatServers,
          config.paths.scannerTossers,
      ];
  }
  
  function initializeModules(cb) {
      const Events = require('./events.js');
  
      const modulePaths = getModulePaths().concat(__dirname);
  
      async.each(modulePaths, (modulePath, nextPath) => {
          glob('*{.js,/*.js}', { cwd : modulePath }, (err, files) => {
              if(err) {
                  return nextPath(err);
              }
  
              const ourPath = paths.join(__dirname, __filename);
  
              async.each(files, (moduleName, nextModule) => {
                  const fullModulePath = paths.join(modulePath, moduleName);
                  if(ourPath === fullModulePath) {
                      return nextModule(null);
                  }
  
                  try {
                      const mod = require(fullModulePath);
  
                      if(_.isFunction(mod.moduleInitialize)) {
                          const initInfo = {
                              events : Events,
                          };
  
                          mod.moduleInitialize(initInfo, err => {
                              if(err) {
                                  Log.warn( { error : err.message, modulePath : fullModulePath }, 'Error during "moduleInitialize"');
                              }
                              return nextModule(null);
                          });
                      } else {
                          return nextModule(null);
                      }
                  } catch(e) {
                      Log.warn( { error : e.message, fullModulePath }, 'Exception during "moduleInitialize"');
                      return nextModule(null);
                  }
              },
              err => {
                  return nextPath(err);
              });
          });
      },
      err => {
          return cb(err);
      });
  }
  