text: 'use strict';
  
  var helpers = require('../helpers/index');
  
  /**
   * Helper function to get relative position for an event
   * @param {Event|IEvent} event - The event to get the position for
   * @param {Chart} chart - The chart
   * @returns {object} the event position
   */
  function getRelativePosition(e, chart) {
  	if (e.native) {
  		return {
  			x: e.x,
  			y: e.y
  		};
  	}
  
  	return helpers.getRelativePosition(e, chart);
  }
  
  /**
   * Helper function to traverse all of the visible elements in the chart
   * @param {Chart} chart - the chart
   * @param {function} handler - the callback to execute for each visible item
   */
  function parseVisibleItems(chart, handler) {
  	var metasets = chart._getSortedVisibleDatasetMetas();
  	var metadata, i, j, ilen, jlen, element;
  
  	for (i = 0, ilen = metasets.length; i < ilen; ++i) {
  		metadata = metasets[i].data;
  		for (j = 0, jlen = metadata.length; j < jlen; ++j) {
  			element = metadata[j];
  			if (!element._view.skip) {
  				handler(element);
  			}
  		}
  	}
  }
  
  /**
   * Helper function to get the items that intersect the event position
   * @param {ChartElement[]} items - elements to filter
   * @param {object} position - the point to be nearest to
   * @return {ChartElement[]} the nearest items
   */
  function getIntersectItems(chart, position) {
  	var elements = [];
  
  	parseVisibleItems(chart, function(element) {
  		if (element.inRange(position.x, position.y)) {
  			elements.push(element);
  		}
  	});
  
  	return elements;
  }
  
  /**
   * Helper function to get the items nearest to the event position considering all visible items in teh chart
   * @param {Chart} chart - the chart to look at elements from
   * @param {object} position - the point to be nearest to
   * @param {boolean} intersect - if true, only consider items that intersect the position
   * @param {function} distanceMetric - function to provide the distance between points
   * @return {ChartElement[]} the nearest items
   */
  function getNearestItems(chart, position, intersect, distanceMetric) {
  	var minDistance = Number.POSITIVE_INFINITY;
  	var nearestItems = [];
  
  	parseVisibleItems(chart, function(element) {
  		if (intersect && !element.inRange(position.x, position.y)) {
  			return;
  		}
  
  		var center = element.getCenterPoint();
  		var distance = distanceMetric(position, center);
  		if (distance < minDistance) {
  			nearestItems = [element];
  			minDistance = distance;
  		} else if (distance === minDistance) {
  			// Can have multiple items at the same distance in which case we sort by size
  			nearestItems.push(element);
  		}
  	});
  
  	return nearestItems;
  }
  
  /**
   * Get a distance metric function for two points based on the
   * axis mode setting
   * @param {string} axis - the axis mode. x|y|xy
   */
  function getDistanceMetricForAxis(axis) {
  	var useX = axis.indexOf('x') !== -1;
  	var useY = axis.indexOf('y') !== -1;
  
  	return function(pt1, pt2) {
  		var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
  		var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
  		return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
  	};
  }
  
  function indexMode(chart, e, options) {
  	var position = getRelativePosition(e, chart);
  	// Default axis for index mode is 'x' to match old behaviour
  	options.axis = options.axis || 'x';
  	var distanceMetric = getDistanceMetricForAxis(options.axis);
  	var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
  	var elements = [];
  
  	if (!items.length) {
  		return [];
  	}
  
  	chart._getSortedVisibleDatasetMetas().forEach(function(meta) {
  		var element = meta.data[items[0]._index];
  
  		// don't count items that are skipped (null data)
  		if (element && !element._view.skip) {
  			elements.push(element);
  		}
  	});
  
  	return elements;
  }
  
  /**
   * @interface IInteractionOptions
   */
  /**
   * If true, only consider items that intersect the point
   * @name IInterfaceOptions#boolean
   * @type Boolean
   */
  
  /**
   * Contains interaction related functions
   * @namespace Chart.Interaction
   */
  module.exports = {
  	// Helper function for different modes
  	modes: {
  		single: function(chart, e) {
  			var position = getRelativePosition(e, chart);
  			var elements = [];
  
  			parseVisibleItems(chart, function(element) {
  				if (element.inRange(position.x, position.y)) {
  					elements.push(element);
  					return elements;
  				}
  			});
  
  			return elements.slice(0, 1);
  		},
  
  		/**
  		 * @function Chart.Interaction.modes.label
  		 * @deprecated since version 2.4.0
  		 * @todo remove at version 3
  		 * @private
  		 */
  		label: indexMode,
  
  		/**
  		 * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something
  		 * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item
  		 * @function Chart.Interaction.modes.index
  		 * @since v2.4.0
  		 * @param {Chart} chart - the chart we are returning items from
  		 * @param {Event} e - the event we are find things at
  		 * @param {IInteractionOptions} options - options to use during interaction
  		 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
  		 */
  		index: indexMode,
  
  		/**
  		 * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something
  		 * If the options.intersect is false, we find the nearest item and return the items in that dataset
  		 * @function Chart.Interaction.modes.dataset
  		 * @param {Chart} chart - the chart we are returning items from
  		 * @param {Event} e - the event we are find things at
  		 * @param {IInteractionOptions} options - options to use during interaction
  		 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
  		 */
  		dataset: function(chart, e, options) {
  			var position = getRelativePosition(e, chart);
  			options.axis = options.axis || 'xy';
  			var distanceMetric = getDistanceMetricForAxis(options.axis);
  			var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
  
  			if (items.length > 0) {
  				items = chart.getDatasetMeta(items[0]._datasetIndex).data;
  			}
  
  			return items;
  		},
  
  		/**
  		 * @function Chart.Interaction.modes.x-axis
  		 * @deprecated since version 2.4.0. Use index mode and intersect == true
  		 * @todo remove at version 3
  		 * @private
  		 */
  		'x-axis': function(chart, e) {
  			return indexMode(chart, e, {intersect: false});
  		},
  
  		/**
  		 * Point mode returns all elements that hit test based on the event position
  		 * of the event
  		 * @function Chart.Interaction.modes.intersect
  		 * @param {Chart} chart - the chart we are returning items from
  		 * @param {Event} e - the event we are find things at
  		 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
  		 */
  		point: function(chart, e) {
  			var position = getRelativePosition(e, chart);
  			return getIntersectItems(chart, position);
  		},
  
  		/**
  		 * nearest mode returns the element closest to the point
  		 * @function Chart.Interaction.modes.intersect
  		 * @param {Chart} chart - the chart we are returning items from
  		 * @param {Event} e - the event we are find things at
  		 * @param {IInteractionOptions} options - options to use
  		 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
  		 */
  		nearest: function(chart, e, options) {
  			var position = getRelativePosition(e, chart);
  			options.axis = options.axis || 'xy';
  			var distanceMetric = getDistanceMetricForAxis(options.axis);
  			return getNearestItems(chart, position, options.intersect, distanceMetric);
  		},
  
  		/**
  		 * x mode returns the elements that hit-test at the current x coordinate
  		 * @function Chart.Interaction.modes.x
  		 * @param {Chart} chart - the chart we are returning items from
  		 * @param {Event} e - the event we are find things at
  		 * @param {IInteractionOptions} options - options to use
  		 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
  		 */
  		x: function(chart, e, options) {
  			var position = getRelativePosition(e, chart);
  			var items = [];
  			var intersectsItem = false;
  
  			parseVisibleItems(chart, function(element) {
  				if (element.inXRange(position.x)) {
  					items.push(element);
  				}
  
  				if (element.inRange(position.x, position.y)) {
  					intersectsItem = true;
  				}
  			});
  
  			// If we want to trigger on an intersect and we don't have any items
  			// that intersect the position, return nothing
  			if (options.intersect && !intersectsItem) {
  				items = [];
  			}
  			return items;
  		},
  
  		/**
  		 * y mode returns the elements that hit-test at the current y coordinate
  		 * @function Chart.Interaction.modes.y
  		 * @param {Chart} chart - the chart we are returning items from
  		 * @param {Event} e - the event we are find things at
  		 * @param {IInteractionOptions} options - options to use
  		 * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
  		 */
  		y: function(chart, e, options) {
  			var position = getRelativePosition(e, chart);
  			var items = [];
  			var intersectsItem = false;
  
  			parseVisibleItems(chart, function(element) {
  				if (element.inYRange(position.y)) {
  					items.push(element);
  				}
  
  				if (element.inRange(position.x, position.y)) {
  					intersectsItem = true;
  				}
  			});
  
  			// If we want to trigger on an intersect and we don't have any items
  			// that intersect the position, return nothing
  			if (options.intersect && !intersectsItem) {
  				items = [];
  			}
  			return items;
  		}
  	}
  };
  