Source: Tracker/plugins.js

/**
 * @class
 * @classdesc Plugins Management.
 * @name PluginsManager
 * @param parent {object} Instance of the Tag used
 * @public
 */
var PluginsManager = function (parent) {
    'use strict';
    var self = this,
        pluginsLoaded = {},

        /**
         * Dictionary indexed by plugin names indicating those currently being lazyloaded (true) and
         * those that were in this case (false).
         * example:
         * {
         *  'Page': true,
         *  'Click': false,
         *  'SalesTracker': true
         * }
         * Here Page, Click and SalesTracker are "Lazyloadable" and all were called. Page and SalesTracker are still loading
         * @private
         */
        pluginsInLazyloading = {},

        /**
         * Gives the number of plugins loading
         * @private
         */
        pluginsInLazyloadingCount = 0,

        /**
         * Dictionary indexed by plugin names indicating those that have one or more methods
         * which have been called and are waiting for execution (awaiting the end of plugin loading)
         * example:
         * {
         *  'Page': 3,
         *  'SalesTracker': 1
         * }
         * Page and SalesTracker have not yet been loaded but some of their methods have been called.
         * Three times for the page (this may be the same method) once for SalesTracker
         * @private
         */
        ExecWaitingLazyloading = {},

        /**
         * Gives the number of methods waiting to be executed
         * @private
         */
        ExecWaitingLazyloadingCount = 0;

    /**
     * Load a plugin.
     * @name load
     * @memberof PluginsManager#
     * @function
     * @param name {string} Plugin's name
     * @param Plugin {function} Plugin's code
     * @returns parent {object} Instance of the Tag used
     * @public
     */
    var _load = self['load'] = function (name, Plugin) {
        if (typeof(Plugin) === 'function') {
            var allowed = (typeof parent.getConfig.plgAllowed === 'undefined') ||
                (parent.getConfig.plgAllowed.length === 0) ||
                (parent.getConfig.plgAllowed.indexOf(name) > -1);
            if (allowed) {
                pluginsLoaded[name] = new Plugin(parent);
                _lazyloadingFinished(name);//dans le cas où ce plugin a été chargé en différé
                parent.emit('Tracker:Plugin:Load:' + name + ':Ok', {lvl: 'INFO'});
            }
            else {
                parent.emit('Tracker:Plugin:Load:' + name + ':Error', {
                    lvl: 'ERROR',
                    msg: 'Plugin not allowed',
                    details: {}
                });
            }
        }
        else {
            parent.emit('Tracker:Plugin:Load:' + name + ':Error', {
                lvl: 'ERROR',
                msg: 'not a function',
                details: {obj: Plugin}
            });
        }
        return parent;
    };

    /**
     * Unload a plugin.
     * @name unload
     * @memberof PluginsManager#
     * @function
     * @param name {string} Plugin's name
     * @returns parent {object} Instance of the Tag used
     * @public
     */
    var _unload = self['unload'] = function (name) {
        if (_isLoaded(name)) {
            pluginsLoaded[name] = undefined;
            parent.emit('Tracker:Plugin:Unload:' + name + ':Ok', {lvl:'INFO'});
        }
        else {
            parent.emit('Tracker:Plugin:Unload:' + name + ':Error', {lvl: 'ERROR', msg: 'not a known plugin'});
        }
        return parent;
    };

    /**
     * Check if a plugin is loaded.
     * @memberof PluginsManager#
     * @param name {string} Plugin's name
     * @returns true|false {boolean} True if the plugin is loaded
     * @private
     */
    var _isLoaded = function (name) {
        if (pluginsLoaded[name]) {
            return true;
        } else {
            return false;
        }
    };

    /**
     * Check if some plugin is loading.
     * @name isLazyloading
     * @memberof PluginsManager#
     * @function
     * @param name {string} Specific plugin's name
     * @returns true|false {boolean} True if one or the specific plugin is loading
     * @public
     */
    var _isLazyloading = self['isLazyloading'] = function (name) {
        if (name) {
            return pluginsInLazyloading[name] === true;
        } else {
            return pluginsInLazyloadingCount !== 0;
        }

    };

    /**
     * Lazyload a plugin if possible.
     * @memberof PluginsManager#
     * @param name {string} Plugin's name
     * @returns true|false {boolean} False if the plugin can't be loaded (not planned for, already loaded or already in progress).
     * @private
     */
    var _lazyLoadIfPossible = function (name) {
        if (_isLazyLoadable(name)) {
            _lazyLoad(name);
            return true;
        } else {
            return false;
        }
    };

    /**
     * Ask the lazyloading of a plugin.
     * @memberof PluginsManager#
     * @param name {string} Plugin's name
     * @private
     */
    var _lazyLoad = function (name) {
        pluginsInLazyloading[name] = true;
        pluginsInLazyloadingCount++;
        ATInternet.Utils.loadScript({url: parent.getConfig('lazyLoadingPath') + name + '.js'});
    };

    /**
     * Mark a plugin's loading as finished (if exists).
     * @memberof PluginsManager#
     * @param name {string} Plugin's name
     * @private
     */
    var _lazyloadingFinished = function (name) {
        if (pluginsInLazyloading[name] && _isLoaded(name)) {
            pluginsInLazyloading[name] = false;
            pluginsInLazyloadingCount--;
            if (_isLoaded(name + "_ll")) {
                _unload(name + "_ll");
            }
            if (pluginsInLazyloadingCount === 0) {
                parent.emit('Tracker:Plugin:Lazyload:File:Complete', {lvl: 'INFO', msg: 'LazyLoading triggers are finished'});
            }
        }
    };

    /**
     * Indicate if the plugin is lazyloadable and not not loaded yet.
     * @memberof PluginsManager#
     * @param name {string} Plugin's name
     * @returns {boolean}
     * @private
     */
    var _isLazyLoadable = function (name) {
        return !_isLoaded(name) && !_isLazyloading(name) && _isLoaded(name + '_ll');
    };

    /**
     * Add a marker showing the method's call of a plugin which is lazyloading.
     * @memberof PluginsManager#
     * @param plg {string} Plugin's name
     * @private
     */
    var _addExecWaitingLazyloading = function (plg) {
        if (!ExecWaitingLazyloading[plg]) {
            ExecWaitingLazyloading[plg] = 1;
        } else {
            ExecWaitingLazyloading[plg]++;
        }
        ExecWaitingLazyloadingCount++;
    };

    /**
     * Indicate that at least one method is waiting the end of its plugin's lazyloading.
     * @name isExecWaitingLazyloading
     * @memberof PluginsManager#
     * @function
     * @returns {number}
     * @public
     */
    var _IsExecWaitingLazyloading = self["isExecWaitingLazyloading"] = function () {
        return ExecWaitingLazyloadingCount !== 0;
    };

    /**
     * Delete a marker showing the method's call of a plugin which is lazyloading.
     * @memberof PluginsManager#
     * @param plg {string} Plugin's name
     * @private
     */
    var _delExecWaitingLazyloading = function (plg) {
        ExecWaitingLazyloading[plg]--;
        ExecWaitingLazyloadingCount--;
        if (ExecWaitingLazyloadingCount === 0) {
            parent.emit('Tracker:Plugin:Lazyload:Exec:Complete', {lvl: 'INFO', msg: 'All exec waiting for lazyloading are done'});
        }
    };

    /**
     * Execute a plugin method.
     * @name exec
     * @memberof PluginsManager#
     * @function
     * @param pluginName {string} Plugin (name) that you want to use
     * @param method {string} Plugin's method that you want to use
     * @param args {Array} Arguments to the method you want to use
     * @param callback {function} Method which be triggered once the plugin's method has finished, it will take one argument which is what the plugin's method returned
     * @public
     */
    parent['exec'] = self['exec'] = function (pluginName, method, args, callback) {
        var result = null;
        var _process = function (plg, mt, arg, cb) {
            var tb = mt.split('.');
            if (_isLoaded(plg) && pluginsLoaded[plg][tb[0]]) {
                if ((tb.length > 1) && pluginsLoaded[plg][tb[0]][tb[1]]) {
                    result = pluginsLoaded[plg][tb[0]][tb[1]].apply(pluginsLoaded[plg], arg);
                }
                else {
                    result = pluginsLoaded[plg][tb[0]].apply(pluginsLoaded[plg], arg);
                }
            }
            cb && cb(result);
        };

        var _waiting_lazyloadin_process = function (pluginName, method, args, callback) {
            _addExecWaitingLazyloading(pluginName);
            parent['onTrigger']('Tracker:Plugin:Load:' + pluginName + ':Ok', function () {
                _process(pluginName, method, args, function (result) {
                    _delExecWaitingLazyloading(pluginName);
                    callback && callback(result);
                });
            }, true);
        };

        if (_isLazyLoadable(pluginName)) {
            _waiting_lazyloadin_process(pluginName, method, args, callback);
            _lazyLoad(pluginName);
        }
        else if (_isLazyloading(pluginName)) {
            _waiting_lazyloadin_process(pluginName, method, args, callback);
        }
        else {
            _process(pluginName, method, args, callback);
        }
    };

    /**
     * Execute a callback only when all required plugins are loaded.
     * @name waitForDependencies
     * @memberof PluginsManager#
     * @function
     * @param dependencies {Array} Names of the required plugins
     * @param callback {function} Method which be triggered once the dependencies will be loaded
     * @public
     */
    self['waitForDependencies'] = function (dependencies, callback) {
        var _missingDependencies = function (listDep) {
            var missing = {'mcount': 0, 'plugins': {}};
            for (var i = 0; i < listDep.length; i++) {
                if (!pluginsLoaded.hasOwnProperty(listDep[i])) {
                    missing.mcount++;
                    missing.plugins[listDep[i]] = true;
                }
            }
            return missing;
        };

        var missing = _missingDependencies(dependencies);

        if (missing.mcount === 0) {
            parent.emit('Tracker:Plugin:Dependencies:Loaded',{
                lvl:'INFO',
                details:{dependencies: dependencies}
            });
            callback();
        } else {
            for (var name in missing.plugins) {
                if (missing.plugins.hasOwnProperty(name)) {
                    parent.emit('Tracker:Plugin:Dependencies:Error',{
                        lvl:'WARNING',
                        msg:'Missing plugin ' + name
                    });
                    //on s'inscrit sur l'évnènement de chargement du plugin manquant
                    parent['onTrigger']('Tracker:Plugin:Load:' + name, function (trig, data) {
                        var trigArr = trig.split(":");
                        var plugName = trigArr[3];
                        var state = trigArr[4];
                        if (state === 'Ok') {
                            missing.plugins[plugName] = false;
                            missing.mcount--;
                            //si tous les plugins attendus ont tous été chargé on lance le callback
                            if (missing.mcount === 0) {
                                callback();
                            }
                        } else { //'Error'
                            //TODO
                        }
                    }, true);
                    //Si le plugins est lazyloadable (et qu'il n'est pas en cours de lazylaoding) on demande son chargement
                    _lazyLoadIfPossible(name);
                }
            }
        }
    };

    /**
     * Create an instance of each plugin already in the prototype.
     * Essential for the inclusion of the plugins already loaded.
     * @name init
     * @memberof PluginsManager#
     * @function
     * @public
     */
    self['init'] = function () {
        for (var key in ATInternet.Tracker.pluginProtos) {
            if (ATInternet.Tracker.pluginProtos.hasOwnProperty(key)) {
                _load(key, ATInternet.Tracker.pluginProtos[key]);
            }
        }
    };

    /* @if test */
    self['isLoaded'] = _isLoaded;
    self['pluginsLoaded'] = function() {return pluginsLoaded};
    /* @endif */
};