text: 'use strict';
  
  var defaults = require('./defaults');
  var helpers = require('../helpers/index');
  
  defaults._set('global', {
  	plugins: {}
  });
  
  /**
   * The plugin service singleton
   * @namespace Chart.plugins
   * @since 2.1.0
   */
  module.exports = {
  	/**
  	 * Globally registered plugins.
  	 * @private
  	 */
  	_plugins: [],
  
  	/**
  	 * This identifier is used to invalidate the descriptors cache attached to each chart
  	 * when a global plugin is registered or unregistered. In this case, the cache ID is
  	 * incremented and descriptors are regenerated during following API calls.
  	 * @private
  	 */
  	_cacheId: 0,
  
  	/**
  	 * Registers the given plugin(s) if not already registered.
  	 * @param {Array|Object} plugins plugin instance(s).
  	 */
  	register: function(plugins) {
  		var p = this._plugins;
  		([]).concat(plugins).forEach(function(plugin) {
  			if (p.indexOf(plugin) === -1) {
  				p.push(plugin);
  			}
  		});
  
  		this._cacheId++;
  	},
  
  	/**
  	 * Unregisters the given plugin(s) only if registered.
  	 * @param {Array|Object} plugins plugin instance(s).
  	 */
  	unregister: function(plugins) {
  		var p = this._plugins;
  		([]).concat(plugins).forEach(function(plugin) {
  			var idx = p.indexOf(plugin);
  			if (idx !== -1) {
  				p.splice(idx, 1);
  			}
  		});
  
  		this._cacheId++;
  	},
  
  	/**
  	 * Remove all registered plugins.
  	 * @since 2.1.5
  	 */
  	clear: function() {
  		this._plugins = [];
  		this._cacheId++;
  	},
  
  	/**
  	 * Returns the number of registered plugins?
  	 * @returns {Number}
  	 * @since 2.1.5
  	 */
  	count: function() {
  		return this._plugins.length;
  	},
  
  	/**
  	 * Returns all registered plugin instances.
  	 * @returns {Array} array of plugin objects.
  	 * @since 2.1.5
  	 */
  	getAll: function() {
  		return this._plugins;
  	},
  
  	/**
  	 * Calls enabled plugins for `chart` on the specified hook and with the given args.
  	 * This method immediately returns as soon as a plugin explicitly returns false. The
  	 * returned value can be used, for instance, to interrupt the current action.
  	 * @param {Object} chart - The chart instance for which plugins should be called.
  	 * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
  	 * @param {Array} [args] - Extra arguments to apply to the hook call.
  	 * @returns {Boolean} false if any of the plugins return false, else returns true.
  	 */
  	notify: function(chart, hook, args) {
  		var descriptors = this.descriptors(chart);
  		var ilen = descriptors.length;
  		var i, descriptor, plugin, params, method;
  
  		for (i = 0; i < ilen; ++i) {
  			descriptor = descriptors[i];
  			plugin = descriptor.plugin;
  			method = plugin[hook];
  			if (typeof method === 'function') {
  				params = [chart].concat(args || []);
  				params.push(descriptor.options);
  				if (method.apply(plugin, params) === false) {
  					return false;
  				}
  			}
  		}
  
  		return true;
  	},
  
  	/**
  	 * Returns descriptors of enabled plugins for the given chart.
  	 * @returns {Array} [{ plugin, options }]
  	 * @private
  	 */
  	descriptors: function(chart) {
  		var cache = chart.$plugins || (chart.$plugins = {});
  		if (cache.id === this._cacheId) {
  			return cache.descriptors;
  		}
  
  		var plugins = [];
  		var descriptors = [];
  		var config = (chart && chart.config) || {};
  		var options = (config.options && config.options.plugins) || {};
  
  		this._plugins.concat(config.plugins || []).forEach(function(plugin) {
  			var idx = plugins.indexOf(plugin);
  			if (idx !== -1) {
  				return;
  			}
  
  			var id = plugin.id;
  			var opts = options[id];
  			if (opts === false) {
  				return;
  			}
  
  			if (opts === true) {
  				opts = helpers.clone(defaults.global.plugins[id]);
  			}
  
  			plugins.push(plugin);
  			descriptors.push({
  				plugin: plugin,
  				options: opts || {}
  			});
  		});
  
  		cache.descriptors = descriptors;
  		cache.id = this._cacheId;
  		return descriptors;
  	},
  
  	/**
  	 * Invalidates cache for the given chart: descriptors hold a reference on plugin option,
  	 * but in some cases, this reference can be changed by the user when updating options.
  	 * https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
  	 * @private
  	 */
  	_invalidate: function(chart) {
  		delete chart.$plugins;
  	}
  };
  
  /**
   * Plugin extension hooks.
   * @interface IPlugin
   * @since 2.1.0
   */
  /**
   * @method IPlugin#beforeInit
   * @desc Called before initializing `chart`.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Object} options - The plugin options.
   */
  /**
   * @method IPlugin#afterInit
   * @desc Called after `chart` has been initialized and before the first update.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Object} options - The plugin options.
   */
  /**
   * @method IPlugin#beforeUpdate
   * @desc Called before updating `chart`. If any plugin returns `false`, the update
   * is cancelled (and thus subsequent render(s)) until another `update` is triggered.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Object} options - The plugin options.
   * @returns {Boolean} `false` to cancel the chart update.
   */
  /**
   * @method IPlugin#afterUpdate
   * @desc Called after `chart` has been updated and before rendering. Note that this
   * hook will not be called if the chart update has been previously cancelled.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Object} options - The plugin options.
   */
  /**
   * @method IPlugin#beforeDatasetsUpdate
   * @desc Called before updating the `chart` datasets. If any plugin returns `false`,
   * the datasets update is cancelled until another `update` is triggered.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Object} options - The plugin options.
   * @returns {Boolean} false to cancel the datasets update.
   * @since version 2.1.5
  */
  /**
   * @method IPlugin#afterDatasetsUpdate
   * @desc Called after the `chart` datasets have been updated. Note that this hook
   * will not be called if the datasets update has been previously cancelled.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Object} options - The plugin options.
   * @since version 2.1.5
   */
  /**
   * @method IPlugin#beforeDatasetUpdate
   * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin
   * returns `false`, the datasets update is cancelled until another `update` is triggered.
   * @param {Chart} chart - The chart instance.
   * @param {Object} args - The call arguments.
   * @param {Number} args.index - The dataset index.
   * @param {Object} args.meta - The dataset metadata.
   * @param {Object} options - The plugin options.
   * @returns {Boolean} `false` to cancel the chart datasets drawing.
   */
  /**
   * @method IPlugin#afterDatasetUpdate
   * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note
   * that this hook will not be called if the datasets update has been previously cancelled.
   * @param {Chart} chart - The chart instance.
   * @param {Object} args - The call arguments.
   * @param {Number} args.index - The dataset index.
   * @param {Object} args.meta - The dataset metadata.
   * @param {Object} options - The plugin options.
   */
  /**
   * @method IPlugin#beforeLayout
   * @desc Called before laying out `chart`. If any plugin returns `false`,
   * the layout update is cancelled until another `update` is triggered.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Object} options - The plugin options.
   * @returns {Boolean} `false` to cancel the chart layout.
   */
  /**
   * @method IPlugin#afterLayout
   * @desc Called after the `chart` has been layed out. Note that this hook will not
   * be called if the layout update has been previously cancelled.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Object} options - The plugin options.
   */
  /**
   * @method IPlugin#beforeRender
   * @desc Called before rendering `chart`. If any plugin returns `false`,
   * the rendering is cancelled until another `render` is triggered.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Object} options - The plugin options.
   * @returns {Boolean} `false` to cancel the chart rendering.
   */
  /**
   * @method IPlugin#afterRender
   * @desc Called after the `chart` has been fully rendered (and animation completed). Note
   * that this hook will not be called if the rendering has been previously cancelled.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Object} options - The plugin options.
   */
  /**
   * @method IPlugin#beforeDraw
   * @desc Called before drawing `chart` at every animation frame specified by the given
   * easing value. If any plugin returns `false`, the frame drawing is cancelled until
   * another `render` is triggered.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
   * @param {Object} options - The plugin options.
   * @returns {Boolean} `false` to cancel the chart drawing.
   */
  /**
   * @method IPlugin#afterDraw
   * @desc Called after the `chart` has been drawn for the specific easing value. Note
   * that this hook will not be called if the drawing has been previously cancelled.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
   * @param {Object} options - The plugin options.
   */
  /**
   * @method IPlugin#beforeDatasetsDraw
   * @desc Called before drawing the `chart` datasets. If any plugin returns `false`,
   * the datasets drawing is cancelled until another `render` is triggered.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
   * @param {Object} options - The plugin options.
   * @returns {Boolean} `false` to cancel the chart datasets drawing.
   */
  /**
   * @method IPlugin#afterDatasetsDraw
   * @desc Called after the `chart` datasets have been drawn. Note that this hook
   * will not be called if the datasets drawing has been previously cancelled.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
   * @param {Object} options - The plugin options.
   */
  /**
   * @method IPlugin#beforeDatasetDraw
   * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets
   * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing
   * is cancelled until another `render` is triggered.
   * @param {Chart} chart - The chart instance.
   * @param {Object} args - The call arguments.
   * @param {Number} args.index - The dataset index.
   * @param {Object} args.meta - The dataset metadata.
   * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
   * @param {Object} options - The plugin options.
   * @returns {Boolean} `false` to cancel the chart datasets drawing.
   */
  /**
   * @method IPlugin#afterDatasetDraw
   * @desc Called after the `chart` datasets at the given `args.index` have been drawn
   * (datasets are drawn in the reverse order). Note that this hook will not be called
   * if the datasets drawing has been previously cancelled.
   * @param {Chart} chart - The chart instance.
   * @param {Object} args - The call arguments.
   * @param {Number} args.index - The dataset index.
   * @param {Object} args.meta - The dataset metadata.
   * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
   * @param {Object} options - The plugin options.
   */
  /**
   * @method IPlugin#beforeTooltipDraw
   * @desc Called before drawing the `tooltip`. If any plugin returns `false`,
   * the tooltip drawing is cancelled until another `render` is triggered.
   * @param {Chart} chart - The chart instance.
   * @param {Object} args - The call arguments.
   * @param {Object} args.tooltip - The tooltip.
   * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
   * @param {Object} options - The plugin options.
   * @returns {Boolean} `false` to cancel the chart tooltip drawing.
   */
  /**
   * @method IPlugin#afterTooltipDraw
   * @desc Called after drawing the `tooltip`. Note that this hook will not
   * be called if the tooltip drawing has been previously cancelled.
   * @param {Chart} chart - The chart instance.
   * @param {Object} args - The call arguments.
   * @param {Object} args.tooltip - The tooltip.
   * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
   * @param {Object} options - The plugin options.
   */
  /**
   * @method IPlugin#beforeEvent
   * @desc Called before processing the specified `event`. If any plugin returns `false`,
   * the event will be discarded.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {IEvent} event - The event object.
   * @param {Object} options - The plugin options.
   */
  /**
   * @method IPlugin#afterEvent
   * @desc Called after the `event` has been consumed. Note that this hook
   * will not be called if the `event` has been previously discarded.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {IEvent} event - The event object.
   * @param {Object} options - The plugin options.
   */
  /**
   * @method IPlugin#resize
   * @desc Called after the chart as been resized.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Number} size - The new canvas display size (eq. canvas.style width & height).
   * @param {Object} options - The plugin options.
   */
  /**
   * @method IPlugin#destroy
   * @desc Called after the chart as been destroyed.
   * @param {Chart.Controller} chart - The chart instance.
   * @param {Object} options - The plugin options.
   */
  