define([
  './base',
  './auraextensions',
  './logger'
  ], function(base, ExtManager, Logger) {

  var _ = base.util._,
      noop     = function() {},
      freeze   = Object.freeze || noop;

  /**
  * Aura constructor and main entry point
  *
  * Every instance of Aura defines an Aura application.
  * An Aura application is in charge of loading the various
  * extensions that will apply to it (defined either
  * programmatically or by way of configuration).
  *
  * An Aura application is the glue between all the extensions
  * and components inside its instance.
  *
  * Internally an Aura application wraps 4 important objects:
  *
  * - `config` is the object passed as the first param of the apps constructor
  * - `core`   is a container where the extensions add new features
  * - `sandbox` is an object that will be used as a prototype, to create fresh sandboxes to the components
  * - `extensions` An instance of the ExtensionManager. It contains all the extensions that will be loaded in the app.
  *
  * Extensions are here to provide features that will be used by the components...
  * They are meant to extend the apps' core & sandbox.
  * They also have access to the apps's config.
  *
  * Example of a creation of an Aura Application:
  *
  *     var app = aura({ key: 'value' });
  *     app.use('ext1').use('ext2');
  *     app.components.addSource('supercomponents', 'https://my.extern.al/components/store');
  *     app.start('body');
  *
  * @class Aura
  * @param {Object} [config] Main App config.
  * @method constructor
  */
  function Aura(config) {

    if (!(this instanceof Aura)) {
      return new Aura(config);
    }

    var extManager = new ExtManager(),
        app = this;

    /**
     * The App's internal unique reference.
     *
     * @property {String} ref
     */
    app.ref = _.uniqueId('aura_');

    /**
     * The App's config object
     *
     * @property {Object} config
     */
    app.config = config = config || {};
    app.config.sources = app.config.sources || { "default" : "./aura_components" };


    /*!
     * App Sandboxes
     */

    var appSandboxes = {};
    var baseSandbox = Object.create(base);

    /**
     * Namespace for sanboxes related methods.
     *
     * @type {Object}
     */
    app.sandboxes = {};

    /**
     * Creates a brand new sandbox, using the App's `sandbox` object as a prototype.
     *
     * @method sandboxes.create
     * @param   {String} [ref]      the Sandbox unique ref
     * @param   {Object} [options]  an object to that directly extends the Sandbox
     * @return  {Sandbox}
     */
    app.sandboxes.create = function (ref, options) {

      // Making shure we have a unique ref
      ref = ref || _.uniqueId('sandbox-');
      if (appSandboxes[ref]) {
        throw new Error("Sandbox with ref " + ref + " already exists.");
      }

      // Create a brand new sandbox based on the baseSandbox
      var sandbox = Object.create(baseSandbox);

      // Give it a ref
      sandbox.ref = ref || _.uniqueId('sandbox-');

      // Attach a Logger
      sandbox.logger = new Logger(sandbox.ref);

      // Register it in the app's sandboxes registry
      appSandboxes[sandbox.ref] = sandbox;

      var debug = config.debug;
      if(debug === true || (debug.enable && (debug.components.length === 0 || debug.components.indexOf(ref) !== -1))){
        sandbox.logger.enable();
      }

      // Extend if we have some options
      _.extend(sandbox, options || {});

      return sandbox;
    };

    /**
     * Get a sandbox by reference.
     *
     * @method sandboxes.get
     * @param  {String} ref  the Sandbox ref to retreive
     * @return {Sandbox}
     */
    app.sandboxes.get = function(ref) {
      return appSandboxes[ref];
    };

    /**
     * Tells the app to load the given extension.
     *
     * Aura extensions are loaded in the app during init.
     * they are responsible for :
     *
     * - resolving & loading external dependencies via requirejs
     * - they have direct access to the app's internals
     * - they are here to add new features to the app... that are made available through the sandboxes to the components.
     *
     * This method can only be called before the App is actually started.
     * Note that the App is started when its `start` method is called.
     *
     * @method use
     * @param  {String | Object | Function} ref the reference of the extension
     * @return {Aura} the Aura app object
     * @chainable
     */
    app.use = function(ref) {
      extManager.add({ ref: ref, context: app });
      return app;
    };

    /**
     * Namespace for components related methods.
     *
     * @type {Object}
     */
    app.components = {};

    /**
     * Adds a new source for components.
     * A Component source is an endpoint (basically a URL)
     * that is the root of a component repository.
     *
     * @method  components.addSource
     * @param {String} name    the name of the source.
     * @param {String} baseUrl the base url for those components.
     */
    app.components.addSource = function(name, baseUrl) {
      if (config.sources[name]) {
        throw new Error("Components source '" + name + "' is already registered");
      }
      config.sources[name] = baseUrl;
      return app;
    };

    /**
     * `core` is just a namespace used by the extensions to add features to the App.
     *
     * @property {Object} core
     */
    app.core = Object.create(base);

    /**
     * Application start.
     *
     * Bootstraps the extensions loading process.
     * All the extensions are resolved and loaded when `start` id called.
     * Start returns a promise that shall fail if any of the
     * extensions fails to load.
     *
     * The app's responsibility is to load its extensions,
     * make sure they are properly loaded when the app starts
     * and eventually make sure they are properly cleaned up when the app stops
     *
     * @method start
     * @param  {Object | String | Array} options start options.
     * @return {Promise} a promise that resolves when the app is started.
     */
    app.start = function(options) {

      if (app.started) {
        app.logger.error("Aura already started!");
        return extManager.initStatus;
      }

      app.logger.log("Starting Aura");

      var startOptions = options || {};

      // Support for different types of options (string, DOM Selector or Object)
      if (typeof options === 'string') {
        startOptions = { components: app.core.dom.find(options) };
      } else if (Array.isArray(options)) {
        startOptions = { components: options };
      } else if (options && options.widgets && !options.components) {
        startOptions.components = options.widgets;
      } else if (startOptions.components === undefined) {
        startOptions.components = app.core.dom.find(app.config.components || 'body');
      }

      extManager.onReady(function(exts) {
        // Then we call all the `afterAppStart` provided by the extensions
        base.util.each(exts, function(i, ext) {
          if (ext && typeof(ext.afterAppStart) === 'function') {
            ext.afterAppStart(app);
          }
        });
      });

      // If there was an error in the boot sequence we
      // reject every body and perform a cleanup
      // TODO: Provide a better error message to the user.
      extManager.onFailure(function() {
        app.logger.error("Error initializing app:", app.config.name, arguments);
        app.stop();
      });

      app.startOptions = startOptions;
      app.started = true;

      // Finally... we return a promise that allows
      // to keep track of the loading process...
      return extManager.init();
    };

    /**
     * Stops the application and unregister its loaded dependencies.
     *
     * @method stop
     * @return {void}
     */
    app.stop = function() {
      // TODO: We ne to actually do some cleanup here.
      app.started = false;
    };

    /**
     * Sandboxes are a way to implement the facade pattern on top of the features provided by `core`.
     *
     * The `sandbox` property of an Aura App is just an Object that will be used
     * as a blueprint, once the app is started, to create new instances
     * of sandboxed environments for the Components.
     * @property {Object} sandbox
     */
    app.sandbox = baseSandbox;

    /**
     * App Logger
     *
     * @property {Logger} logger
     */
    app.logger     = new Logger(app.ref);

    /**
     * @class Sandbox
     */

    /**
     * Stop method for a Sandbox.
     * If no arguments provided, the sandbox itself and all its children are stopped.
     * If a DOM Selector is provided, all matching children will be stopped.
     *
     * @param  {undefined|String} DOM Selector
     */
    app.sandbox.stop = function(selector) {
      if (selector) {
        app.core.dom.find(selector, this.el).each(function(i, el) {
          var ref = app.core.dom.find(el).data('__sandbox_ref__');
          stopSandbox(ref);
        });
      } else {
        stopSandbox(this);
      }
    };

    function stopSandbox(sandbox) {
      if (typeof sandbox === 'string') {
        sandbox = app.sandboxes.get(sandbox);
      }
      if (sandbox) {
        var event = ['aura', 'sandbox', 'stop'].join(app.config.mediator.delimiter);
        _.invoke(sandbox._children, 'stop');
        app.core.mediator.emit(event, sandbox);
        if (sandbox._component) {
          sandbox._component.invokeWithCallbacks('remove');
        }
        sandbox.stopped  = true;
        sandbox.el && app.core.dom.find(sandbox.el).remove();
        appSandboxes[sandbox.ref] = null;
        delete appSandboxes[sandbox.ref]; // do we need to actually call `delete` ?
        return sandbox;
      }
    }

    // Register core extensions : debug, mediator and components.
    config.debug = config.debug || {};
    var debug = config.debug;
    if (debug.enable) {
      if(debug.components){
        debug.components = debug.components.split(' ');
      }else{
        debug.components = [];
      }
      app.logger.enable();
      app.use('aura/ext/debug');
    }

    app.use('aura/ext/mediator');
    app.use('aura/ext/components');

    return app;
  }

  return Aura;
});
