(function () {

  'use strict';

  /* istanbul ignore next */
  var angular = window.angular ? window.angular : 'undefined' !== typeof require ? require('angular') : undefined;

  var angularChart = angular.module('angularChart', []);

  // HTML-Tag: <angular-chart>
  angularChart.directive('angularChart', angularChartDirective);
  // HTML-Tag <angularchart>
  angularChart.directive('angularchart', angularChartDirective);

  function angularChartDirective() {
    return {
      restrict: 'EA',
      scope: {
        options: '=',
        instance: '=?'
      },
      controller: 'AngularChartController'
    };
  }

  /* istanbul ignore next */
  if (typeof define === 'function' && define.amd) {
    define('angularChart', ['c3', 'angular'], angularChart);
  } else if ('undefined' !== typeof exports && 'undefined' !== typeof module) {
    module.exports = angularChart;
  }

})();

(function () {

  'use strict';

  /* istanbul ignore next */
  var angular = window.angular ? window.angular : 'undefined' !== typeof require ? require('angular') : undefined;

  function AngularChartWatcher() {

    var service = {
      init: init,
      updateState: updateState,
      applyFunction: applyFunction
    };

    return service;

    ////////////

    function init(scope) {
      var watcher = {
        scope: scope,
        dimensionsCallback: null,
        dimensionsTypeCallback: null,
        chartCallback: null,
        stateCallback: null,
        dataCallback: null,
        dataSmallWatcher: null,
        dataBigWatcher: null,
        disableStateWatcher: false
      };

      setupDimensionsWatcher(watcher);
      setupDimensionsTypeWatcher(watcher);
      setupChartWatcher(watcher);
      setupStateWatcher(watcher);
      setupWatchLimitWatcher(watcher);
      setupDataWatcher(watcher);

      return watcher;
    }

    ////
    // SETUP
    ////

    function setupDimensionsWatcher(watcher) {
      watcher.scope.$watch(function () {
        var check = watcher.scope.options && watcher.scope.options.dimensions;

        // remove types from copy to check only other changes
        if (angular.isObject(check)) {
          check = angular.copy(check);
          angular.forEach(check, function (dimension) {
            if (dimension.type) {
              delete dimension.type;
            }
          });
        }

        return check;
      }, function () {
        if (angular.isFunction(watcher.dimensionsCallback)) {
          watcher.dimensionsCallback();
        }
      }, true);
    }

    function setupDimensionsTypeWatcher(watcher) {
      watcher.scope.$watch(function () {
        var check = {};

        // extract only dimension types
        if (watcher.scope.options && watcher.scope.options.dimensions) {
          angular.forEach(watcher.scope.options.dimensions, function (dimension, key) {
            check[key] = dimension.type;
          });
        }

        return check;
      }, function () {
        if (angular.isFunction(watcher.dimensionsTypeCallback)) {
          watcher.dimensionsTypeCallback();
        }
      }, true);
    }

    function setupChartWatcher(watcher) {
      watcher.scope.$watch('options.chart', function () {
        if (watcher.chartCallback) {
          watcher.chartCallback();
        }
      }, true);
    }

    function setupStateWatcher(watcher) {
      watcher.scope.$watch('options.state', function () {
        if (!watcher.disableStateWatcher && angular.isFunction(watcher.stateCallback)) {
          watcher.stateCallback();
        }
      }, true);
    }

    function setupWatchLimitWatcher(watcher) {
      watcher.scope.$watch('options.chart.data.watchLimit', function () {
        setupDataWatcher(watcher);
      });
    }

    function setupDataWatcher(watcher) {
      // variables
      var limit = (angular.isObject(watcher.scope.options) && angular.isObject(watcher.scope.options.chart) && watcher.scope.options.chart.data && angular.isNumber(watcher.scope.options.chart.data.watchLimit)) ? watcher.scope.options.chart.data.watchLimit : 100;
      var numberOfDataRecords = 0;
      if (angular.isObject(watcher.scope.options) && angular.isArray(watcher.scope.options.data)) {
        numberOfDataRecords = watcher.scope.options.data.length;
      }

      // choose watcher
      if (numberOfDataRecords < limit) {
        // start small watcher
        if (!watcher.dataSmallWatcher) {
          watcher.dataSmallWatcher = setupDataSmallWatcher(watcher);
        }
        // stop big watcher
        if (watcher.dataBigWatcher) {
          watcher.dataBigWatcher();
          watcher.dataBigWatcher = undefined;
        }
      } else {
        // start big watcher
        if (!watcher.dataBigWatcher) {
          watcher.dataBigWatcher = setupDataBigWatcher(watcher);
        }
        // stop small watcher
        if (watcher.dataSmallWatcher) {
          watcher.dataSmallWatcher();
          watcher.dataSmallWatcher = undefined;
        }
      }
    }

    /**
     * start watcher changes in small datasets, compares whole object
     */
    function setupDataSmallWatcher(watcher) {
      return watcher.scope.$watch('options.data', function () {
        if (angular.isFunction(watcher.dataCallback)) {
          watcher.dataCallback();
        }
        setupDataWatcher(watcher);
      }, true);
    }

    /**
     * start watcher changes in big datasets, compares length of records
     */
    function setupDataBigWatcher(watcher) {
      return watcher.scope.$watch(function () {
        if (watcher.scope.options.data && angular.isArray(watcher.scope.options.data)) {
          return watcher.scope.options.data.length;
        } else {
          return 0;
        }
      }, function () {
        if (angular.isFunction(watcher.dataCallback)) {
          watcher.dataCallback();
        }
        setupDataWatcher(watcher);
      });
    }

    ////
    // $apply
    ////

    function updateState(watcher, func) {
      watcher.disableStateWatcher = true;
      watcher.scope.$apply(func);
      watcher.disableStateWatcher = false;
    }

    function applyFunction(watcher, func) {
      watcher.scope.$apply(func);
    }

  }

  angular
    .module('angularChart')
    .service('AngularChartWatcher', AngularChartWatcher);

})();

(function () {

  'use strict';

  /* istanbul ignore next */
  AngularChartState.$inject = ['AngularChartWatcher'];
  var angular = window.angular ? window.angular : 'undefined' !== typeof require ? require('angular') : undefined;

  function AngularChartState(AngularChartWatcher) {
    var service = {
      disableSelectionListener: false,
      synchronizeZoom: synchronizeZoom,
      applyZoom: applyZoom,
      synchronizeSelection: synchronizeSelection,
      applySelection: applySelection
    };

    return service;

    ////////////

    /**
     * Apply earlier zoom
     */
    function applyZoom(options, chart) {
      if (angular.isObject(options.state) && angular.isObject(options.state) && angular.isArray(options.state.range)) {
        chart.zoom(options.state.range);
      } else {
        chart.unzoom();
      }
    }

    /**
     * Create nested options objects.
     */
    function createZoomRangePath(options) {
      if (!angular.isObject(options.state)) {
        options.state = {};
      }
      if (!angular.isObject(options.state.range)) {
        options.state.range = [];
      }
    }

    /**
     * Setup zoom event listeners which update the state
     */
    function synchronizeZoom(options, configuration, watcher) {
      if (angular.isObject(options.chart) && angular.isObject(options.chart.zoom) && options.chart.zoom.enabled === true) {

        // setup onzoomend listener
        configuration.zoom.onzoomend = function (domain) {

          // update state
          AngularChartWatcher.updateState(watcher, function () {
            createZoomRangePath(options);
            options.state.range = domain;
          });

          // call user defined callback
          if (angular.isFunction(options.chart.zoom.onzoomend)) {
            AngularChartWatcher.applyFunction(watcher, function () {
              options.chart.zoom.onzoomend(domain);
            });
          }
        };
      }

      if (angular.isObject(options.chart) && angular.isObject(options.chart.subchart) && options.chart.subchart.show === true) {
        // setup onbrush listener
        configuration.subchart.onbrush = function (domain) {

          // update state
          AngularChartWatcher.updateState(watcher, function () {
            createZoomRangePath(options);
            options.state.range = domain;
          });

          // call user defined callback
          if (angular.isFunction(options.chart.subchart.onbrush)) {
            AngularChartWatcher.applyFunction(watcher, function () {
              options.chart.subchart.onbrush(domain);
            });
          }
        };
      }
    }

    /**
     * Add passed selection to the chart.
     */
    function addSelections(chart, selections) {
      service.disableSelectionListener = true;
      selections.forEach(function (selection) {
        chart.select([selection.id], [selection.index]);
      });
      service.disableSelectionListener = false;
    }

    /**
     * Remove passed selections from the chart.
     */
    //function removeSelections(chart, selections) {
    //  disableSelectionListener = true;
    //  selections.forEach(function (selection) {
    //    chart.unselect([selection.id], [selection.index]);
    //  });
    //  disableSelectionListener = false;
    //}

    /**
     * Remove all selections present in the chart.
     */
    function removeAllSelections(chart) {
      service.disableSelectionListener = true;
      chart.unselect();
      service.disableSelectionListener = false;
    }

    /**
     * Apply earlier selections.
     */
    function applySelection(options, chart) {
      if (angular.isObject(options.state) && angular.isArray(options.state.selected)) {
        // TODO: get new selections
        // TODO: get removed selections
        // var chartSelections = chart.selected();
        //    // addedSelections
        //    var addedSelections = newSelections.filter(function (elm) {
        //      var isNew = true;
        //      oldSelections.forEach(function (old) {
        //        if (old.id === elm.id && old.index === elm.index) {
        //          isNew = false;
        //          return isNew;
        //        }
        //      });
        //      return isNew;
        //    });
        //
        //    // removedSelections
        //    var removedSelections = oldSelections.filter(function (elm) {
        //      var isOld = true;
        //      newSelections.forEach(function (old) {
        //        if (old.id === elm.id && old.index === elm.index) {
        //          isOld = false;
        //          return isOld;
        //        }
        //      });
        //      return isOld;
        //    });

        // alternative: deselect all and select again
        //removeAllSelections(chart);
        addSelections(chart, options.state.selected);

      } else {
        removeAllSelections(chart);
      }
    }

    /**
     * Create nested options object.
     */
    function createSelectionsPath(options) {
      if (!angular.isObject(options.state)) {
        options.state = {};
      }
      if (!angular.isArray(options.state.selected)) {
        options.state.selected = [];
      }
    }

    /**
     * Listen to chart events to save selections into to state object.
     */
    function synchronizeSelection(options, configuration, watcher) {
      if (angular.isObject(options.chart) && angular.isObject(options.chart.data) && angular.isObject(options.chart.data.selection) && options.chart.data.selection.enabled === true) {

        // add onselected listener
        configuration.data.onselected = function (data, element) {

          // check if listener is disabled currently
          if (service.disableSelectionListener) {
            return;
          }

          // update state
          AngularChartWatcher.updateState(watcher, function () {
            createSelectionsPath(options);
            options.state.selected.push(data);
          });

          // call user defined callback
          if (angular.isFunction(options.chart.data.onselected)) {
            AngularChartWatcher.applyFunction(watcher, function () {
              options.chart.data.onselected(data, element);
            });
          }

        };

        // add onunselection listener
        configuration.data.onunselected = function (data, element) {

          // check if listener is disabled currently
          if (service.disableSelectionListener) {
            return;
          }

          // update state
          AngularChartWatcher.updateState(watcher, function () {
            createSelectionsPath(options);
            options.state.selected = options.state.selected.filter(function (selected) {
              return selected.id !== data.id || selected.index !== data.index;
            });
          });

          // call user defined callback
          if (angular.isFunction(options.chart.data.onunselected)) {
            AngularChartWatcher.applyFunction(watcher, function () {
              options.chart.data.onunselected(data, element);
            });
          }

        };

      }
    }

  }

  angular
    .module('angularChart')
    .service('AngularChartState', AngularChartState);

})();

