text: var app, rootpath = process.cwd() + '/',
    path = require('path'),
    calipso = require(path.join('..', 'calipso'))
  fs = require('fs');
  
  /**
   * Route all of the modules based on the module event model
   * This replaces an earlier version that only executed modules in
   * parallel via step
   */
  
  function eventRouteModules(req, res, next) {
  
    // Ignore static content
    // TODO : Make this more connect friendly or at least configurable
    // STATIC content may or may not be static. We should route it normally for now.
    //if (req.url.match(/^\/images|^\/js|^\/css|^\/favicon.ico|png$|jpg$|gif$|css$|js$/)) {
    //  return next();
    //}
  
    req.timeStart = new Date();
    var end = res.end;
    res.calipsoEndCalled = false;
    res.end = function () {
      res.calipsoEndCalled = true;
      end.apply(res, arguments);
    }
  
    // Attach our event listener to this request
    attachRequestEvents(req, res);
  
    // Initialise the response re. matches
    // Later used to decide to show 404
    res.routeMatched = false;
  
    // Store our callback here
    req.routeComplete = function (res) {
      if (!res.calipsoEndCalled) {
        next();
      }
    };
  
    // Route 'first' modules that fire before all others
    // These first modules can stop the routing of all others
    doFirstModules(req, res, function (err) {
  
      var iterator = function (module, cb) {
        routeModule(req, res, module, false, false, cb);
      }
  
      calipso.lib.async.map(calipso.lib._.keys(calipso.modules), iterator, function (err, result) {
        // Not important
      })
  
    });
  
  }
  
  /**
   * Attach module event emitters and request event listener
   * to this request instance.
   * This will only last for the context of a current request
   */
  
  function attachRequestEvents(req, res) {
  
    // Create a request event listener for this request, pass in functions
    // to enable testing.
    req.event = new calipso.event.RequestEventListener({
      notifyDependencyFn:notifyDependenciesOfRoute,
      registerDependenciesFn:registerDependencies
    });
  
    //
    var maxListeners = calipso.config.get('server:events:maxListeners');
  
    // Attach all the modules to it
    for (var module in calipso.modules) {
      req.event.registerModule(req, res, module, {maxListeners:maxListeners});
    }
  
  }
  
  /**
   * Helper to register dependent modules that should be checked by a module when
   * routing, the parent module's emitter is passed in.
   */
  function registerDependencies(moduleEmitter, moduleName) {
  
    // Register depends on parent
    if (calipso.modules[moduleName].fn && calipso.modules[moduleName].fn.depends) {
      calipso.modules[moduleName].fn.depends.forEach(function (dependentModule) {
        moduleEmitter.modules[moduleName].check[dependentModule] = false;
      });
    }
  }
  
  /**
   * Route a specific module
   * Called by both the eventRouteModules but also by when dependencies trigger
   * a module to be routed
   *
   * req, res : request/resposne
   * module : the module to route
   * depends : has this route been triggered by an event based on dependencies being met
   * last : final modules, after all others have routed
   *
   */
  
  function routeModule(req, res, moduleName, depends, last, next) {
  
    var module = calipso.modules[moduleName];
  
    // If module is enabled and has no dependencies, or if we are explicitly triggering this via depends
    // Ignore modules that are specified as post route only
    if (module.enabled && (depends || !module.fn.depends) && (last || !module.fn.last) && !module.fn.first) {
  
      // Fire event to start
      req.event.modules[moduleName].route_start();
  
      // Route
      module.fn.route(req, res, module, calipso.app, function (err, moduleName) {
  
        // Gracefully deal with errors
        if (err) {
          res.statusCode = 500;
          calipso.error(err.message);
          res.errorMessage = "Module " + moduleName + ", error: " + err.message + err.stack;
        }
  
        // Expose configuration if module has it
        if (module.fn && module.fn.config) {
          var modulePermit = calipso.permission.Helper.hasPermission("admin:module:configuration");
          res.menu.admin.addMenuItem(req, {
            name:moduleName,
            path:'admin/modules/' + moduleName,
            url:'/admin/modules?module=' + moduleName,
            description:'Manage ' + moduleName + ' settings ...',
            permit:modulePermit
          });
        }
  
        // Finish event
        req.event.modules[moduleName].route_finish();
  
        // Check to see if we have completed routing all modules
        if (!last) {
          checkAllModulesRouted(req, res);
        }
  
        next();
  
      });
  
    } else {
  
      checkAllModulesRouted(req, res);
  
      next();
  
    }
  
  }
  
  /**
   * Check that all enabled modules have been initialised
   * Don't check disabled modules or modules that are setup for postRoute only
   */
  function checkAllModulesRouted(req, res) {
  
    var allRouted = true;
  
    for (var module in req.event.modules) {
      var moduleRouted = (req.event.modules[module].routed || (calipso.modules[module].enabled && (calipso.modules[module].fn.last || calipso.modules[module].fn.first)) || !calipso.modules[module].enabled);
      allRouted = moduleRouted && allRouted;
    }
  
    if (allRouted && !req.event.routeComplete) {
      req.event.routeComplete = true;
      doLastModules(req, res, function () {
        req.timeFinish = new Date();
        req.timeDuration = req.timeFinish - req.timeStart;
        calipso.silly("All modules routed in " + req.timeDuration + " ms");
        doResponse(req, res);
      });
    }
  
  }
  
  
  /**
   * RUn any modules that are defined as first routing modules
   * via first: true, dependencies are ignored for these.
   */
  function doFirstModules(req, res, next) {
  
    // Get all the postRoute modules
    var firstModules = [];
    for (var moduleName in calipso.modules) {
      if (calipso.modules[moduleName].enabled && calipso.modules[moduleName].fn.first) {
        firstModules.push(calipso.modules[moduleName]);
      }
    }
  
  
    if (firstModules.length === 0) {
      return next();
    }
  
    // Execute their routing functions
    calipso.lib.step(
  
      function doFirstModules() {
        var group = this.group();
        firstModules.forEach(function (module) {
          module.fn.route(req, res, module, calipso.app, group());
        });
      }, function done(err) {
  
        // Gracefully deal with errors
        if (err) {
          res.statusCode = 500;
          console.log(err.message);
          res.errorMessage = err.message + err.stack;
        }
  
        next();
  
      });
  
  }
  
  
  /**
   * RUn any modules that are defined as last routing modules
   * via last: true, dependencies are ignored for these atm.
   */
  
  function doLastModules(req, res, next) {
  
    // Get all the postRoute modules
    var lastModules = [];
    for (var moduleName in calipso.modules) {
      if (calipso.modules[moduleName].enabled && calipso.modules[moduleName].fn.last) {
        lastModules.push(calipso.modules[moduleName]);
      }
    }
  
  
    if (lastModules.length === 0) {
      return next();
    }
  
    // Execute their routing functions
    calipso.lib.step(
  
      function doLastModules() {
        var group = this.group();
        lastModules.forEach(function (module) {
          module.fn.route(req, res, module, calipso.app, group());
        });
      }, function done(err) {
  
        // Gracefully deal with errors
        if (err) {
          res.statusCode = 500;
          console.log(err.message);
          res.errorMessage = err.message + err.stack;
        }
  
        next();
  
      });
  
  }
  
  /**
   * Standard response to all modules completing their routing
   */
  
  function doResponse(req, res, next) {
  
    // If we are in install mode, and are not in the installation process, then redirect
    if (!calipso.config.get('installed') && !req.url.match(/^\/admin\/install/)) {
      calipso.silly("Redirecting to admin/install ...");
      calipso.app.doingInstall = true;
      res.redirect("/admin/install");
      return;
    }
  
    // If nothing could be matched ...
    if (!res.routeMatched) {
      calipso.log("No Calipso module routes matched the current URL " + req.url);
      res.statusCode = 404;
    }
  
    // Render statuscodes dealt with by themeing engine
    // TODO - this is not very clean
    calipso.silly("Responding with statusCode: " + res.statusCode);
    if (res.statusCode === 404 || res.statusCode === 500 || res.statusCode === 200 || res.statusCode === 403) {
  
      calipso.theme.render(req, res, function (err, content) {
  
        if (err) {
  
          // Something went wrong at the layout, cannot use layout to render.
          res.statusCode = 500;
          res.send(500, "<html><h2>A fatal error occurred!</h2>" + "<p>" + (err.xMessage ? err.xMessage : err.message) + "</p>" + "<pre>" + err.stack + "</pre></html>");
          req.routeComplete(res);
  
        } else {
  
          res.setHeader('Content-Type', 'text/html');
          // Who am I?
          res.setHeader('X-Powered-By', 'Calipso');
  
          // render
          res.send(content);
  
          // Callback
          req.routeComplete(res);
  
        }
  
      });
  
    } else {
  
      // Otherwise, provided we haven't already issued a redirect, then pass back to Express
      req.routeComplete(res);
  
    }
  
  }
  
  
  /**
   * Initialise the modules currently enabled.
   * This iterates through the modules loaded by loadModules (it places them in an array in the calipso object),
   * and calls the 'init' function exposed by each module (in parallel controlled via step).
   */
  
  function initModules() {
  
    // Reset
    calipso.initComplete = false;
  
    // Create a list of all our enabled modules
    var enabledModules = [];
    for (var module in calipso.modules) {
      if (calipso.modules[module].enabled) {
        enabledModules.push(module);
      }
    }
  
    // Initialise them all
    enabledModules.forEach(function (module) {
      initModule(module, false);
    });
  
  }
  
  /**
   * Init a specific module, called by event listeners re. dependent modules
   */
  
  function initModule(module, depends) {
    var p = path.join(calipso.modules[module].router.modulePath, 'public');
    if ((fs.existsSync || path.existsSync)(p)) {
      var express = require('express');
      calipso.app.use(express["static"](p, {maxAge:86400000}));
    }
    p = path.join(calipso.modules[module].router.modulePath, 'theme-extension.json');
    if ((fs.existsSync || path.existsSync)(p)) {
      var moduleThemeConfig = fs.readFileSync(p, 'utf8');
      calipso.theme.cacheModuleTheme(JSON.parse(moduleThemeConfig), calipso.modules[module].router.modulePath, finish);
      return;
    }
    finish();
    function finish() {
      // If the module has no dependencies, kick start it
      if (depends || !calipso.modules[module].fn.depends) {
  
        // Init start event
        calipso.modules[module].event.init_start();
  
        // Next run any init functions
        calipso.modules[module].fn.init(calipso.modules[module], calipso.app, function (err) {
  
          // Init finish event
          calipso.modules[module].inited = true;
          calipso.modules[module].event.init_finish();
  
          // Now, load any routes to go along with it
          if (calipso.modules[module].fn.routes && calipso.modules[module].fn.routes.length > 0) {
            calipso.lib.async.map(calipso.modules[module].fn.routes, function (options, next) {
              calipso.modules[module].router.addRoute(options, next);
            }, function (err, data) {
              if (err) {
                calipso.error(err);
              }
              checkAllModulesInited();
            });
          } else {
            checkAllModulesInited();
          }
  
        });
  
      }
    }
  }
  
  /**
   * Check that all enabled modules have been initialised
   * If they have been initialised, then call the callback supplied on initialisation
   */
  
  function checkAllModulesInited() {
  
    var allLoaded = true;
    for (var module in calipso.modules) {
      allLoaded = (calipso.modules[module].inited || !calipso.modules[module].enabled) && allLoaded;
    }
  
    if (allLoaded && !calipso.initComplete) {
      calipso.initComplete = true;
      calipso.initCallback();
    }
  
  }
  
  /**
   * Load the modules from the file system, into a 'modules' array
   * that can be managed and iterated.
   *
   * The first level folder is the module type (e.g. core, contrib, ui).
   * It doesn't actually change the processing, but that folder structure is
   * now stored as a property of the module (so makes admin easier).
   *
   * It will take in an options object that holds the configuration parameters
   * for the modules (e.g. if they are enabled or not).
   * If they are switching (e.g. enabled > disabled) it will run the disable hook.
   *
   */
  
  function loadModules(next) {
  
    var configuredModules = calipso.config.get('modules') || {};
  
    // Run any disable hooks
    for (var module in calipso.modules) {
      // Check to see if the module is currently enabled, if we are disabling it.
      if (calipso.modules[module].enabled && configuredModules[module].enabled === false && typeof calipso.modules[module].fn.disable === 'function') {
        calipso.modules[module].fn.disable();
      }
    }
  
    // Clear the modules object (not sure if this is required, but was getting strange errors initially)
    delete calipso.modules; // 'Delete' it.
    calipso.modules = {}; // Always reset it
  
    var moduleBasePath = path.join(rootpath, calipso.config.get('server:modulePath'));
  
    // Read the modules in from the file system, sync is fine as we do it once on load.
    calipso.lib.fs.readdirSync(moduleBasePath).forEach(function (type) {
  
      // Check for all files or folder starting with "." so that we can handle ".svn", ".git" and so on without problems.
  
      if (type != "README" && type[0] != '.') { // Ignore the readme file and .DS_Store file for Macs
        calipso.lib.fs.readdirSync(path.join(moduleBasePath, type)).forEach(function (moduleFolderName) {
  
          if (moduleFolderName != "README" && moduleFolderName[0] != '.') { // Ignore the readme file and .DS_Store file for Macs
  
            var modulePath = path.join(moduleBasePath, type, moduleFolderName);
  
            var module = {
              name:moduleFolderName,
              folder:moduleFolderName,
              library:moduleFolderName,
              type:type,
              path:modulePath,
              enabled:false,
              inited:false
            };
  
            // Add about info to it
            loadAbout(module, modulePath, 'package.json');
  
            // Set the module name to what is in the package.json, default to folder name
            module.name = (module.about && module.about.name) ? module.about.name : moduleFolderName;
  
            // Now set the module
            calipso.modules[module.name] = module;
  
            // Set if it is enabled or not
            module.enabled = configuredModules[module.name] ? configuredModules[module.name].enabled : false;
  
            if (module.enabled) {
  
              // Load the module itself via require
              requireModule(calipso.modules[module.name], modulePath);
  
              // Load the templates (factored out so it can be recalled by watcher)
              loadModuleTemplates(calipso.modules[module.name], path.join(modulePath, 'templates'));
  
            }
  
          }
  
        });
      }
  
    });
  
    // Now that all are loaded, attach events & depends
    attachModuleEventsAndDependencies();
  
    // Save configuration changes (if required)
    if (calipso.config.dirty) {
      calipso.config.save(next);
    } else {
      return next();
    }
  
  }
  
  /**
   * Load data from package.json or theme.json
   */
  
  function loadAbout(obj, fromPath, file) {
  
    var fs = calipso.lib.fs;
  
    var packageFile = calipso.lib.path.join(fromPath, file);
  
    if ((fs.existsSync || path.existsSync)(packageFile)) {
      var json = fs.readFileSync(packageFile);
      try {
        obj.about = JSON.parse(json.toString());
        if (obj.about && obj.about.name) {
          obj.library = obj.about.name;
        } else {
          obj.library = obj.name;
        }
      } catch (ex) {
        obj.about = {
          description:'Invalid ' + file
        };
      }
    }
  
  }
  
  /**
   * Connect up events and dependencies
   * Must come after all modules are loaded
   */
  
  function attachModuleEventsAndDependencies() {
  
    var options = {maxListeners:calipso.config.get('server:events:maxListeners'), notifyDependencyFn:notifyDependenciesOfInit};
  
    for (var module in calipso.modules) {
  
      // Register dependencies
      registerModuleDependencies(calipso.modules[module]);
  
      // Attach event listener
      calipso.event.addModuleEventListener(calipso.modules[module], options);
  
    }
  
    // Sweep through the dependency tree and make sure any broken dependencies are disabled
    disableBrokenDependencies();
  
  }
  
  /**
   * Ensure dependencies are mapped and registered against parent and child
   */
  
  function registerModuleDependencies(module) {
  
    if (module.fn && module.fn.depends && module.enabled) {
  
      // Create object to hold dependent status
      module.check = {};
  
      // Register depends on parent
      module.fn.depends.forEach(function (dependentModule) {
  
        module.check[dependentModule] = false;
  
        if (calipso.modules[dependentModule] && calipso.modules[dependentModule].enabled) {
  
          // Create a notification array to allow this module to notify modules that depend on it
          calipso.modules[dependentModule].notify = calipso.modules[dependentModule].notify || [];
          calipso.modules[dependentModule].notify.push(module.name);
  
        } else {
  
          calipso.modules[module.name].error = "Module " + module.name + " depends on " + dependentModule + ", but it does not exist or is disabled - this module will not load.";
          calipso.error(calipso.modules[module.name].error);
          calipso.modules[module.name].enabled = false;
  
        }
  
      });
  
    }
  
  }
  
  
  /**
   * Disable everythign in a broken dependency tree
   */
  
  function disableBrokenDependencies() {
  
    var disabled = 0;
    for (var moduleName in calipso.modules) {
      var module = calipso.modules[moduleName];
      if (module.enabled && module.fn && module.fn.depends) {
        module.fn.depends.forEach(function (dependentModule) {
          if (!calipso.modules[dependentModule].enabled) {
            calipso.modules[module.name].error = "Module " + module.name + " depends on " + dependentModule + ", but it does not exist or is disabled - this module will not load.";
            calipso.error(calipso.modules[module.name].error);
            calipso.modules[module.name].enabled = false;
            disabled = disabled + 1;
          }
        });
      }
    }
  
    // Recursive
    if (disabled > 0) {
      disableBrokenDependencies();
    }
  
  }
  
  /**
   * Notify dependencies for initialisation
   */
  
  function notifyDependenciesOfInit(moduleName, options) {
  
    var module = calipso.modules[moduleName];
    if (module.notify) {
      module.notify.forEach(function (notifyModuleName) {
        notifyDependencyOfInit(moduleName, notifyModuleName, options);
      });
    }
  
  }
  
  
  /**
   * Notify dependencies for routing
   */
  
  function notifyDependenciesOfRoute(req, res, moduleName, reqModules) {
  
    var module = calipso.modules[moduleName];
    if (module.notify) {
      module.notify.forEach(function (notifyModuleName) {
        notifyDependencyOfRoute(req, res, moduleName, notifyModuleName);
      });
    }
  
  }
  
  /**
   * Notify dependency
   * moduleName - module that has init'd
   * notifyModuleName - module to tell
   */
  
  function notifyDependencyOfInit(moduleName, notifyModuleName, options) {
  
    // Set it to true
    var module = calipso.modules[notifyModuleName];
    module.check[moduleName] = true;
    checkInit(module);
  
  }
  
  
  /**
   * Notify dependency
   * req - request
   * res - response
   * moduleName - module that has init'd
   * notifyModuleName - module to tell
   */
  
  function notifyDependencyOfRoute(req, res, moduleName, notifyModuleName) {
  
    var module = req.event.modules[notifyModuleName];
    module.check[moduleName] = true;
    checkRouted(req, res, moduleName, notifyModuleName);
  
  }
  
  /**
   * Check if all dependencies are met and we should init the module
   */
  
  function checkInit(module, next) {
  
    var doInit = true;
    for (var check in module.check) {
      doInit = doInit & module.check[check];
    }
    if (doInit) {
      // Initiate the module, no req for callback
      initModule(module.name, true, function () {
      });
    }
  
  }
  
  /**
   * Check if all dependencies are met and we should route the module
   */
  
  function checkRouted(req, res, moduleName, notifyModuleName) {
  
    var doRoute = true;
  
    for (var check in req.event.modules[notifyModuleName].check) {
      doRoute = doRoute && req.event.modules[notifyModuleName].check[check];
    }
  
    if (doRoute) {
      // Initiate the module, no req for callback
      // initModule(module.name,true,function() {});
      routeModule(req, res, notifyModuleName, true, false, function () {
      });
    }
  
  }
  
  /**
   * Load the module itself, refactored out to enable watch / reload
   * Note, while it was refactored out, you can't currently reload
   * a module, will patch in node-supervisor to watch the js files and restart
   * the whole server (only option :())
   */
  
  function requireModule(module, modulePath, reload, next) {
  
    var fs = calipso.lib.fs;
    var moduleFile = path.join(modulePath + '/' + module.name);
  
    try {
  
      // Require the module
      module.fn = require(moduleFile);
  
      // Attach a router - legacy check for default routes
      module.router = new calipso.router(module.name, modulePath);
  
      // Load the routes if specified as either array or function
      if (typeof module.fn.routes === "function") {
        module.fn.routes = module.fn.routes();
      }
      module.fn.routes = module.fn.routes || [];
  
      // Ensure the defaultConfig exists (e.g. if it hasn't been required before)
      // This is saved in the wider loadModules loop to ensure only one config save action (if required)
      if (module.fn.config && !calipso.config.getModuleConfig(module.name, '')) {
        calipso.config.setDefaultModuleConfig(module.name, module.fn.config);
      }
  
    } catch (ex) {
  
      calipso.error("Module " + module.name + " has been disabled because " + ex.message);
      calipso.modules[module.name].enabled = false;
  
    }
  
  }
  
  /**
   * Pre load all the templates in a module, synch, but only happens on app start up and config reload
   * This is attached to the templates attribute so used later.
   *
   * @param calipso
   * @param moduleTemplatePath
   * @returns template object
   */
  
  function loadModuleTemplates(module, moduleTemplatePath) {
  
    var templates = {};
  
    // Default the template to any loaded in the theme (overrides)
    var fs = calipso.lib.fs;
  
    if (!(fs.existsSync || calipso.lib.path.existsSync)(moduleTemplatePath)) {
      return null;
    }
  
    fs.readdirSync(moduleTemplatePath).forEach(function (name) {
      // Template paths and functions
      var templatePath = moduleTemplatePath + "/" + name;
  
      if (fs.statSync(templatePath).isDirectory()) {
        return;
      }
  
      var templateExtension = templatePath.match(/([^\.]+)$/)[0];
      var template = fs.readFileSync(templatePath, 'utf8');
      var templateName = name.replace(/\.([^\.]+)$/, '');
  
      // Load the template - only if not already loaded by theme (e.g. overriden)
      var hasTemplate = calipso.utils.hasProperty('theme.cache.modules.' + module.name + '.templates.' + templateName, calipso);
  
      if (hasTemplate) {
  
        // Use the theme version
        templates[templateName] = calipso.theme.cache.modules[module.name].templates[templateName];
  
      } else {
  
        // Else load it
        if (template) {
          // calipso.theme.compileTemplate => ./Theme.js
          templates[templateName] = calipso.theme.compileTemplate(template, templatePath, templateExtension);
  
          // Watch / unwatch files - always unwatch (e.g. around config changes)
          if (calipso.config.get('performance:watchFiles')) {
  
            fs.unwatchFile(templatePath); // Always unwatch first due to recursive behaviour
            fs.watchFile(templatePath, {
              persistent:true,
              interval:200
            }, function (curr, prev) {
              loadModuleTemplates(module, moduleTemplatePath);
              calipso.silly("Module " + module.name + " template " + name + " reloaded.");
            });
  
          }
  
        }
      }
    });
  
    module.templates = templates;
  
  }
  /**
   *  Exports
   */
  module.exports.loadModules = loadModules;
  module.exports.initModules = initModules;
  module.exports.eventRouteModules = eventRouteModules;
  module.exports.notifyDependenciesOfInit = notifyDependenciesOfInit;
  module.exports.notifyDependenciesOfRoute = notifyDependenciesOfRoute;
  module.exports.registerDependencies = registerDependencies;
  module.exports.loadAbout = loadAbout;
  