(function() {

  'use strict';

  /* istanbul ignore next */
  AngularChartService.$inject = ['$timeout', '$q', 'AngularChartWatcher', 'AngularChartConverter', 'AngularChartState'];
  var angular = window.angular ? window.angular : 'undefined' !== typeof require ? require('angular') : undefined;
  /* istanbul ignore next */
  var c3 = window.c3 ? window.c3 : 'undefined' !== typeof require ? require('c3') : undefined;

  function AngularChartService($timeout, $q, AngularChartWatcher, AngularChartConverter, AngularChartState) {

    var ChartService = function(baseConfig, scope) {
      this.deferredChart = $q.defer();
      this.chart = this.deferredChart.promise;
      this.baseConfiguration = {};
      this.configuration = {};
      this.scopeReference = null;
      this.options = {};
      this.watcher = null;

      this.init(baseConfig, scope);
    };

    ChartService.prototype.init = function(baseConfig, scope) {
      this.watcher = AngularChartWatcher.init(scope);
      this.baseConfiguration = baseConfig;
      this.scopeReference = scope;
      this.updateCallback();

      // register callbacks after first digest cycle
      var chartService = this;
      $timeout(function() {
        chartService.registerCallbacks();
      });
    };

    /**
     * Register callbacks for the watchers.
     */
    ChartService.prototype.registerCallbacks = function() {
      var chartService = this;

      // updateCallback(), closure to keep reference to chart service
      this.watcher.dimensionsCallback = function() {
        chartService.updateCallback();
      };
      this.watcher.chartCallback = function() {
        chartService.updateCallback();
      };
      this.watcher.dataCallback = function() {
        chartService.updateCallback();
      };

      // transformCallback(), closure to keep reference to chart service
      this.watcher.dimensionsTypeCallback = function() {
        chartService.transformCallback();
      };

      // stateCallback(), closure to keep reference to chart service
      this.watcher.stateCallback = function() {
        chartService.stateCallback();
      };
    };

    /**
     * Update the configuration and render the chart.
     */
    ChartService.prototype.updateCallback = function() {
      this.configuration = angular.copy(this.baseConfiguration);
      this.buildOptions();
      this.convertOptions();
      this.applyChartOptions();
      this.synchronizeState();

      // call long running generation async
      var chartService = this;
      $timeout(function() {
        chartService.generateChart(chartService);
        chartService.stateCallback();
      });
    };

    /**
     * Pushes type changes using transform to update the chart without a full render.
     */
    ChartService.prototype.transformCallback = function() {
      var chartService = this;
      if (chartService.options && chartService.options.dimensions) {
        angular.forEach(chartService.options.dimensions, function(dimension, key) {
          chartService.chart.transform(dimension.type, key);
        });
      }
    };

    /**
     * Build options based on the values provided from scope.
     */
    ChartService.prototype.buildOptions = function() {
      this.options = angular.isObject(this.scopeReference.options) ? this.scopeReference.options : {};
    };

    /**
     * Convert the angular-chart specific options into a c3-configuration.
     */
    ChartService.prototype.convertOptions = function() {
      AngularChartConverter.convertData(this.options, this.configuration);
      AngularChartConverter.convertDimensions(this.options, this.configuration);
    };

    /**
     * Use the user defined chart configuration to extend and/or overwrite
     * the automatic set configuration.
     */
    ChartService.prototype.applyChartOptions = function() {
      this.merge(
        this.configuration,
        this.options.chart
      );
    };

    /**
     * Setup the synchronize from c3 events into the options.
     */
    ChartService.prototype.synchronizeState = function() {
      AngularChartState.synchronizeZoom(this.options, this.configuration, this.watcher);
      AngularChartState.synchronizeSelection(this.options, this.configuration, this.watcher);
    };

    /**
     * Render the chart.
     */
    ChartService.prototype.generateChart = function(chartService) {
      // TODO add own onresize listener?
      // TODO regenerate chart only one or two times per second
      // TODO evaluate if it makes sense to destroy the chart first

      var chart = c3.generate(chartService.configuration);

      chartService.deferredChart.resolve(chart);
      chartService.scopeReference.instance = chart;
      chartService.chart = chart;

      /*
       Custom event: 'angular-chart-rendered'
       - new chart instance generated
       - { configuration, chart } - data format
       - configuration - the options used to generate the chart
       - chart - the newly created chart instance
       */
      this.scopeReference.$emit('angular-chart-rendered', chartService.configuration, chartService.chart);
    };

    /**
     * Apply state options on the chart.
     */
    ChartService.prototype.stateCallback = function() {
      AngularChartState.applyZoom(this.options, this.chart);
      AngularChartState.applySelection(this.options, this.chart);
    };

    /**
     * Destroy the chart if one ist present.
     */
    ChartService.prototype.destroyChart = function() {
      if (this.chart.destroy) {
        this.chart.destroy();
      }
    };

    ChartService.prototype.merge = angular.merge || deepMerge;

    function deepMerge(target, src) {
      src = src || {};

      Object.keys(src).forEach(function (key) {
        if (!angular.isObject(src[key]) || !src[key]) {
          target[key] = src[key];
        } else {
          if (!target[key]) {
            target[key] = src[key];
          } else {
            target[key] = deepMerge(target[key], src[key]);
          }
        }
      });

      return target;
    }

    return {
      getInstance: function(baseConfig, scope) {
        return new ChartService(baseConfig, scope);
      }
    };
  }

  angular
    .module('angularChart')
    .factory('AngularChartService', AngularChartService);

})();

(function () {

  'use strict';

  /* istanbul ignore next */
  var angular = window.angular ? window.angular : 'undefined' !== typeof require ? require('angular') : undefined;

  function AngularChartConverter() {

    var service = {
      convertData: convertData,
      convertDimensions: convertDimensions
    };

    return service;

    ////////////

    function convertData(options, configuration) {
      // TODO support different data formats
      if (angular.isArray(options.data)) {
        configuration.data.json = options.data;
      }
    }

    function convertDimensions(options, configuration) {
      if (!angular.isObject(options.dimensions)) {
        return;
      }

      // only show used axes
      configuration.axis.y.show = false;

      // save displayFormat for reuse
      var displayFormat = {
        isUse: false,
        y: [],
        y2: []
      };

      // apply all dimensions
      angular.forEach(options.dimensions, function (dimension, key) {
        // TODO only when JSON (array of objects) data
        // set dimensions to show
        if (!angular.isDefined(dimension.show) || dimension.show) {
          configuration.data.keys.value.push(key);
        }

        // set name
        if (angular.isString(dimension.name)) {
          configuration.data.names[key] = dimension.name;
        }

        // set type
        if (angular.isDefined(dimension.type)) {
          configuration.data.types[key] = dimension.type;
        }

        // set color
        if (angular.isString(dimension.color)) {
          configuration.data.colors[key] = dimension.color;
        }

        // axis
        if (dimension.axis === 'y2') {
          configuration.data.axes[key] = 'y2';
          configuration.axis.y2.show = true;
          displayFormat.y2.push(key);
        } else if (dimension.axis !== 'x') {
          configuration.axis.y.show = true;
          displayFormat.y.push(key);
        }

        // get displayFormats
        displayFormat[key] = true;
        if (angular.isDefined(dimension.displayFormat)) {
          displayFormat.inUse = true;
          displayFormat[key] = dimension.displayFormat;
        } else if (angular.isDefined(dimension.prefix) || angular.isDefined(dimension.postfix)) {
          displayFormat.inUse = true;
          displayFormat[key] = function (label) {
            return (dimension.prefix || '') + label + (dimension.postfix || '');
          };
        }

        // data label
        if (dimension.label === true) {
          configuration.data.labels.format[key] = displayFormat[key];
        }

        // x-Axis
        if (dimension.axis === 'x') {
          configuration.data.keys.x = key;
          configuration.data.x = key;

          if (angular.isString(displayFormat[key]) || angular.isFunction(displayFormat[key])) {
            configuration.axis.x.tick.format = displayFormat[key];
          }

          if (['datetime', 'date', 'timeseries'].indexOf(dimension.dataType) !== -1) {
            configuration.axis.x.type = 'timeseries';
            if (dimension.dataFormat) {
              configuration.data.xFormat = dimension.dataFormat;
            }
          } else if (['numeric', 'number', 'indexed'].indexOf(dimension.dataType) !== -1) {
            configuration.axis.x.type = 'indexed';
          } else if (dimension.dataType === 'category' || (angular.isArray(options.data) && options.data[0] && options.data[0][key] && options.data[0][key] && !angular.isNumber(options.data[0][key]))) {
            configuration.axis.x.type = 'category';
          }
        }

      });

      // Tooltips
      // http://c3js.org/samples/tooltip_format.html
      if (displayFormat.inUse) {
        configuration.tooltip = {
          format: {
            value: function (value, ratio, id) {
              if (angular.isFunction(displayFormat[id])) {
                return displayFormat[id](value);
              } else {
                return value;
              }
            }
          }
        };
      }

      // Y-Axes
      // http://c3js.org/samples/axes_y_tick_format.html
      angular.forEach(['y', 'y2'], function (axis) {
        var format = null;
        var formatKey = null;
        angular.forEach(displayFormat[axis], function (key) {
          if (format === null) {
            format = displayFormat[key];
            formatKey = key;
          } else if (
            format !== displayFormat[key] && !(

              // not two functuins
            (!angular.isFunction(options.dimensions[formatKey].displayFormat) && !angular.isFunction(options.dimensions[key].displayFormat)) &&

            (

              // not two prefixes
            (!angular.isDefined(options.dimensions[formatKey].prefix) && !angular.isDefined(options.dimensions[key].prefix)) ||

              // two same prefixes
            (angular.isDefined(options.dimensions[formatKey].prefix) &&
            angular.isDefined(options.dimensions[key].prefix) &&
            options.dimensions[formatKey].prefix === options.dimensions[key].prefix)

            ) && (
              // not two postfixes
            (!angular.isDefined(options.dimensions[formatKey].postfix) && !angular.isDefined(options.dimensions[key].postfix)) ||

              // two same postfixes
            (angular.isDefined(options.dimensions[formatKey].postfix) &&
            angular.isDefined(options.dimensions[key].postfix) &&
            options.dimensions[formatKey].postfix === options.dimensions[key].postfix)

            ))) {
            format = false;
          }
        });
        if (format !== false && format !== true && format !== null) {
          configuration.axis[axis].tick.format = format;
        }
      });

    }

  }

  angular
    .module('angularChart')
    .service('AngularChartConverter', AngularChartConverter);

})();

(function () {

  'use strict';

  /* istanbul ignore next */
  AngularChartController.$inject = ['$scope', '$element', '$q', 'baseConfiguration', 'AngularChartService'];
  var angular = window.angular ? window.angular : 'undefined' !== typeof require ? require('angular') : undefined;

  function AngularChartController($scope, $element, $q, baseConfiguration, AngularChartService) {
    var configuration = angular.copy(baseConfiguration);
    var chartService = null;

    activate();

    ////////////

    function activate() {
      unwrapPromise();
      addIdentifier();
      addInlineStyle();
      getInstance();
      registerDestroyListener();
    }

    /**
     * Unwrap a options promise if onw exists
     */
    function unwrapPromise() {
      $q.when($scope.options, function (options) {
        $scope.options = options;
      });
    }

    /**
     * Add unique identifier for each chart
     */
    function addIdentifier() {
      $scope.dataAttributeChartID = 'chartid' + Math.floor(Math.random() * 1000000001);
      angular.element($element).attr('id', $scope.dataAttributeChartID);
      configuration.bindto = '#' + $scope.dataAttributeChartID;
    }

    /**
     * Add inline style to avoid additional css file
     */
    function addInlineStyle() {
      angular.element($element).css('display', 'block');
    }

    function getInstance() {
      chartService = AngularChartService.getInstance(configuration, $scope);
      $scope.instance = chartService.chart;
    }

    /**
     * Remove all references when directive is destroyed
     */
    function registerDestroyListener() {
      $scope.$on('$destroy', function () {
        chartService.destroyChart(configuration);
        $element.remove();
      });
    }

  }

  angular
    .module('angularChart')
    .controller('AngularChartController', AngularChartController);

})();

(function () {

  'use strict';

  /* istanbul ignore next */
  var angular = window.angular ? window.angular : 'undefined' !== typeof require ? require('angular') : undefined;

  var baseConfiguration = {
    data: {
      json: [],
      keys: {
        value: []
      },
      names: {},
      types: {},
      colors: {},
      axes: {},
      labels: {
        format: {}
      }
    },
    axis: {
      y: {
        show: true,
        tick: {}
      },
      y2: {
        tick: {}
      },
      x: {
        tick: {}
      }
    },
    zoom: {},
    subchart: {},
    tooltip: {
      format: {}
    }
  };

  angular
    .module('angularChart')
    .value('baseConfiguration', baseConfiguration);

})();


"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = loadEntity;

var _entityLifecycle = _interopRequireDefault(require("./common/entity-lifecycle"));

var _validateOptions = _interopRequireDefault(require("./util/validateOptions"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
 * Redux thunk action creator for performing asynchronous actions.
 *
 * @param {string}  name        Entity name
 * @param {Promise} promise     Promise (e.g. OrderService.getOrders())
 * @param {object}  options     Configuration options object
 * @return {function}           Perform an asynchronous action, dispatch Redux actions accordingly
 */
function loadEntity(name, promise, options) {
  if (!name || typeof name !== 'string') throw new Error('Missing required entity name');
  if (!promise || !promise.then) throw new Error('Missing required entity promise');

  try {
    !(0, _validateOptions.default)(options);
  } catch (error) {
    throw error;
  }

  var entityLifecycle = new _entityLifecycle.default(name, options);
  return function (dispatch, getState) {
    entityLifecycle.setDispatch(dispatch);
    entityLifecycle.setGetState(getState);
    entityLifecycle.onLoad();
    return new Promise(function (resolve, reject) {
      promise.then(function (data) {
        resolve(entityLifecycle.onSuccess(data));
      }).catch(function (error) {
        reject(entityLifecycle.onFailure(error));
      });
    });
  };
}


"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.makeActionCreator = makeActionCreator;
exports.makeEntityActionCreator = makeEntityActionCreator;

/**
 * Generation a Redux action object
 * @param action
 * @param keys
 * @param args
 * @returns {*}
 * @private
 */
var generateAction = function generateAction(action, keys, values) {
  var generatedAction = Object.assign({}, action);
  keys.forEach(function (arg, index) {
    generatedAction[keys[index]] = values[index];
  });
  return generatedAction;
};
/**
 * Generate action creators based on input arguments. The first argument is always
 * treated as the Redux action type; all other passed arguments are treated
 * as property on the action object itself.
 *
 *   Example: const myActionType = 'DO_IT';
 *            const doItAction = makeActionCreator(myActionType, 'data');
 *            doItAction(123); --> { type: "DO_IT", data: 123 }
 *
 * @param type
 * @returns {Function}
 */

function makeActionCreator(type) {
  for (var _len = arguments.length, keys = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
    keys[_key - 1] = arguments[_key];
  }

  if (!type) throw new Error('Type cannot be null/undefined');
  return function () {
    for (var _len2 = arguments.length, values = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
      values[_key2] = arguments[_key2];
    }

    return generateAction({
      type: type
    }, keys, values);
  };
}
/**
 * Identical to makeActionCreator(), however this function expects the second
 * argument to be the name of an entity.
 * @param type              Redux action type
 * @param entity            Model entity name (e.g 'users', 'orders', 'foobar')
 * @returns {Function}      Action creator that contains an entity key
 */

function makeEntityActionCreator(type, entity) {
  for (var _len3 = arguments.length, keys = new Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++) {
    keys[_key3 - 2] = arguments[_key3];
  }

  if (!type) throw new Error('Type cannot be null/undefined');
  if (!entity) throw new Error('Entity cannot be null/undefined');
  return function () {
    for (var _len4 = arguments.length, values = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
      values[_key4] = arguments[_key4];
    }

    return generateAction({
      type: type,
      entity: entity
    }, keys, values);
  };
}


'use strict';

const entities = require('redux-entity').entities;
const loadEntity = require('redux-entity').loadEntity;
const chalk = require('chalk');

// Redux state
let state = {};

// Invoke your custom thunk
fetchFoobar()(dispatch);

/**
 * Define custom thunk
 * @returns {*}
 */
function fetchFoobar () {
    return loadEntity(
        'foobar',
        _getFakePromise(),
        null
    );
}

/**
 * Simulate Redux's dispatch, with a logger
 * @param action
 */
function dispatch (action) {
    _logDetails(action);
    state = entities(state, action);
    _logDetails();
}

/**
 * Generate a promise that resolves randomly between 1-3 seconds
  * @returns {Promise}
 * @private
 */
function _getFakePromise () {
    return new Promise((resolve) => {
        const delay = _getRandomDelayBetween(1, 3, 2);
        setTimeout(() => resolve({delay}), delay * 1000);
    });
}

/**
 * Generate a random number in a given range, and round to a given value
 * @param min
 * @param max
 * @param roundTo
 * @returns {string}
 * @private
 */
function _getRandomDelayBetween (min, max, roundTo) {
    return Number(Math.random() * (max - min) + min).toFixed(roundTo);
}

/**
 * Log Redux actions
 * @param action
 * @private
 */
function _logDetails (action) {
    if (action) {
        console.log(`${chalk.white.bgRed('  Prev State:')}
        ${__toString(state)}`);
        console.log(`${chalk.white.bgBlue('      Action:')}
        ${__toString(action)}`);
    } else {
        console.log(`${chalk.white.bgGreen('  Next State:')}
        ${__toString(state)}`);
        console.log('\n');
    }
}

/**
 * Format objects to a JSON string, and indent with 2 spaces
 * @param obj
 * @private
 */
function __toString (obj) {
    return JSON.stringify(obj, null);
}


/*

The Just-Good-Enough XML Parser

*/

'use strict';

const sInitial = 0;
const sDeclaration = 1;
const sPreElement = 2;
const sElement = 3;
const sAttribute = 5;
const sAttrNML = 6; // No Mans Land
const sValue = 7;
const sEndElement = 9;
const sContent = 11;
const sAttributeSpacer = 12;
const sComment = 13;
const sProcessingInstruction = 15;
const sCData = 17;
const sDocType = 19;
const sDTD = 21;
const sError = 23;
const sEndDocument = 25;

String.prototype.replaceAll = function(search, replacement) {
    var target = this;
    return target.split(search).join(replacement);
};

function stateName(state) {
	if (state == sInitial) {
		return 'INITIAL';
	}
	else if (state == sDeclaration) {
		return 'DECLARATION';
	}
	else if (state == sElement) {
		return 'ELEMENT';
	}
	else if (state == sAttribute) {
		return 'ATTRIBUTE';
	}
	else if (state == sValue) {
		return 'VALUE';
	}
	else if (state == sEndElement) {
		return 'END_ELEMENT';
	}
	else if (state == sContent) {
		return 'CONTENT';
	}
	else if (state == sComment) {
		return 'COMMENT';
	}
	else if (state == sProcessingInstruction) {
		return 'PROCESSING_INSTRUCTION';
	}
	else if (state == sCData) {
		return 'CDATA';
	}
	else if (state == sDocType) {
		return 'DOCTYPE';
	}
	else if (state == sDTD) {
		return 'DTD';
	}
	else if (state == sError) {
		return 'ERROR';
	}
	else if (state == sEndDocument) {
		return 'END_DOCUMENT';
	}
}

function reset(context) {
	context.state = sInitial;
	context.newState = sInitial;
	context.token = '';
	context.boundary = ['<?','<'];
	context.bIndex = -1;
	context.lastElement = '';
	context.keepToken = false;
	context.position = 0;
	context.depth = 0;
	context.wellFormed = false;
	context.validControlChars = ['\t','\r','\n'];
	context.error = false;
}

// to create a push parser, pass in a callback function and omit the context parameter
// to create a pull parser, pass in null for the callback function and initially provide an empty object as the context
function jgeParse(s,callback,context) {

	if (context && context.newState) {
		if (!context.keepToken) context.token = '';
		context.state = context.newState;
	}
	else {
		context = {};
		reset(context);
	}

	var c;
	for (var i=context.position;i<s.length;i++) {
		c = s.charAt(i);
		if ((c.charCodeAt(0) < 32) && (context.validControlChars.indexOf(c) < 0)) {
			context.newState = context.state = sError;
		}

		if (context.state != sContent) {
			if (context.validControlChars.indexOf(c) >= 0) { //other unicode spaces are not treated as whitespace
				c = ' ';
			}
		}

		context.bIndex = -1;
		for (var b=0;b<context.boundary.length;b++) {
			if (s.substr(i,context.boundary[b].length) == context.boundary[b]) {
				context.bIndex = b;
				if (context.boundary[context.bIndex].length>1) {
					i = i + context.boundary[context.bIndex].length-1;
				}
				break;
			}
		}

		if (context.bIndex >= 0) {

			if ((context.state != sValue) && (context.state != sComment)) { // && (context.state != sContent)
				context.token = context.token.trim();
			}

			context.keepToken = false;
			if (((context.state & 1) == 1) && ((context.token.trim() !== '') || context.state == sValue)) {
				// TODO test element names for validity (using regex?)
				if (context.state != sCData) {
					context.token = context.token.replaceAll('&','&');
					context.token = context.token.replaceAll('"','"');
					context.token = context.token.replaceAll(''',"'");
					context.token = context.token.replaceAll('>','>');
					context.token = context.token.replaceAll('<','<');
					if (context.token.indexOf('&#') >= 0) {
						context.token = context.token.replace(/&(?:#([0-9]+)|#x([0-9a-fA-F]+));/g, function(match, group1, group2) {
							var e;
							if (group2) {
								e = String.fromCharCode(parseInt(group2,16));
								if ((e.charCodeAt(0) < 32) && (context.validControlChars.indexOf(e) < 0)) {
									context.newState = context.state = sError;
								}
								return e;
							}
							else {
								e = String.fromCharCode(group1);
								if ((e.charCodeAt(0) < 32) && (context.validControlChars.indexOf(e) < 0)) {
									context.newState = context.state = sError;
								}
								return e;
							}
						});
					}
				}

				if (context.state == sElement) context.depth++;
				else if (context.state == sEndElement) {
					context.depth--;
					if (context.depth<0) {
						context.newState = context.state = sError;
					}
				}
				if (context.state == sError) {
					context.error = true;
				}
				if (callback) {
					callback(context.state,context.token);
				}
				if (context.state == sError) {
					context.boundary = [];
				}
			}

			if (context.state == sInitial) {
				if (context.boundary[context.bIndex] == '<?') {
					context.newState = sDeclaration;
					context.boundary = ['?>'];
				}
				else {
					context.newState = sElement;
					context.boundary = ['>',' ','/','!--','?','!DOCTYPE','![CDATA['];
					context.boundary = context.boundary.concat(context.validControlChars);
				}
			}
			else if (context.state == sDeclaration) {
				context.newState = sPreElement;
				context.boundary = ['<'];
				if (context.token.indexOf('1.1')>0) {
					context.validControlChars.push('\u2028','\u0085','\u0015');
				}
			}
			else if (context.state == sPreElement) {
				context.newState = sElement;
				context.boundary = ['>',' ','/','!--','?','!DOCTYPE','![CDATA['];
				context.boundary = context.boundary.concat(context.validControlChars);
			}
			else if (context.state == sElement) {
				context.lastElement = context.token;
				if (c == '>') {
					context.newState = sContent;
					context.boundary = ['<!DOCTYPE','<'];
				}
				else if (c == ' ') {
					context.newState = sAttribute;
					context.boundary = ['/','=','>'];
				}
				else if (c == '/') {
					context.newState = sEndElement;
					context.boundary = ['>'];
					context.keepToken = true;
				}
				else if (c == '?') {
					context.newState = sProcessingInstruction;
					context.boundary = ['?>'];
				}
				else if (context.boundary[context.bIndex] == '!--') {
					context.newState = sComment;
					context.boundary = ['-->'];
				}
				else if (context.boundary[context.bIndex] == '![CDATA[') {
					context.newState = sCData;
					context.boundary = [']]>'];
				}
				else if (context.boundary[context.bIndex] == '!DOCTYPE') {
					context.newState = sDocType;
					context.boundary = ['>','['];
				}
			}
			else if (context.state == sAttribute) {
				if (c == '=' ) {
					context.newState = sAttrNML;
					context.boundary = ['\'','"'];
				}
				else if (c == '>') {
					context.newState = sContent;
					context.boundary = ['<!DOCTYPE','<'];
				}
				else if (c == '/') {
					context.newState = sEndElement;
					context.keepToken = true;
					context.state = sAttributeSpacer; // to stop dummy attributes being emitted to pullparser
					context.token = context.lastElement;
				}
			}
			else if (context.state == sAttrNML) {
				context.newState = sValue;
				context.boundary = [c];
			}
			else if (context.state == sValue) {
				context.newState = sAttribute;
				context.boundary = ['=','/','>'];
			}
			else if (context.state == sEndElement) {
				if (context.depth !== 0) context.newState = sContent;
				context.boundary = ['<']; // don't allow DOCTYPE's after the first sEndElement
			}
			else if (context.state == sContent) {
				if (context.boundary[context.bIndex] == '<!DOCTYPE') {
					context.newState = sDocType;
					context.boundary = ['>','['];
				}
				else {
					context.newState = sElement;
					context.boundary = ['>',' ','/','!--','?','![CDATA['];
					context.boundary = context.boundary.concat(context.validControlChars);
				}
			}
			else if (context.state == sComment) {
				context.newState = sContent;
				context.boundary = ['<!DOCTYPE','<'];
			}
			else if (context.state == sProcessingInstruction) {
				context.newState = sContent;
				context.boundary = ['<!DOCTYPE','<'];
			}
			else if (context.state == sCData) {
				context.newState = sContent;
				context.boundary = ['<!DOCTYPE','<'];
			}
			else if (context.state == sDocType) {
				if (context.boundary[context.bIndex] == '[') {
					context.newState = sDTD;
					context.boundary = [']>'];
				}
				else {
					context.newState = sPreElement;
					context.boundary = ['<'];
				}
			}
			else if (context.state == sDTD) {
				context.newState = sPreElement;
				context.boundary = ['<'];
			}

			if (!callback) {
				if (((context.state & 1) == 1) && ((context.token.trim() !== '') || context.state == sValue)) {
					context.position = i+1;
					return context;
				}
			}
			context.state = context.newState;

			if (!context.keepToken) context.token = '';
		}
		else {
			context.token += c;
		}

	}
	if ((context.state == sEndElement) && (context.depth === 0) && (context.token.trim() === '')) {
		context.wellFormed = true;
	}
	if ((!context.wellFormed) && (!context.error)) {
		if (callback) {
			// generate a final error, only for pushparsers though
			callback(sError,context.token);
		}
	}
	context.state = sEndDocument;
	if (callback) {
		callback(context.state,context.token);
		return context.wellFormed;
	}
	else {
		return context;
	}
}

module.exports = {
	parse : jgeParse,
	getStateName : stateName,
	sInitial : sInitial,
	sDeclaration : sDeclaration,
	sElement : sElement,
	sAttribute : sAttribute,
	sValue : sValue,
	sEndElement : sEndElement,
	sContent : sContent,
	sComment : sComment,
	sProcessingInstruction: sProcessingInstruction,
	sCData : sCData,
	sDocType : sDocType,
	sDTD : sDTD,
	sEndDocument : sEndDocument
};


'use strict';

String.prototype.replaceAll = function(search, replacement) {
    var target = this;
    return target.split(search).join(replacement);
};

function jpescape(s) {
	s = s.replaceAll('~','~0');
	s = s.replaceAll('/','~1');
	return s;
}

// JSON Pointer specification: http://tools.ietf.org/html/rfc6901
function jptr(obj, prop, newValue) {
    //property not found
    if (typeof obj === 'undefined') return false;
	if ((!prop) || (prop == '#')) return obj;

	if (prop.startsWith('#')) prop = prop.slice(1);
	if (prop.startsWith('/')) prop = prop.slice(1);
	var props = prop.split('/');

	var current = props[0];
	current = current.replaceAll('~1','/');
	current = current.replaceAll('~0','~');

	var index = -1;
	if ((props.length>1) && (Array.isArray(obj[current]))) {
		var next = props[1];
		var value = parseInt(next,10);
		if (next == '-') {
			index = obj[current].length;
		}
		else {
			if (!isNaN(value)) index = value;
		}
		if (index>=0) {
			props.splice(1,1);
			prop = props.join('/');
		}
	}

    //property split found; recursive call
    if (props.length>1) {
		var pos = prop.indexOf('/');
        //get object at property (before split), pass on remainder
		if (index>=0) {
			return jptr(obj[current][index], prop.substr(pos+1),newValue); //was props
		}
		else {
			return jptr(obj[current], prop.substr(pos+1),newValue);
		}
	}
	//no split; get property[index] or property
	var source = obj;
	if (current) source = obj[current];
	if (index>=0) {
		if (index>=source.length) {
			if (typeof newValue != 'undefined') {
				source.push(newValue);
				return newValue;
			}
			else {
				return null;
			}
		}
		else {
			if (typeof newValue != 'undefined') {
				source[index] = newValue;
			}
			return source[index];
		}
	}
    else {
		if (typeof newValue != 'undefined') {
			obj[prop] = newValue;
			source = obj[prop];
		}
		return source;
	}
}

// simple object accessor using dotted notation and [] for array indices
function fetchFromObject(obj, prop, newValue) {
    //property not found
    if (typeof obj === 'undefined') return false;
	if (!prop) {
		if (typeof newValue != 'undefined') {
			obj = newValue;
		}
		return obj;
	}

	var props = prop.split('.');
	var arr = props[0].split(/[\[\]]+/);
	var index = -1;
	if (arr.length>1) {
		index = parseInt(arr[1],10);
	}

    //property split found; recursive call
    if (props.length>1) {
		var pos = prop.indexOf('.');
        //get object at property (before split), pass on remainder
		if (index>=0) {
			return fetchFromObject(obj[arr[0]][index], prop.substr(pos+1), newValue); //was props
		}
		else {
			return fetchFromObject(obj[arr[0]], prop.substr(pos+1), newValue);
		}
	}
	//no split; get property[index] or property
	var source = obj;
	if (arr[0]) source = obj[prop];
	if (index>=0) {
		if (typeof newValue != 'undefined') source[index] = newValue;
		return source[index];
	}
    else {
		if (typeof newValue != 'undefined') obj[prop] = newValue;
		return obj[prop];
	}
}

function traverse(obj,prefix,depth,parent) {

var result = [];

	for (var key in obj) {
		// skip loop if the property is from prototype
		if (!obj.hasOwnProperty(key)) continue;

		var display = key;
		var sep = '.';
		if (Array.isArray(obj)) {
			display = '['+key+']';
			sep = '';
		}

		var item = {};
		item.prefix = prefix;
		item.key = key;
		item.display = display;
		item.value = obj[key];
		item.depth = depth;
		item.parent = parent;
		result.push(item);
		if (typeof obj[key] === 'object') {
			result = result.concat(traverse(obj[key],prefix+sep+display,depth+1,obj));
		}
	}
	return result;
}

function path(item,bracketed) {
	if (bracketed) {
		var result = '';
		var parents = item.prefix.split('.');
		for (var p=0;p<parents.length;p++) {
			result += "['" + parents[p] + "']";
		}
		if (item.display.charAt(0) == '[') {
			result += item.display;
		}
		else {
			result += '[' + item.display + ']';
		}
		return result;
	}
	else {
		var sep = '.';
		if ((typeof(item.value) === 'object') && (Array.isArray(item.parent)) && (item.prefix != '$')) {
			sep = '';
		}
		if (item.display.charAt(0) == '[') {
			sep = '';
		}
		return item.prefix+sep+item.display;
	}
}

function selectRegex(tree,expr,bracketed) {
	// not currently working, we are going to need some serious escaping of the regex
	if (!expr) {
		expr = /[*]/;
	}
	var result = [];
	for (var i=0;i<tree.length;i++) {
		var p = path(tree[i],bracketed);
		if (p.match(expr)) {
			result.push(tree[i]);
		}
	}
	return result;
}

function select(tree,target,bracketed) {
	var result = [];
	var returnParent = false;
	var checkEnd = false;

	// ^
	if (target.endsWith('^')) { // unoffical JSONPath extension
		target = target.substring(0,target.length-1);
		returnParent = true;
	}
	// .*
	if (target.endsWith('.*') && (target != '$..*')) {
		target = target.substring(0,target.length-2);
	}
	// [*]
	target = target.replaceAll('[*]','[]');
	// ..
	if ((target.indexOf('..') > 0) && (target != '$..*')) {
		var x = target.split('..');
		target = x[x.length-1];
		//target = target.replaceAll('..','.');
		target = target.replaceAll('$','');
		checkEnd = true;
	}

	for (var i=0;i<tree.length;i++) {
		var p = path(tree[i],bracketed);
		if ((target == '*') || (target == '$..*') || (p == target) || ((p.endsWith(target) && checkEnd))) {
			if (returnParent) {
				result.push(tree[i].parent);
			}
			else {
				result.push(tree[i]);
			}
		}
	}
	return result;
}

module.exports = {
	build : function(obj) {
		return traverse(obj,'$',0,{});
	},
	select : select,
	selectRegex : selectRegex,
	path : path,
	fetchFromObject : fetchFromObject,
	jptr : jptr,
	jpescape : jpescape
};


'use strict';

var xmlWrite = require('./xmlWrite');

function traverse(obj,parent,attributePrefix) {

var result = [];

	var array = Array.isArray(obj);
	for (var key in obj) {
		// skip loop if the property is from prototype
		if (!obj.hasOwnProperty(key)) continue;

		var propArray = Array.isArray(obj[key]);
		var output = array ? parent : key;

		if (typeof obj[key] !== 'object'){
			if (key.indexOf(attributePrefix) === 0) {
				xmlWrite.attribute(key.substring(1),obj[key]);
			}
			else {
				xmlWrite.startElement(output);
				xmlWrite.content(obj[key]);
				xmlWrite.endElement(output);
			}
		}
		else {
			if (!propArray) {
				xmlWrite.startElement(output);
			}
			traverse(obj[key],output,attributePrefix);
			if (!propArray) {
				xmlWrite.endElement(output);
			}
		}
	}
	return result;
}

module.exports = {
	// TODO convert this to an options object
	getXml : function(obj,attrPrefix,standalone,indent,indentStr,fragment) {
		var attributePrefix = (attrPrefix ? attrPrefix : '@');
		if (fragment) {
			xmlWrite.startFragment(indent,indentStr);
		}
		else {
			xmlWrite.startDocument('UTF-8',standalone,indent,indentStr);
		}
		traverse(obj,'',attributePrefix);
		return xmlWrite.endDocument();
	}
};


import { getDebug } from './Debug';
import { errorHandler } from './ErrorHandler';
import {
  isTypeOf, sNotDefined,
  iterateObject,copyArray,
  clone
} from './Utils';

const und = undefined;
/**
 * Private object to save the channels for communicating event driven
 * @type {Object}
 * @private
 */
let oChannels = {
  global: {}
};
/**
 * _removeSubscribers remove the subscribers to one channel and return the number of
 * subscribers that have been unsubscribed.
 * @param {Array<Object>} aSubscribers
 * @param {Object} oSubscriber
 * @return {Number}
 * @private
 */
function _removeSubscribers(aSubscribers, oSubscriber) {
  let nUnsubscribed = 0;
  if (!isTypeOf(aSubscribers, sNotDefined)) {
    let nIndex = aSubscribers.length - 1;
    for (; nIndex >= 0; nIndex--) {
      if (aSubscribers[nIndex].subscriber === oSubscriber) {
        nUnsubscribed++;
        aSubscribers.splice(nIndex, 1);
      }
    }
  }
  return nUnsubscribed;
}
/**
 * Loops per all the events to remove subscribers.
 * @param {Object} oEventsCallbacks
 * @param {String} sChannelId
 * @param {Object} oSubscriber
 * @return {Number}
 * @private
 */
function _removeSubscribersPerEvent(oEventsCallbacks, sChannelId, oSubscriber) {
  let nUnsubscribed = 0;
  iterateObject(oEventsCallbacks, function (oItem, sEvent) {
    const aEventsParts = sEvent.split(':');
    let sChannel = sChannelId;
    let sEventType = sEvent;
    if (aEventsParts[0] === 'global') {
      sChannel = aEventsParts[0];
      sEventType = aEventsParts[1];
    }
    nUnsubscribed += _removeSubscribers(oChannels[sChannel][sEventType], oSubscriber);
  });
  return nUnsubscribed;
}
/**
 * _addSubscribers add all the events of one channel from the subscriber
 * @param {Object} oEventsCallbacks
 * @param {String} sChannelId
 * @param {Object} oSubscriber
 * @private
 */
function _addSubscribers(oEventsCallbacks, sChannelId, oSubscriber) {
  iterateObject(oEventsCallbacks, function (oItem, sEvent) {
    subscribeTo(sChannelId, sEvent, oItem, oSubscriber);
  });
}
/**
 * _getChannelEvents return the events array in channel.
 * @param {String} sChannelId
 * @param {String} sEvent
 * @return {Object}
 * @private
 */
function _getChannelEvents(sChannelId, sEvent) {
  if (oChannels[sChannelId] === und) {
    oChannels[sChannelId] = {};
  }
  if (oChannels[sChannelId][sEvent] === und) {
    oChannels[sChannelId][sEvent] = [];
  }
  return oChannels[sChannelId][sEvent];
}

/**
 * subscribersByEvent return all the subscribers of the event in the channel.
 * @param {Object} oChannel
 * @param {String} sEventName
 * @return {Array<Object>}
 * @private
 */
function subscribersByEvent(oChannel, sEventName) {
  let aSubscribers = [];
  if (!isTypeOf(oChannel, sNotDefined)) {
    iterateObject(oChannel, function (oItem, sKey) {
      if (sKey === sEventName) {
        aSubscribers = oItem;
      }
    });
  }
  return aSubscribers;
}
/**
 * Sets the preprocessor of data before send the data to handlers.
 * @param {Function} fpCallback
 */
function preprocessorPublishData(fpCallback) {
  preprocessorsPublishData = function (oData) {
    return fpCallback(oData, clone);
  };
}
/**
 * Method to add a single callback in one channel an in one event.
 * @param {String} sChannelId
 * @param {String} sEventType
 * @param {Function} fpHandler
 * @param {Object} oSubscriber
 */
function subscribeTo(sChannelId, sEventType, fpHandler, oSubscriber) {
  const aChannelEvents = _getChannelEvents(sChannelId, sEventType);
  aChannelEvents.push({
    subscriber: oSubscriber,
    handler: fpHandler
  });
}
/**
 * subscribers return the array of subscribers to one channel and event.
 * @param {String} sChannelId
 * @param {String} sEventName
 * @return {Array<Object>}
 */
function subscribers(sChannelId, sEventName) {
  return subscribersByEvent(oChannels[sChannelId], sEventName);
}
/**
 * Method to unsubscribe a subscriber from a channel and event type.
 * It iterates in reverse order to avoid messing with array length when removing items.
 * @param {String} sChannelId
 * @param {String} sEventType
 * @param {Object} oSubscriber
 */
function unsubscribeFrom(sChannelId, sEventType, oSubscriber) {
  const aChannelEvents = _getChannelEvents(sChannelId, sEventType);
  for (let nEvent = aChannelEvents.length - 1; nEvent >= 0; nEvent--) {
    const oItem = aChannelEvents[nEvent];
    if (oItem.subscriber === oSubscriber) {
      aChannelEvents.splice(nEvent, 1);
    }
  }
}
/**
 * subscribe method gets the oEventsCallbacks object with all the handlers and add these handlers to the channel.
 * @param {Object} oSubscriber
 * @return {Boolean}
 */
function subscribe(oSubscriber) {
  const oEventsCallbacks = oSubscriber.events;
  if (!oSubscriber || oEventsCallbacks === und) {
    return false;
  }
  iterateObject(oEventsCallbacks, function (oItem, sChannelId) {
    if (oChannels[sChannelId] === und) {
      oChannels[sChannelId] = {};
    }
    _addSubscribers(oItem, sChannelId, oSubscriber);
  });

  return true;
}
/**
 * unsubscribe gets the oEventsCallbacks methods and removes the handlers of the channel.
 * @param {Object} oSubscriber
 * @return {Boolean}
 */
function unsubscribe(oSubscriber) {
  let nUnsubscribed = 0;
  const oEventsCallbacks = oSubscriber.events;
  if (!oSubscriber || oEventsCallbacks === und) {
    return false;
  }
  iterateObject(oEventsCallbacks, function (oItem, sChannelId) {
    if (oChannels[sChannelId] === und) {
      oChannels[sChannelId] = {};
    }
    nUnsubscribed = _removeSubscribersPerEvent(oItem, sChannelId, oSubscriber);
  });

  return nUnsubscribed > 0;
}
/**
 * Method to execute handlers
 * @param {Object} oHandlerObject
 * @param {Object} oData
 * @param {String} sChannelId
 * @param {String} sEvent
 */
function _executeHandler(oHandlerObject, oData, sChannelId, sEvent) {
  oHandlerObject.handler.call(oHandlerObject.subscriber, oData);
  if (getDebug()) {
    const ErrorHandler = errorHandler();
    ErrorHandler.log(sChannelId, sEvent, oHandlerObject);
  }
}
/**
 * Makes changes in oData before passing it to handler.
 * @param {Object} oData
 * @returns {*}
 */
function preprocessorsPublishData(oData) {
  return oData;
}
/**
 * Reset channels
 */
function reset() {
  oChannels = {
    global: {}
  };
}
/**
 * Publish the event in one channel.
 * @param {String} sChannelId
 * @param {String} sEvent
 * @param {String} oData
 * @return {Boolean}
 */
function publish(sChannelId, sEvent, oData) {
  const aSubscribers = copyArray(this.subscribers(sChannelId, sEvent));
  let oSubscriber;
  const nLenSubscribers = aSubscribers.length;
  if (nLenSubscribers === 0) {
    return false;
  }
  oData = preprocessorsPublishData(oData);
  while (!!(oSubscriber = aSubscribers.shift())) {
    _executeHandler(oSubscriber, oData, sChannelId, sEvent);
  }
  return true;
}
/**
 * To be used about extension, it will return a deep copy of the Channels object to avoid modifying the original
 * object.
 * @type {Function}
 * @return {Object}
 * @static
 */
function getCopyChannels() {
  return clone(oChannels);
}
/**
 * Bus is the object that must be used to manage the notifications by channels
 * @name Bus
 */
export default {
  subscribers,
  unsubscribeFrom,
  subscribeTo,
  subscribe,
  unsubscribe,
  publish,
  preprocessorPublishData,
  reset,
  getCopyChannels,
  __type__: 'bus'
};


import { getRoot, isTypeOf, copyArray, isArray } from './Utils';
import { getPromise } from './Promise';
import { getCopyModules, getModules } from './Modules';
import When from './When';
import { getVars } from './Vars';
import Bus from './Bus';
import { getNamespace } from './Namespace';
import Module from './Module';
import { errorHandler } from './ErrorHandler';
import { getApi } from './Hydra';

const root = getRoot();
let und;
const oMapping = {
  'bus': Bus,
  'module': Module,
  'log': errorHandler(),
  'api': getApi(),
  'global': root,
  'doc': root.document || null
};
/**
 * Mapping of prefixes by object to check to resolve dependencies.
 * @type {Object}
 * @private
 */
const oMappingMaps = { ___order___: [] };
/**
 * Helper function to create the mapping
 * @param {Object} oMapping
 * @param {String} sId
 * @param {Object} oMap
 * @param {Function} [fpResolveDI]
 * @private
 */
export function createMapping(oMapping, sId, oMap, fpResolveDI) {
  oMapping.___order___.push(sId);
  oMapping[sId] = {
    __map__: oMap
  };
  if (!fpResolveDI) {
    fpResolveDI = getResolveDICallback(oMapping[sId]);
  }
  oMapping[sId].__resolveDI__ = fpResolveDI;
}
/**
 * Return oMappingMaps
 * @returns {Object}
 */
export function getMappingMaps() {
  return oMappingMaps;
}

/**
 * Create or get a namespace by a namespace defined as string
 * @param {String}sNamespace
 * @return {Object}
 * @private
 */
function resolveNamespace(sNamespace) {
  var oObj = root,
    aElements = sNamespace.split('.'),
    sElement;
  while (!!( sElement = aElements.shift() )) {
    oObj = oObj[sElement] !== und ? oObj[sElement] : oObj[sElement] = {};
  }
  return oObj;
}
/**
 * Resolve dependency injection by default.
 * @param {Object} oMapping
 * @return {Function}
 * @private
 */
function getResolveDICallback(oMapping) {
  return function (sDependency) {
    var oPromise = getPromise();
    if (!oMapping.__map__[sDependency]) {
      return false;
    }
    oPromise.resolve(oMapping.__map__[sDependency]);
    return oPromise;
  };
}

/**
  * Traverse all the mapping systems to get a match.
  * @param {String} sDependency
  * @return {Boolean|Promise}
  * @private
  */
function getDependencyThroughAllMaps(sDependency) {
  var oMap,
    oDependency,
    nIndexOrder,
    nLenOrder,
    aOrderDependency = oMappingMaps.___order___;

  createMapping(oMappingMaps, '__', root, function (sDependency) {
    var oDependency,
      oPromise = getPromise();
    oDependency = resolveNamespace(sDependency);
    oPromise.resolve(oDependency);
    return oPromise;
  });

  for (nIndexOrder = 0, nLenOrder = aOrderDependency.length; nIndexOrder < nLenOrder; nIndexOrder++) {
    oMap = oMappingMaps[aOrderDependency[nIndexOrder]];
    oDependency = oMap.__resolveDI__(sDependency);
    if (oDependency) {
      delete oMappingMaps['__'];
      return oDependency;
    }
  }
  delete oMappingMaps['__'];
  return false;
}

/**
  * Inject dependencies creating modules
  * Look for dependencies in:
  * Hydra mappings
  * oVars
  * oModules
  * namespace
  * root
  * @param {String} sModuleId
  * @param {Array} aDependencies
  */
export function resolveDependencies(sModuleId, aDependencies) {
  const oMappingMaps = getMappingMaps();
  var sDependency,
    sPrefix,
    oModules,
    oMod,
    aPromises = [],
    nDependencies = 0,
    oMap,
    aExtraDependencies,
    oDependency,
    oPromise,
    oResult = {
      mapping: [],
      dependencies: []
    };

  oModules = getCopyModules();
  oMod = oModules[sModuleId];
  if (!oMod) {
    oMod = {};
  }
  if (!oMod.dependencies) {
    oMod.dependencies = ['$$_bus', '$$_module', '$$_log', 'gl_Hydra'];
  }
  aExtraDependencies = oMod.dependencies;
  if (!isArray(aExtraDependencies) && typeof aExtraDependencies === 'object') {
    aExtraDependencies = getKeys(aExtraDependencies);
  }

  aDependencies = (isArray(aDependencies) ? aDependencies : ( aExtraDependencies || [])).concat();

  while (!!(sDependency = aDependencies.shift())) {

    if (isTypeOf(sDependency, 'string')) {
      if (sDependency.indexOf('_') === 2) {
        sPrefix = sDependency.substr(0, 3);
      } else if (sDependency.indexOf('$') === 0) {
        sPrefix = sDependency.substr(0, 1);
      } else {
        sPrefix = '';
      }
      oMap = oMappingMaps[ sPrefix ] || oMappingMaps['gl_'];
      sDependency = sDependency.replace(sPrefix, '');
      oDependency = oMap.__map__[sDependency];
      if (!oDependency) {
        oDependency = getDependencyThroughAllMaps(sDependency);
      } else {
        oDependency = oMap.__resolveDI__(sDependency);
      }

      oResult.mapping.push(sDependency);
    } else {
      oDependency = getPromise();
      oDependency.resolve(sDependency);
      oResult.mapping.push(sDependency.__type__ || oMod.dependencies[nDependencies]);
    }
    aPromises.push(oDependency);
  }
  oPromise = getPromise();

  When.apply(When, aPromises).then(function () {
    oPromise.resolve.apply(oPromise, [oResult.mapping].concat(copyArray(arguments)));
  });
  return oPromise;
}

createMapping(oMappingMaps, '$$_', oMapping);
createMapping(oMappingMaps, '$', oMapping);
createMapping(oMappingMaps, 'pr_', getVars());
createMapping(oMappingMaps, 'hm_', getModules());
createMapping(oMappingMaps, 'ns_', getNamespace() || root);
createMapping(oMappingMaps, 'gl_', root);


import {nullFunc} from './Utils';
/**
 * Returns the promise callback by type
 * @param {Object}oContext
 * @param {String} sType
 * @return {Function}
 * @private
 */
function getPromiseCallbacks(oContext, sType) {
  return function () {
    var aCompleted, nLenPromises, oDeferred, aPromises, nPromise, oPromise, aResults = [];
    oContext.bCompleted = true;
    oContext.sType = sType;
    oContext.oResult = arguments;
    while (oContext.aPending[0]) {
      oContext.aPending.shift()[sType].apply(oContext, arguments);
    }
    oDeferred = oContext.oDeferred;
    if(oDeferred){
      aCompleted = [];
      aPromises = oDeferred.aPromises;
      nLenPromises = aPromises.length;
      aResults = [];
      for(nPromise = 0; nPromise < nLenPromises; nPromise++){
        oPromise = aPromises[nPromise];
        aCompleted.push(Number(oPromise.bCompleted));
        aResults.push( oPromise.oResult );
      }
      if(aCompleted.join('').indexOf('0') === -1){
        oDeferred[sType].apply(oDeferred, aResults);
      }
    }
  };
}

/**
 * Promise is a class that must/can be used to defer execution of one or some callbacks when one
 * condition (normally some asynchronous callbacks that are depending one of other)
 * @class Promise
 * @param {Function} fpCallback
 * @constructor
 * @name Promise
 */
export default function Promise(fpCallback) {
  // Pending callbacks
  this.oDeferred = null;
  this.aResults = [];
  this.aPromises = [];
  this.aPending = [];
  this.bCompleted = false;
  this.sType = '';
  this.oResult = null;
  fpCallback = fpCallback || nullFunc;  // Made to be compatible with previous versions not ES6 compliant.

  // Must be called when something finished successfully
  this.resolve = getPromiseCallbacks(this, 'resolve');
  // Must be called when something fails
  this.reject = getPromiseCallbacks(this, 'reject');
  fpCallback(this.resolve, this.reject);
}

Promise.prototype = {

  /**
   * Adds new callbacks to execute when the promise has been completed
   * @member Promise.prototype
   * @param {Function} fpSuccess
   * @param {Function} fpFailure
   * @return {Promise} Promise instance
   */
  then: function (fpSuccess, fpFailure) {
    var oResult = this.oResult;
    if (this.bCompleted) {
      if (this.sType === 'resolve') {
        fpSuccess.apply(fpSuccess, oResult);
      } else {
        fpFailure.apply(fpFailure, oResult);
      }
    } else {
      this.aPending.push({ resolve: fpSuccess, reject: fpFailure});
    }
    return this;
  },

  /**
   * Adds a new callback to be executed when the promise is resolved.
   * @member Promise.prototype
   * @param {Function} fpSuccess
   */
  done: function (fpSuccess) {
    return this.then(fpSuccess, nullFunc);
  },

  /**
   * Adds a new callback to be executed when the promise is rejected.
   * @member Promise.prototype
   * @param {Function} fpFailure
   */
  fail: function (fpFailure) {
    return this.then(nullFunc, fpFailure);
  },

  /**
   * Sugar function to create a new Deferred object.
   * When expects Promise objects to be added to the Deferred object [Promise1, Promise2,...PromiseN]
   * If one of the arguments is not a Promise When assume that we want to complete the Deferred object
   */
  all: function () {
    return When.apply(When, arguments);
  },
  /**
   * Adds a new callback to be executed when the promise is rejected.
   * According with the new
   * @member Promise.prototype
   * @param {Function} fpFailure
   */
  'catch': function (fpFailure) {
    return this.fail(fpFailure);
  },
  /**
   * Adds a new promise to be used as a Deferred object.
   * @param oPromise
   * @returns {*}
   */
  add: function (oPromise) {
    oPromise.oDeferred = this;
    this.aPromises.push(oPromise);
    return this;
  }
};
/**
 * Returns the callback to be executed when needed.
 * @param {Number} nIndex
 * @param {String} sType
 * @param {Object} oData
 * @param {Number} nLenArgs
 * @param {Promise} oPromise
 * @param {Array} aSolutions
 * @return {Function}
 * @private
 */
export function getThenCallbacks(nIndex, sType, oData, nLenArgs, oPromise, aSolutions) {
  return function (oObj) {
    oData.nLenPromisesResolved++;
    aSolutions[nIndex] = oObj;
    if (nLenArgs === oData.nLenPromisesResolved) {
      oPromise[sType].apply(oPromise, aSolutions);
    }
  };
}
/**
 * Returns an instance of Promise
 * @param {Function} [fpCallback]
 * @returns {Promise}
 * @private
 */
export function getPromise(fpCallback) {
  return new Promise(fpCallback);
}


import { isTypeOf, sNotDefined } from './Utils';
import Module from './Module';
/**
 * Module to be stored, adds two methods to start and extend modules.
 * @param {String} sModuleId
 * @param {Function} fpCreator
 * @constructor
 * @class FakeModule
 * @name FakeModule
 * @private
 */

function FakeModule(sModuleId, fpCreator) {
  if (isTypeOf(fpCreator, sNotDefined)) {
    throw new Error('Something goes wrong!');
  }
  this.creator = fpCreator;
  this.instances = {};
  this.sModuleId = sModuleId;
}

FakeModule.prototype = {

  /**
   * Wraps the module start
   * @member FakeModule.prototype
   * @param {Object} oData
   * @return {FakeModule}
   */
  start: function (oData) {
    Module.start(this.sModuleId, oData);
    return this;
  },

  /**
   * Wraps the module extend
   * @member FakeModule.prototype
   * @param {String|Function} oSecondParameter
   * @param {Array|Function} oThirdParameter
   * @param {Function} oFourthParameter
   * @return {FakeModule}
   */
  extend: function (oSecondParameter, oThirdParameter, oFourthParameter) {
    Module.extend(this.sModuleId, oSecondParameter, oThirdParameter, oFourthParameter);
    return this;
  },

  /**
   * Wraps the module stop
   * @member FakeModule.prototype
   * @return {FakeModule}
   */
  stop: function () {
    Module.stop(this.sModuleId);
    return this;
  }
}

export default FakeModule;


/**
 * Wrapper of Object.prototype.toString to detect type of object in cross browsing mode.
 * @param {Object} oObject
 * @return {String}
 * @private
 */
function toString(oObject) {
  return Object.prototype.toString.call(oObject);
}
/**
 * Use jQuery detection
 * @param {Object} oObj
 * @return {Boolean}
 * @private
 */
function isJqueryObject(oObj) {
  var isJquery = false,
    $ = getRoot().jQuery;
  if ($) {
    isJquery = isInstanceOf(oObj, $);
  }
  return isJquery;
}
/**
 * Use Event detection and if it fails it degrades to use duck typing detection to
 * test if the supplied object is an Event
 * @param {Object} oObj
 * @return {Boolean}
 * @private
 */
function isEvent(oObj) {
  try {
    return isInstanceOf(oObj, Event);
  } catch (erError) {
    // Duck typing detection (If it sounds like a duck and it moves like a duck, it's a duck)
    if (oObj.altKey !== und && ( oObj.srcElement || oObj.target )) {
      return true;
    }
  }
  return false;
}
/**
 * isArray is a function to know if the object passed as parameter is an Array object.
 * @param {*} aArray
 * @return {Boolean}
 * @private
 */
export function isArray(aArray) {
  return toString(aArray) === '[object Array]';
}
/**
 * Return a copy of the object.
 * @param {Object} oObject
 * @return {Object}
 * @private
 */
export function clone(oObject) {
  var oCopy;
  /*
   Handle null, undefined, DOM element, Event and jQuery objects,
   and all the objects that are instances of a constructor different from Object.
   */
  if (null == oObject ||      // Is null or undefined
    !isTypeOf(oObject, 'object') ||  // Is not an object (primitive)
    oObject.constructor.toString().indexOf('Object()') === -1 ||  // Is an instance
    isEvent(oObject) ||   // Is an event
    isJqueryObject(oObject) ||  // Is a jQuery object
    ( oObject.nodeType && oObject.nodeType === 1 )) { // Is a DOM element
    return oObject;
  }

  // Handle Date
  if (isInstanceOf(oObject, Date)) {
    oCopy = new Date();
    oCopy.setTime(oObject.getTime());
    return oCopy;
  }

  // Handle Array
  if (isInstanceOf(oObject, Array)) {
    oCopy = copyArray(oObject);
    return oCopy;
  }

  // Handle Object
  if (isInstanceOf(oObject, Object)) {
    oCopy = {};
    iterateObject(oObject, function (oItem, sKey) {
      oCopy[sKey] = clone(oItem);
    });
    return oCopy;
  }

  throw new Error('Unable to copy object!');
}
/**
 * Check if is the type indicated
 * @param {Object} oMix
 * @param {String} sType
 * @return {Boolean}
 */
export function isTypeOf(oMix, sType) {
  return typeof oMix === sType;
}
/**
 * Cache 'undefined' string to test typeof
 * @type {String}
 */
export const sNotDefined = 'undefined';
/**
 * Return a copy of an Array or convert a LikeArray object to Array
 * @param {Object|Array} oLikeArray
 * @private
 */
export function copyArray(oLikeArray) {
  return [].slice.call(oLikeArray, 0);
}
/**
 * Returns global or window object
 * @returns {boolean|Window|*|getRoot}
 */
export function getRoot() {
  var root = ((typeof self === 'object' && self.self === self && self) ||
  (typeof global === 'object' && global.global === global && global) ||
  this);
  if (root.document) {
    root.document.__type__ = 'doc';
  }
  if (root.console) {
    root.console.__type__ = 'log';
  }
  return root;
}
/**
 * isFunction is a function to know if the object passed as parameter is a Function object.
 * @param {*} fpCallback
 * @return {Boolean}
 * @private
 */
export function isFunction(fpCallback) {
  return toString(fpCallback) === '[object Function]';
}
/**
 * Helper to iterate over objects using for-in approach
 * @param {Object} oObject
 * @param {Function} fpProcess
 * @private
 */
export function iterateObject(oObject, fpProcess) {
  var sKey;

  for (sKey in oObject) {
    if (oObject.hasOwnProperty(sKey)) {
      fpProcess(oObject[sKey], sKey);
    }
  }
}
/**
 * nullFunc
 * An empty function to be used as default is no supplied callbacks.
 * @private
 */
export function nullFunc() {}
/**
 * Do a simple merge of two objects overwriting the target properties with source properties
 * @param {Object} oTarget
 * @param {Object} oSource
 * @private
 */
export function simpleMerge(oTarget, oSource) {
  iterateObject(oSource, function (oItem, sKey) {
    oTarget[sKey] = oSource[sKey];
  });
  return oTarget;
}
/**
 * Function type string
 * @type {String}
 * @private
 */
export const sFunctionType = 'function';
/**
 * Do a simple merge of two objects overwriting the target properties with source properties
 * @param {Object} oTarget
 * @param {Object} oSource
 * @private
 */
export function simpleMerge(oTarget, oSource) {
  iterateObject(oSource, function (oItem, sKey) {
    oTarget[sKey] = oSource[sKey];
  });
  return oTarget;
}
/**
 * Wrapper of instanceof to reduce final size
 * @param {Object} oInstance
 * @param {Object} oConstructor
 * @return {Boolean}
 * @private
 */
export function isInstanceOf(oInstance, oConstructor) {
  return oInstance instanceof oConstructor;
}
/**
 * Return the length of properties of one object
 * @param {Object} oObj
 * @return {Number}
 * @private
 */
export function getObjectLength(oObj) {
  return getKeys(oObj).length;
}
/**
 * Return the length of properties of one object
 * @param {Object} oObj
 * @return {Number}
 * @private
 */
function getObjectLength(oObj) {
  return getKeys(oObj).length;
}
/**
 * Return the function to execute simple callbacks in extended modules.
 * @param {Function} oCallback
 * @param {Object} [oContext]
 * @returns {Function}
 */
export function getSimpleFunction(oCallback, oContext) {
  return function () {
    return oCallback.apply(oContext || this, arguments);
  };
}
/**
 * Used to generate an unique key for instance ids that are not supplied by the user.
 * @return {String}
 * @private
 */
export function generateUniqueKey() {
  var oMath = Math, sFirstToken = +new Date() + '',
    sSecondToken = oMath.floor(oMath.random() * ( 999999 - 1 + 1 )) + 1;
  return sFirstToken + '_' + sSecondToken;
}


import {
  isTypeOf, sNotDefined,
  iterateObject, isFunction,
  nullFunc, copyArray, simpleMerge,
  sFunctionType, generateUniqueKey,
  getSimpleFunction, getObjectLength,
  isInstanceOf, isArray
} from './Utils';
import Bus from './Bus';
import { resetVars, setVars, getVars } from './Vars';
import { getModules, resetModules } from './Modules';
import { getDebug } from './Debug';
import { getPromise } from './Promise';
import { resolveDependencies, createMapping, getMappingMaps } from './DependencyInjector';
import { errorHandler } from './ErrorHandler';
import FakeModule from './FakeModule';

const __super__ = {};
const instances = {};
const __type__ = 'module';
const oModifyInit = {};
/**
 * type is a property to be able to know the class type.
 * @type {String}
 */
const type = 'Module';
/**
 * Return the message to show when a module is not registered
 * @param {String} sModuleId
 * @param {Boolean} [bThrow]
 * @return {String}
 * @private
 */
function fpThrowErrorModuleNotRegistered(sModuleId, bThrow) {
  var sMessage = 'The module ' + sModuleId + ' is not registered in the system';
  if (bThrow) {
    throw new Error(sMessage);
  }
  return sMessage;
}

export function extendModifyInit(oVar) {
  simpleMerge(oModifyInit, oVar);
}
/**
 * Add common properties and methods to avoid repeating code in modules
 * @param {String} sModuleId
 * @param {Array} aDependencies
 * @param {Function} fpCallback
 * @private
 */
function addPropertiesAndMethodsToModule(sModuleId, aDependencies, fpCallback) {
  var oPromise;

  function success(mapping) {
    const oModules = getModules();
    var oModule, fpInitProxy;
    oModule = oModules[sModuleId].creator.apply(oModules[sModuleId], [].slice.call(arguments, 1));
    oModule.__children__ = [];
    oModule.dependencies = aDependencies || [].slice.call(arguments, 1);
    oModule.resolvedDependencies = mapping;
    oModule.__module_id__ = sModuleId;
    fpInitProxy = oModule.init || nullFunc;
    // Provide compatibility with old versions of Hydra.js
    oModule.__action__ = oModule.__sandbox__ = Bus;
    oModule.events = oModule.events || {};
    oModule.init = function () {
      var aArgs = copyArray(arguments).concat(getVars());
      if (oModule.__children__.length === 0) {  // Only subscribe last element of inheritance.
        Bus.subscribe(oModule);
      }
      return fpInitProxy.apply(this, aArgs);
    };
    oModule.handleAction = function (oNotifier) {
      var fpCallback = this.events[oNotifier.type];
      if (isTypeOf(fpCallback, sNotDefined)) {
        return;
      }
      fpCallback.call(this, oNotifier);
    };
    // Provide compatibility with old Hydra versions which used to use "destroy" as onDestroy hook.
    oModule.onDestroy = oModule.onDestroy || oModule.destroy || function () {
      };
    oModule.destroy = function () {
      this.onDestroy();
      Bus.unsubscribe(oModule);
      delete oModules[sModuleId].instances[oModule.__instance_id__];
    };
    fpCallback(oModule);
  }

  oPromise = resolveDependencies(sModuleId, aDependencies);
  oPromise.then(function () {
    success.apply(success, arguments);
  });
}
/**
 * Checks if module was already successfully started
 * @member Module.prototype
 * @param {String} sModuleId Name of the module
 * @param {String} [sInstanceId] Id of the instance
 * @return {Boolean}
 */
export function isModuleStarted(sModuleId, sInstanceId) {
  const oModules = getModules();
  var bStarted = false,
    bModuleDefined = isTypeOf(oModules[sModuleId], sNotDefined);
  if (isTypeOf(sInstanceId, sNotDefined)) {
    bStarted = ( !bModuleDefined && getObjectLength(oModules[sModuleId].instances) > 0 );
  } else {
    bStarted = ( !bModuleDefined && !isTypeOf(oModules[sModuleId].instances[sInstanceId], sNotDefined) );
  }
  return bStarted;
}
/**
 * getInstance is the method that will create the module instance and wrap the method if needed.
 * @param {String} sModuleId
 * @param {Array} [aDependencies]
 * @param {Function} fpCallback
 * @private
 */
function getInstance(sModuleId, aDependencies, fpCallback) {
  const oModules = getModules();
  if (isTypeOf(oModules[sModuleId], sNotDefined)) {
    fpThrowErrorModuleNotRegistered(sModuleId, true);
  }
  addPropertiesAndMethodsToModule(sModuleId, aDependencies, function (oInstance) {
    if (!getDebug()) {
      iterateObject(oInstance, function (oItem, sName) {
        if (isFunction(oItem)) {
          wrapMethod(oInstance, sName, sModuleId, oInstance[sName]);
        }
      });
    }
    fpCallback(oInstance);
  });
}
/**
 * wrapMethod is a method to wrap the original method to avoid failing code.
 * This will be only called if bDebug flag is set to false.
 * @param {Object} oInstance
 * @param {String} sName
 * @param {String} sModuleId
 * @param {Function} fpMethod
 * @private
 */
function wrapMethod(oInstance, sName, sModuleId, fpMethod) {
  oInstance[sName] = ( function (sName, fpMethod) {
    return function () {
      var aArgs = copyArray(arguments);
      try {
        return fpMethod.apply(this, aArgs);
      }
      catch (erError) {
        const ErrorHandler = errorHandler();
        ErrorHandler.error(sModuleId, sName, erError);
        return false;
      }
    };
  }(sName, fpMethod));
}

/**
 * register is the method that will add the new module to the oModules object.
 * sModuleId will be the key where it will be stored.
 * @param {String} sModuleId
 * @param {Array} aDependencies
 * @param {Function | *} fpCreator
 * @return {Object}
 */
function register(sModuleId, aDependencies, fpCreator) {
  const oModules = getModules();
  if (isFunction(aDependencies)) {
    fpCreator = aDependencies;
    aDependencies = [ '$$_bus', '$$_module', '$$_log', 'gl_Hydra' ];
  }
  oModules[sModuleId] = new FakeModule(sModuleId, fpCreator);

  oModules[sModuleId].dependencies = aDependencies;
  return oModules[sModuleId];
}
/**
 * Method to set an instance of a module
 * @param {String} sModuleId
 * @param {String} sIdInstance
 * @param {Module} oInstance
 * @return {Module}
 */
function setInstance(sModuleId, sIdInstance, oInstance) {
  const oModules = getModules();
  var oModule = oModules[sModuleId];
  if (!oModule) {
    fpThrowErrorModuleNotRegistered(sModuleId, true);
  }
  oModule.instances[sIdInstance] = oInstance;
  return oModule;
}
/**
 * start more than one module at the same time.
 * @param {Object} oInstance
 * @param {Array<String>} aModulesIds
 * @param {String} sIdInstance
 * @param {Object} oData
 * @param {Boolean} bSingle
 * @private
 */
function _multiModuleStart(oInstance, aModulesIds, sIdInstance, oData, bSingle) {
  var aInstancesIds, aData, aSingle, nIndex, nLenModules, sModuleId;
  if (isArray(sIdInstance)) {
    aInstancesIds = copyArray(sIdInstance);
  }
  if (isArray(oData)) {
    aData = copyArray(oData);
  }
  if (isArray(bSingle)) {
    aSingle = copyArray(bSingle);
  }
  for (nIndex = 0, nLenModules = aModulesIds.length; nIndex < nLenModules; nIndex++) {
    sModuleId = aModulesIds[nIndex];
    sIdInstance = aInstancesIds && aInstancesIds[nIndex] || generateUniqueKey();
    oData = aData && aData[nIndex] || oData;
    bSingle = aSingle && aSingle[nIndex] || bSingle;
    startSingleModule(oInstance, sModuleId, sIdInstance, oData, bSingle);
  }
}
/**
 * Method to modify the init method to use it for extend.
 * @param {Object} oInstance
 * @param {Object} oData
 * @param {Boolean} bSingle
 * @private
 */
function beforeInit(oInstance, oData, bSingle) {
  iterateObject(oModifyInit, function (oMember) {
    if (oMember && isTypeOf(oMember, sFunctionType)) {
      oMember(oInstance, oData, bSingle);
    }
  });
}
/**
 * startSingleModule is the method that will initialize the module.
 * When start is called the module instance will be created and the init method is called.
 * If bSingle is true and the module is started the module will be stopped before instance it again.
 * This avoid execute the same listeners more than one time.
 * @param {Object} oWrapper
 * @param {String} sModuleId
 * @param {String} sIdInstance
 * @param {Object} oData
 * @param {Boolean} bSingle
 * @private
 */
function startSingleModule(oWrapper, sModuleId, sIdInstance, oData, bSingle) {
  const oModules = getModules();
  var oModule;
  oModule = oModules[sModuleId];
  if ( (bSingle && isModuleStarted(sModuleId)) || isModuleStarted(sModuleId, sIdInstance)) {
    oWrapper.stop(sModuleId, sIdInstance);
  }
  if (!isTypeOf(oModule, sNotDefined)) {
    createInstance(sModuleId, undefined, function (oInstance) {
      oModule.instances[sIdInstance] = oInstance;
      oInstance.__instance_id__ = sIdInstance;

      beforeInit(oInstance, oData, bSingle);

      if (!isTypeOf(oData, sNotDefined)) {
        oInstance.init(oData);
      } else {
        oInstance.init();
      }
    });
  } else {
    const ErrorHandler = errorHandler();
    ErrorHandler.error(new Error(), fpThrowErrorModuleNotRegistered(sModuleId));
  }
}

/**
 * Start only one module.
 * @param {Object} oInstance
 * @param {String} sModuleId
 * @param {String} sIdInstance
 * @param {Object} oData
 * @param {Boolean|*} bSingle
 * @private
 */
function _singleModuleStart(oInstance, sModuleId, sIdInstance, oData, bSingle) {
  if (!isTypeOf(sIdInstance, 'string')) {
    bSingle = oData;
    oData = sIdInstance;
    sIdInstance = generateUniqueKey();
  }

  startSingleModule(oInstance, sModuleId, sIdInstance, oData, bSingle);
}
/**
 * start is the method that initialize the module/s
 * If you use array instead of arrays you can start more than one module even adding the instance,
 * the data and if it must be executed as single module start.
 * @param {String|Array} oModuleId
 * @param {String|Array} [oIdInstance]
 * @param {Object|Array} [oData]
 * @param {Boolean|Array} [oSingle]
 */
function start(oModuleId, oIdInstance, oData, oSingle) {
  var bStartMultipleModules = isArray(oModuleId);

  if (bStartMultipleModules) {
    _multiModuleStart(this, copyArray(oModuleId), oIdInstance, oData, oSingle);
  } else {
    _singleModuleStart(this, oModuleId, oIdInstance, oData, oSingle);
  }
}
/**
 * createInstance is the method that will create the module instance and wrap the method if needed.
 * @param {String} sModuleId
 * @param {Array|undefined} [aDependencies]
 * @param {Function} fpCallback
 * @private
 */
function createInstance(sModuleId, aDependencies, fpCallback) {
  const oModules = getModules();
  if (isTypeOf(oModules[sModuleId], sNotDefined)) {
    fpThrowErrorModuleNotRegistered(sModuleId, true);
  }
  addPropertiesAndMethodsToModule(sModuleId, aDependencies, function (oInstance) {
    if (!getDebug()) {
      iterateObject(oInstance, function (oItem, sName) {
        if (isFunction(oItem)) {
          wrapMethod(oInstance, sName, sModuleId, oInstance[sName]);
        }
      });
    }
    fpCallback(oInstance);
  });
}
/**
 * Sets properties and methods from a template object.
 * @param {Object} oMethodsObject
 * @param {Object} oPropertiesObject
 * @returns {Function}
 */
function getCallbackToSetObjectFromTemplate(oMethodsObject, oPropertiesObject) {
  return function (oValue, sKey) {
    if (typeof oValue === 'function') {
      oMethodsObject[sKey] = getSimpleFunction(oValue);
    } else if (isArray(oValue)) {
      oPropertiesObject[sKey] = copyArray(oValue);
    } else if (typeof oValue === 'object' && oValue !== null ) {
      oPropertiesObject[sKey] = simpleMerge({}, oValue);
    } else if (isInstanceOf(oValue, Date)) {
      oPropertiesObject[sKey] = new Date();
      oPropertiesObject[sKey].setTime(oValue.getTime());
    } else {
      oPropertiesObject[sKey] = oValue;
    }
  };
}

/**
 * Method to extend modules using inheritance or decoration pattern
 * @param {String} sBaseModule
 * @param {String|Function} sModuleDecorated
 * @param {Array|Function} aDependencies
 * @param {Function} fpDecorator
 * @return {Promise}
 */
function extend(sBaseModule, sModuleDecorated, aDependencies, fpDecorator) {
  const oModules = getModules();
  var oModule = oModules[sBaseModule], oDecorated, oPromise;
  oPromise = getPromise();
  if (!oModule) {
    const ErrorHandler = errorHandler();
    ErrorHandler.log(fpThrowErrorModuleNotRegistered(sBaseModule));
    oPromise.resolve(null);
    return oPromise;
  }

  createInstance(sBaseModule, aDependencies, function (oInstance) {
    var oPromise2, aNoDependencies = [ '$$_bus', '$$_module', '$$_log', 'gl_Hydra' ];

    if (isTypeOf(sModuleDecorated, sFunctionType)) {
      fpDecorator = sModuleDecorated;
      sModuleDecorated = sBaseModule;
      aDependencies = aNoDependencies;
    }
    if (isTypeOf(aDependencies, sFunctionType)) {
      fpDecorator = aDependencies;
      aDependencies = aNoDependencies;
    }
    oPromise2 = resolveDependencies(sModuleDecorated, aDependencies);
    oPromise2.then(function () {
      const oModules = getModules();
      var oParentProperties = {},
        oParentMethods = {},
        Parent,
        Child;
      oModules[sModuleDecorated] = new FakeModule(sModuleDecorated, function () {
        var aDepends = [].slice.call(arguments);
        aDepends.push(oInstance);
        // If we extend the module with the different name, we
        // create proxy class for the original methods.

        oDecorated = fpDecorator.apply(fpDecorator, aDepends);

        if (isTypeOf(sBaseModule, 'string') && isTypeOf(sModuleDecorated, 'string')) {
          oInstance.__children__.push(oDecorated);
        }

        iterateObject(oInstance, getCallbackToSetObjectFromTemplate(oParentMethods, oParentProperties));

        Parent = function () {
          var self = this;
          iterateObject(oParentProperties, function (oValue, sKey) {
            self[sKey] = oValue;
          });
        };
        Parent.prototype = oParentMethods;

        Child = function () {
          var self = this, _super = oParentMethods;
          Parent.apply(self, arguments);

          _super.parentModule = sBaseModule;
          iterateObject(oDecorated, getCallbackToSetObjectFromTemplate(self, self));
          this.uber = _super;

          if (oInstance.uber) {
            _super.uber = {};
            iterateObject(oInstance.uber, function (oValue, sKey) {
              var fpCallback = getSimpleFunction(oValue, self);
              if (!self.uber[sKey]) {
                self.uber[sKey] = fpCallback;
              }
              // If the son does not have the method but the parent has then the son should have it too.
              _super.uber[sKey] = fpCallback;
            });
          }

          this.__children__ = [];
          this.__super__ = {
            __call__: function (sKey, aArgs) {
              return oInstance[sKey].apply(self, aArgs);
            }
          };
        };
        Child.prototype = new Parent();

        return new Child();
      });
      oModules[sModuleDecorated].dependencies = aDependencies;
      oPromise.resolve(oModules[sModuleDecorated]);
    });
  });
  return oPromise;
}
/**
 * Alias decorate to extend modules.
 * @return {Promise}
 */
function decorate() {
  return this.extend.apply(this, arguments);
}
/**
 * startAll is the method that will initialize all the registered modules.
 * @member Module.prototype
 */
function startAll() {
  const oModules = getModules();
  iterateObject(oModules, function (_oModule, sModuleId) {
    if (!isTypeOf(_oModule, sNotDefined)) {
      start(sModuleId, generateUniqueKey());
    }
  });
}
/**
 * stop is the method that will finish the module if it was registered and started.
 * When stop is called the module will call the destroy method and will nullify the instance.
 * @param {String} sModuleId
 * @param {String} [sInstanceId]
 * @return {Boolean}
 */
function stop(sModuleId, sInstanceId) {
  const oModules = getModules();
  var oModule;
  oModule = oModules[sModuleId];
  if (isTypeOf(oModule, sNotDefined)) {
    return false;
  }
  if (!isTypeOf(sInstanceId, sNotDefined)) {
    _singleModuleStop(oModule, sInstanceId);
  } else {
    _multiModuleStop(oModule);
  }
  return true;
}
/**
 * stop more than one module at the same time.
 * @param {Object} oModule
 * @private
 */
function _multiModuleStop(oModule) {
  iterateObject(oModule.instances, function (oInstance) {
    if (!isTypeOf(oModule, sNotDefined) && !isTypeOf(oInstance, sNotDefined)) {
      oInstance.destroy();
    }
  });
  oModule.instances = {};
}
/**
 * Stop only one module.
 * @param {Object} oModule
 * @param {String} sInstanceId
 * @private
 */
function _singleModuleStop(oModule, sInstanceId) {
  var oInstance = oModule.instances[sInstanceId];
  if (!isTypeOf(oModule, sNotDefined) && !isTypeOf(oInstance, sNotDefined)) {
    oInstance.destroy();
    delete oModule.instances[sInstanceId];
  }
}
/**
 * stopAll is the method that will finish all the registered and started modules.
 */
function stopAll() {
  const oModules = getModules();
  iterateObject(oModules, function (_oModule, sModuleId) {
    if (!isTypeOf(_oModule, sNotDefined)) {
      _stopOneByOne(_oModule, sModuleId);
    }
  });
}
/**
 * Loops over instances of modules to stop them.
 * @param {Object} oModule
 * @param {String} sModuleId
 * @private
 */
function _stopOneByOne(oModule, sModuleId) {
  iterateObject(oModule.instances, function (oItem, sInstanceId) {
    stop(sModuleId, sInstanceId);
  });
}
/**
 * remove is the method that will remove the full module from the oModules object
 * @param {String} sModuleId
 * @return {*}
 */
function remove(sModuleId) {
  const oModules = getModules();
  var oModule = oModules[sModuleId];
  if (isTypeOf(oModule, sNotDefined)) {
    return null;
  }
  if (!isTypeOf(oModule, sNotDefined)) {
    try {
      return Module;
    }
    finally {
      _delete(sModuleId);
      createMapping(getMappingMaps(), 'hm_', oModules);
    }
  }
  return null;
}
/**
 * _delete is a wrapper method that will call the native delete javascript function
 * It's important to test the full code.
 * @param {String} sModuleId
 * @return {Boolean}
 */
function _delete(sModuleId) {
  const oModules = getModules();
  if (!isTypeOf(oModules[sModuleId], sNotDefined)) {
    delete oModules[sModuleId];
    return true;
  }
  return false;
}
/**
 * Stop all the running modules and cleans all the stored modules.
 */
function reset() {
  stopAll();
  resetModules();
  createMapping(getMappingMaps(), 'hm_', getModules());
}

/**
 * Class to manage the modules.
 * @constructor
 * @class Module
 * @name Module
 * @private
 */
const Module = {
  __super__,
  instances,
  __type__,
  type,
  getInstance,
  setInstance,
  setVars,
  resetVars,
  getVars,
  extend,
  decorate,
  isModuleStarted,
  register,
  start,
  startAll,
  stop,
  stopAll,
  remove,
  reset
};

export default Module;