'use strict';

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

/**
 *+------------------------------------------------------------------------+
 *| Licensed Materials - Property of IBM
 *| IBM Cognos Products: Dashboard
 *| (C) Copyright IBM Corp. 2017, 2020
 *|
 *| US Government Users Restricted Rights - Use, duplication or disclosure
 *| restricted by GSA ADP Schedule Contract with IBM Corp.
 *+------------------------------------------------------------------------+
 */
define(['jquery', 'underscore', '../../lib/@waca/dashboard-common/dist/utils/EventChainLocal', '../../apiHelpers/SlotAPIHelper', './VisSelectionInfo', './VisTooltipInfo', '../../util/DashboardFormatter', '../interactions/lassoSelect/LassoController', '../../features/content/visualizationGesture/api/VisualizationGestureAPI', '../../lib/@waca/dashboard-common/dist/core/APIFactory'], function ($, _, EventChainLocal, SlotAPIHelper, VisSelectionInfo, VisTooltipInfo, Formatter, LassoController, VisualizationGestureAPI, APIFactory) {
	'use strict';

	/////////////////////
	// VisEventHandler
	//
	// options : {
	// 		target: {
	//			getTargetElement(): (JQuery element of the target view which the events will be handled.),
	//			getEventTargets(event): (Retrieve a list of event target objects. See EventTarget.),
	//			getTargetsByCords(cords, summarize) (Retrieve an array of targets base on the specified co-ordintates),
	//			decorateTarget(targets, name): (Decorate the given target.),
	//			getDecoration(target, name): (Obtain the decoration value of the given target),
	//			completeDecorations: (End of decorations.)
	//			applyCustomDataSelection(target): Apply custom data selection to target.
	//			getCustomDataSelections(items): Get the list of custom selections
	//			canZoom(): Determines whether the target supports zoom.
	//			zoom(event): Apply zoom to the target with the given event.
	//			zoomStart(event): Begin the zooming session
	//			zoomEnd(event): End the zooming session
	//			pan(event): Apply pan movements to the target
	//			panStart(event): Begin the panning session
	//			panEnd(event): End the panning session
	//		},
	//		visAPI: (Instance of VisAPI)
	// }
	//
	// EventTarget: {
	//		key: (A unique key representing a target.)
	//		type: (Target type. datapoint | item | itemclass)
	//		source: (original source object of the target.)
	//		values[]: (values of the target in mapped order.)
	//		datasetId:  (dataset id of origainl source object)
	// }
	//

	var VisEventHandler = function () {
		function VisEventHandler(options) {
			_classCallCheck(this, VisEventHandler);

			this._init(options);
		}

		VisEventHandler.prototype._init = function _init(options) {
			this._oTimerOut = null;
			this._lastSelected = null;
			this._lastHighlighted = null;
			this._lastImpliedSelection = [];

			this.dashboard = options.dashboard;
			this.logger = options.logger;
			this._target = options.target;
			this._targetElement = this._target.getTargetElement();

			this._ownerWidget = options.ownerWidget;
			this.content = this._ownerWidget.content;
			this.state = this.content.getFeature('state');
			this.visualization = this.content.getFeature('Visualization');
			this._visAPI = options.visAPI;
			this._visController = this.content.getFeature('InteractivityController.deprecated');
			this._selector = this.content.getFeature('DataPointSelections');
			this._transaction = options.transaction;

			this._visActionHelper = this._visController ? this._visController.getActionHelper() : null;

			// use jQuery touchstart, touchend and touchmove to handle panning to avoid issues with hammerjs drag/start/end
			var panAndZoomEvents = [{
				'key': 'mousedown',
				'callback': this.onMouseDown.bind(this)
			}, {
				'key': 'mouseup',
				'callback': this.onMouseUp.bind(this)
			}, {
				'key': 'mousewheel wheel',
				'callback': this.onMouseWheel.bind(this)
			}, {
				'key': 'touchstart',
				'callback': this.onClickTapPanStart.bind(this)
			}, {
				'key': 'touchend',
				'callback': this.onPanEnd.bind(this)
			}, {
				'key': 'touchmove',
				'callback': this.onPanMove.bind(this)
			}, {
				'key': 'transformstart',
				'callback': this.onPinchStart.bind(this),
				'hammer': true
			}, {
				'key': 'transform',
				'callback': this.onPinch.bind(this),
				'hammer': true
			}, {
				'key': 'transformend',
				'callback': this.onPinchEnd.bind(this),
				'hammer': true
			}];

			var hoverEvents = [{
				'key': 'mousemove touchmove',
				'callback': this.onHover.bind(this)
			}, {
				'key': 'mouseleave touchend',
				'callback': this.onMouseLeave.bind(this)
			}];

			var clickEvents = [{
				'key': 'contextmenu',
				'callback': this.onClick.bind(this)
			}, {
				'key': 'clicktap',
				'callback': this.onClickTapPanStart.bind(this),
				'data': { allowPropagationDefaultAction: true }
			}, {
				'key': 'hold',
				'callback': this.onHold.bind(this),
				'hammer': true
			}];

			var keyEvents = [{
				'key': 'keydown',
				'callback': this.onKeyDown.bind(this)
			}];

			this._visControlEvents = [];

			var interactivitySettings = this._visAPI.getInteractivitySettings();

			if (!interactivitySettings.isClickDisabled) {
				this._visControlEvents.push.apply(this._visControlEvents, clickEvents);
				this._visControlEvents.push.apply(this._visControlEvents, keyEvents);
			}

			if (!interactivitySettings.isPanAndZoomDisabled) {
				this._visControlEvents.push.apply(this._visControlEvents, panAndZoomEvents);
			}

			if (!interactivitySettings.isHoverDisabled) {
				this._visControlEvents.push.apply(this._visControlEvents, hoverEvents);
			}
			this.serializeInteractivity = !!interactivitySettings.serialize;

			this._edgeSelection = !!options.edgeSelection;
			this._registerEvents();
			this.lassoCtrl = new LassoController({
				targetElement: this._targetElement,
				cordinateResolver: this._resolveCoordinates,
				isDragging: this._isDragging.bind(this),
				dashboard: this.dashboard
			});

			// TODO: Remove this check after contentFeatureLoader is available
			// in live widgets in conversationalPanel
			if (this._ownerWidget.contentFeatureLoader) {
				// Add VisualizationGesture feature
				this._ownerWidget.contentFeatureLoader.registerFeature(this._ownerWidget.id, 'VisualizationGesture', this);
			}
		};

		VisEventHandler.prototype.getAPI = function getAPI() {
			if (!this.api) {
				this.api = APIFactory.createAPI(this, [VisualizationGestureAPI]);
			}
			return this.api;
		};

		/**
   * Handle the app configuration for serialized interactions.
   * Applications may configure the interactivity of the livewidget
   * from the perspective file with the following configuration
   *
   * config: {
   * 		interactions: {
   * 			serialize: true
   * 		}
   * }
   */


		VisEventHandler.prototype.isReadyToInteract = function isReadyToInteract() {
			if (this.serializeInteractivity) {
				return this.state.getStatus() === this.state.STATUS.RENDERED;
			}
			// default: interactivity is always ready
			return true;
		};

		/**
   * Restore the interactive state (ie. zoom, pan) to the visualization
   */


		VisEventHandler.prototype.restoreState = function restoreState() {
			if (this._target.canRestore()) {
				this._target.restore(this._visAPI.getPropertyValue('interactivityState'));
			}
		};

		/**
   * Remove the handler from target
   */


		VisEventHandler.prototype.remove = function remove() {
			var _this = this;

			$(document).off('mouseup', this._onMouseUpHandler);
			$(document).off('touchend', this._onTouchEndHandler);
			//Release events from the RAVE control.
			if (this._targetElement) {
				this._visControlEvents.forEach(function (eventInfo) {
					if (eventInfo.hammer) {
						_this._targetElement.hammer().off(eventInfo.key, eventInfo.callback);
					} else {
						_this._targetElement.off(eventInfo.key, null, eventInfo.callback);
					}
				});
			}

			//prevent from memory leak
			if (this._target) {
				this._target.remove();
				this._target = null;
			}

			this._lastHoverEvent = null;
			this._tooltipItems = null;
			this._lastHighlighted = null;

			this.content = null;

			if (this.lassoCtrl) {
				this._endLassoSelection();
				this.lassoCtrl.remove();
				this.lassoCtrl = null;
			}
		};

		/**
   * Register the events to the target
   */


		VisEventHandler.prototype._registerEvents = function _registerEvents() {
			var _this2 = this;

			this._onMouseUpHandler = this._onDocumentMouseUp.bind(this);
			$(document).on('mouseup', this._onMouseUpHandler);

			this._onTouchEndHandler = this._onDocumentTouchEnd.bind(this);
			$(document).on('touchend', this._onTouchEndHandler);

			if (this._targetElement) {
				this._visControlEvents.forEach(function (eventInfo) {
					if (eventInfo.hammer) {
						_this2._targetElement.hammer(eventInfo.hammerOption).on(eventInfo.key, eventInfo.callback);
					} else {
						if (eventInfo.data) {
							_this2._targetElement.on(eventInfo.key, null, eventInfo.data, eventInfo.callback);
						} else {
							_this2._targetElement.on(eventInfo.key, null, eventInfo.callback);
						}
					}
				});
			}
		};
		/**
   * Handle mouse hover on Desktop and touchmove event on Mobile
   * If hover and pan are both enabled, on Mobile touchmove is binded both two event handlers
   * Both handlers will run and will execute in the order in which they were bound.
   * For example, in Explore crosshair and pan are both enabled.
   * For charts whose pan direction is left/right and crosshair is top/bottom, touching move along the diagonal of the chart results
   *  a) pan the chart at left/right direction
   *  b) move the crosshair at top/bottom direction
   * and vice versa.
   *
   * @param event 	Event Object
   */


		VisEventHandler.prototype.onHover = function onHover(event) {
			if (!event.clientX) {
				this._resolveCoordinates(event);
			}
			// pan and zoom takes priority
			if (this._isDragging(event) && !this.lassoCtrl.isLassoMode()) {

				if (this._target.canZoom() && this._target.pan) {
					this._target.pan(event);
				}
				// This flag is used to detect if we moved the mouse while the mouse is down so that we can ignore the vis selection
				// Without this, if we pan while we are on top of an element, then the pan is done after the mouse is up, the click is invoked and we select the element
				this._skipSelection = true;
			} else {
				this._skipSelection = !this.isReadyToInteract();
				this._skipHoverover = this._skipSelection;
			}

			var items = this._target && this._target.getEventTargets ? this._target.getEventTargets(event) : [];
			if (this._stopHoverover) {
				// only apply pointer cursor, no tooltips
				if (this._targetElement) {
					if (items && items.length > 0) {
						this._targetElement.addClass('cursor-pointer');
					} else {
						this._targetElement.removeClass('cursor-pointer');
					}
				}

				return;
			}

			// If the mouse is down while it's moving we should never show the tooltip or toolbar actions
			if (this._mouseDown) {
				this._clearHoverTimer();
				this._visActionHelper.hideToolbarActions();
			}

			this._handleImpliedSelections(items, event.ctrlKey || event.metaKey, false);
			this._handleHighlights(items);
			this._handleTooltips(event, items);
			this._triggerWidgetEvent('visevent:onmove', event, this._target, items, { mouseDown: this._mouseDown, onHold: this._onHold });
		};

		/**
   * Handle mouseleave on Desktop and touchmove event on Mobile
   *
   * @param event	Event object
   */


		VisEventHandler.prototype.onMouseLeave = function onMouseLeave(event) {
			if (!event.clientX) {
				this._resolveCoordinates(event);
			}
			var items = this._target && this._target.getEventTargets ? this._target.getEventTargets(event) : [];

			this._lastHoverEvent = event;
			this._tooltipItems = items;

			if (this._tooltipItems && this._tooltipItems.length > 0) {
				this._clearHoverTimer();
			} else if (this._lastTooltip) {
				this._lastTooltip = null;
				this._clearHoverTimer();
				this._oTimerOut = setTimeout(this._handleMouseleaveTimeout.bind(this), 500);
			}
		};

		VisEventHandler.prototype._handleMouseleaveTimeout = function _handleMouseleaveTimeout() {
			if (!this._stopHoverover) {
				this._visActionHelper.hideToolbarActions();
			}
		};

		/**
   * Handle either click or tap event
   *
   * @param event	Event object
   */


		VisEventHandler.prototype.onClickTap = function onClickTap(event) {
			return event && event.type === 'tap' ? this.onTap(event) : this.onClick(event);
		};

		/**
   * Handle mouse click event
   *
   * @param event	Event object
   */


		VisEventHandler.prototype.onClick = function onClick(event) {
			if (this._skipSelection) {
				this.setEventLocalProperty(event, 'preventDefaultContextBar', true);
			} else {
				var _getOnClickData2 = this._getOnClickData(event),
				    shouldHandle = _getOnClickData2.shouldHandle,
				    items = _getOnClickData2.items,
				    isMultiSelect = _getOnClickData2.isMultiSelect;

				if (shouldHandle) {
					if (items) {
						if (items.length > 0) {
							this._handleSelections(event, items, { isMultiSelect: isMultiSelect });
							event.preventDefault();
							this.setEventLocalProperty(event, 'preventDefaultContextBar', true);
						} else {
							//prevent tooltip showing so it does not win over ODT
							this._clearHoverTimer();
							this._clearAll(event);
						}
					}
				}
			}
		};

		/**
   * We do not need to check for this._skipSelection because it is only used onHover.
   * We do not need to close the toolbar because we can only open a new one after closing the current one
   * when using the keyboard.
   */


		VisEventHandler.prototype.onKeyDown = function onKeyDown(event) {
			if (this._target) {
				var result = this._target.processKeyDown(event);
				if (result) {
					var items = this._target.getEventTargets(event);
					if (items && items.length > 0) {
						this._handleSelections(event, items, { forceRightClick: true });
						event.preventDefault();
						this.setEventLocalProperty(event, 'preventDefaultContextBar', true);
					}
				}
			}
		};

		/**
   * Returns the onClick data
  		 *
   * 1. Left Click + isLassoActive :  Ignore
   * 2. Left Click + !isLassoActive:  Return the clicked item only
   * 3. Right Click + isLassoActive:  Return selectedItemCache
   * 4. Right Click + !isLassoActive
   * 		a. User clicked on an already selected item :  Return the selectedItemCache
   * 		b. User clicked on a non-selected item      :  Return event targets
   *
   * @param event Event object
   */


		VisEventHandler.prototype._getOnClickData = function _getOnClickData(event) {
			var items = this._target && this._target.getEventTargets ? this._target.getEventTargets(event) : [];
			var isRightClick = event.type === 'contextmenu';
			var ignoreCase = !isRightClick && this.lassoCtrl.isLassoMode();
			var isMultiSelect = !ignoreCase && items && items.length > 1;
			return { shouldHandle: !ignoreCase, items: items, isMultiSelect: isMultiSelect };
		};

		/**
   * Handle mobile tap event
   *
   * @param event	Event object
   */


		VisEventHandler.prototype.onTap = function onTap(event) {
			this._resolveCoordinates(event);

			if (this._skipSelection) {
				this.setEventLocalProperty(event, 'preventDefaultContextBar', true);
			} else {
				var items = this._target && this._target.getEventTargets ? this._target.getEventTargets(event) : [];
				if (items) {
					if (items.length > 0) {
						var bSelected = this._isItemsSelected(items);
						var bHighlighted = this._isItemsHighlighed(items);
						this.setEventLocalProperty(event, 'preventDefaultContextBar', true);
						// if the target items are either highlighted or selected then toggle the selection
						// Otherwise highlight and force a tooltip.
						if (bSelected || bHighlighted) {
							// force to toggle / append selections (aka. CTRL + left-click)
							this._handleSelections(event, items, { forceCtrlClick: true });
						} else {
							this._handleHighlights(items);
							// force to show the tooltips
							this._handleTooltips(event, items, true);
						}
					} else {
						this._clearAll(event);
					}
				}
			}
		};

		/**
   * Handle mobile hold (long press) event
   */


		VisEventHandler.prototype.onHold = function onHold(event) {
			this._resolveCoordinates(event);
			var isMultiSelect = false;

			this._onHold = true;
			var items = void 0;
			if (this.lassoCtrl.isLassoMode()) {
				items = this._target && this._target.getTargetsByCords ? this._target.getTargetsByCords(this.lassoCtrl.getCoordinates(), false) : [];
				isMultiSelect = true;
			} else {
				items = this._target && this._target.getEventTargets ? this._target.getEventTargets(event) : [];
			}

			// treat it like an right-click
			this._handleSelections(event, items, { forceRightClick: true, isMultiSelect: isMultiSelect });
		};

		/**
   * Store the visualization Interactivity state
   */


		VisEventHandler.prototype._storeInteractivityState = function _storeInteractivityState(state) {
			// store the state as a visualization property
			this._ownerWidget.updateVisProperties({
				id: 'interactivityState',
				value: state
			}, {
				silent: true
			});
		};

		/**
   * Handle mouse wheel scroll event
   *
   * @param event Event object
   */


		VisEventHandler.prototype.onMouseWheel = function onMouseWheel(event) {
			if (this._target.canZoom() && this._target.zoom) {
				this._target.zoomStart(event);
				this._target.zoom(event);
				var state = this._target.zoomEnd(event);
				if (state) {
					this._storeInteractivityState(state);
				}

				event.preventDefault();
			}
		};

		/**
   * Handle transformstart to handle beginning of zoom-in/out
   */


		VisEventHandler.prototype.onPinchStart = function onPinchStart(event) {
			if (this._target.canZoom() && this._target.zoom) {
				this._resolveCoordinates(event);
				// Pan gesture starts due to the touch event that occurs before the pinch
				// explicitly cancel the pan gesture before starting the zoom gesture
				this._target.panEnd(event);
				this._target.zoomStart(event);
			}
		};

		/**
   * Handle transform gesture to zoom-in/out
   */


		VisEventHandler.prototype.onPinch = function onPinch(event) {
			if (this._target.canZoom() && this._target.zoom) {
				this._resolveCoordinates(event);
				this._target.zoom(event);
				// Prevent synthetic events now that a touch gesture is guaranteed
				(event.gesture || event.originalEvent || event).preventDefault();
			}
		};

		/**
   * Handle transformend to handle the end of zoom-in/out
   */


		VisEventHandler.prototype.onPinchEnd = function onPinchEnd(event) {
			if (this._target.canZoom() && this._target.zoom) {
				this._resolveCoordinates(event);
				var state = this._target.zoomEnd(event);
				if (state) {
					this._storeInteractivityState(state);
				}
			}
		};

		/**
   * Handle either the tap or touchstart
   */


		VisEventHandler.prototype.onClickTapPanStart = function onClickTapPanStart(event) {
			// avoid clicktap being cancelled out by touchstart
			return event.type === 'touchstart' ? this.onPanStart(event) : this.onClickTap(event);
		};

		/**
   * Handle dragstart to handle beginning of pan
   */


		VisEventHandler.prototype.onPanStart = function onPanStart(event) {
			if (this._canPan(event)) {
				this._resolveCoordinates(event);
				this._target.panStart(event);
				// Do not prevent synthetic events on touchstart/touchend as some visualizations use click events with no touch fallback
			}
		};

		/**
   * Handle dragend to handle the end of pan
   */


		VisEventHandler.prototype.onPanEnd = function onPanEnd(event) {
			this._resolveCoordinates(event);
			var state = this._target.panEnd(event);
			if (state) {
				this._storeInteractivityState(state);
			}
			// Do not prevent synthetic events on touchstart/touchend as some visualizations use click events with no touch fallback
		};

		/**
   * Handle touchmove to handle pan
   */


		VisEventHandler.prototype.onPanMove = function onPanMove(event) {
			var originalEvent = event.gesture || event.originalEvent || event;
			var isZoom = originalEvent.touches && event.originalEvent.touches.length === 2;
			if (this._canPan(event)) {
				this._resolveCoordinates(event);
				this._target.pan(event);
				// Prevent synthetic events now that a touch gesture is guaranteed
				originalEvent.preventDefault();
			} else if (isZoom) {
				// When user pinches with two touches, both 'transform' and 'touchmove' events are fired.
				// In order to prevent the entire page from zooming in and out,
				// Prevent default for all double touch gestures which are considered as zoom
				originalEvent.preventDefault();
			}
		};

		/**
   * Handle mouse down event, fired before click
   *
   * @param event	Event object
   */


		VisEventHandler.prototype.onMouseDown = function onMouseDown(event) {
			var _getOnClickData3 = this._getOnClickData(event),
			    shouldHandle = _getOnClickData3.shouldHandle,
			    items = _getOnClickData3.items;

			if (shouldHandle && !!items && items.length > 0) {
				// Multiselect (Defect #219253): If there are items under the point,
				// set key on eventChain to tell dashboard-core to not deselect
				this.setEventLocalProperty(event, 'preventWidgetDeselect', true);
			}

			this._skipSelection = false;
			this._mouseDown = {
				X: event.pageX,
				Y: event.pageY
			};

			if (!this.lassoCtrl.isLassoMode() && this._target.canZoom && this._target.canZoom() && this._target.panStart) {
				this._target.panStart(event);
			}
		};

		/**
   * Handle mouse up event
   */


		VisEventHandler.prototype.onMouseUp = function onMouseUp(event) {
			if (this._target.canZoom && this._target.canZoom() && this._target.panEnd) {
				var state = this._target.panEnd(event);
				if (state) {
					this._storeInteractivityState(state);
				}
			}
		};

		VisEventHandler.prototype.getEdgeSelection = function getEdgeSelection() {
			return this._edgeSelection;
		};

		/**
   * Handle mouse up event
   */


		VisEventHandler.prototype._onDocumentMouseUp = function _onDocumentMouseUp() {
			this._mouseDown = false;
		};

		/**
   * Handle touch end event on mobile
   */


		VisEventHandler.prototype._onDocumentTouchEnd = function _onDocumentTouchEnd() {
			if (this._onHold) {
				this._onHold = false;
			}
		};

		/**
   * Determine whether the target can pan
   *
   * @param event 	Event object
   * @return true if the target can pan with the given event, otherwise false
   */


		VisEventHandler.prototype._canPan = function _canPan(event) {
			return this._target.canZoom && this._target.canZoom() && event.originalEvent.touches.length === 1 && !this.lassoCtrl.isLassoMode();
		};

		/**
   * Resolve the coordinates from the given event
   *
   * @param event 	Event object
   * @return object containing the resolved x / y coordinates
   */


		VisEventHandler.prototype._resolveCoordinates = function _resolveCoordinates(event) {
			var touches = null;
			if (event.targetTouches) {
				touches = event.targetTouches;
			} else if (event.originalEvent && event.originalEvent.targetTouches) {
				touches = event.originalEvent.targetTouches;
			} else if (event.gesture) {
				touches = event.gesture.touches;
			}
			var isTouch = touches && touches.length;

			if (isTouch) {
				var coords = touches && touches.length ? touches[0] : null;
				// touchend does not have coordinates
				if (coords && coords.clientX !== undefined) {
					event.isTouch = true;
					event.clientX = coords.clientX;
					event.clientY = coords.clientY;
					event.pageX = coords.pageX;
					event.pageY = coords.pageY;
				}
			} else {
				if (event.gesture && event.gesture.center) {
					event.clientX = event.clientX || event.gesture.center.clientX;
					event.clientY = event.clientY || event.gesture.center.clientY;
					event.pageX = event.pageX || event.gesture.center.pageX;
					event.pageY = event.pageY || event.gesture.center.pageY;
				} else if (event.originalEvent) {
					event.clientX = event.clientX || event.originalEvent.clientX || event.originalEvent.pageX;
					event.clientY = event.clientY || event.originalEvent.clientY || event.originalEvent.pageY;
					event.pageX = event.pageX || event.originalEvent.pageX;
					event.pageY = event.pageY || event.originalEvent.pageY;
				}
			}
		};

		/**
   * Get the tooltip bound (coordinates) based on the given event
   *
   * @param event	Event object
   */


		VisEventHandler.prototype._getBoundsInfoFromEvent = function _getBoundsInfoFromEvent(event) {
			return {
				top: event.clientY,
				height: 1,
				left: event.clientX - 10, // Want the tooltip to show a little to the left of the mouse
				width: 20,
				parent: document.body
			};
		};

		/**
   * Set the event local property
   *
   * @param event	Event object
   * @param name	Property name
   * @param value	Property value
   */


		VisEventHandler.prototype.setEventLocalProperty = function setEventLocalProperty(event, name, value) {
			var eventChainLocal = new EventChainLocal(event);
			eventChainLocal.setProperty(name, value);
		};

		/**
   * Determine whether the mouse is dragging
   *
   * @param event	Event object
   */


		VisEventHandler.prototype._isDragging = function _isDragging(event) {
			if (event.type === 'touchmove' && this.lassoCtrl.isLassoMode()) {
				return event.originalEvent.touches.length === 1;
			}

			return this._mouseDown && (Math.abs(this._mouseDown.X - event.pageX) > 5 || Math.abs(this._mouseDown.Y - event.pageY) > 5);
		};

		/**
   * Clear the hover timer
   */


		VisEventHandler.prototype._clearHoverTimer = function _clearHoverTimer() {
			if (this._oTimerOut !== null) {
				clearTimeout(this._oTimerOut);
				this._oTimerOut = null;
			}
		};

		/**
   * Clear the last highlight
   */


		VisEventHandler.prototype._clearLastHighlight = function _clearLastHighlight() {
			if (this._lastHighlighted) {
				// clear previous highlighted item decorations
				this._clearDecorations(this._lastHighlighted, 'highlight');
			}
			this._lastHighlighted = undefined;
			this._skipHoverover = false;
		};

		/**
   * Clear deocrations
   *
   * @param items	Event target items
   * @param name	Decoration name, Clears all decorations if name is undefined
   */


		VisEventHandler.prototype._clearDecorations = function _clearDecorations(items, name) {
			if (this._target && this._target.decorateTarget) {
				this._target.decorateTarget(items, name, false);
			}
		};

		/**
   * Compare two event targets
   *
   * @param items1	event targets 1
   * @param items2	event targets 2
   * @return true if two targets are the same, otherwise false
   */


		VisEventHandler.prototype._compareItems = function _compareItems(items1, items2) {
			var keys1 = _.pluck(items1, 'key');
			var keys2 = _.pluck(items2, 'key');
			return _.difference(keys1, keys2).length === 0 && _.difference(keys2, keys1).length === 0;
		};

		/**
   * Determines whether the given target items are currently highlighted
   *
   * @param items		event target items
   * @return true if the target items are highlighted, otherwise false
   */


		VisEventHandler.prototype._isItemsHighlighed = function _isItemsHighlighed(items) {
			return this._compareItems(this._lastHighlighted, items);
		};

		/**
   * Determines whether the given target items are currently implicitly selected
   *
   * @param items		event target items
   * @return true if the target items are implicitly selected, otherwise false
   */


		VisEventHandler.prototype._isItemsImpliedSelected = function _isItemsImpliedSelected(items) {
			return _.find(this._lastImpliedSelection, function (impliedSelection) {
				return impliedSelection === items;
			});
		};

		/**
   * Handle hover tooltip event.
   * Show a delayed (500ms) tooltip on chart elements
   *
   * @param event	Event object
   * @param items	Event target items
   */


		VisEventHandler.prototype._handleTooltips = function _handleTooltips(event, items, forceTooltips) {
			var _this3 = this;

			var prevTooltip = this._lastTooltip ? this._lastTooltip : [];
			this._lastHoverEvent = event;
			this._tooltipItems = items;

			if (this._tooltipItems && this._tooltipItems.length > 0) {
				// keep track of the selected item
				this._targetElement.addClass('cursor-pointer');
				if (!this._compareItems(prevTooltip, this._tooltipItems)) {
					this._clearHoverTimer();
					this._oTimerOut = setTimeout(function () {
						_this3._clearHoverTimer();

						var itemExist = _this3._tooltipItems && _this3._tooltipItems.length > 0;
						var allowHover = !_this3._stopHoverover && !_this3._skipHoverover;
						var force = forceTooltips;

						if (itemExist && (allowHover || force)) {

							var aSelectionInfo = _this3._getDataPayload(_this3._tooltipItems, VisTooltipInfo);
							if (aSelectionInfo) {
								var infoShown = _this3._showSelectionInfo(aSelectionInfo, _this3._lastHoverEvent, false, true, _this3._drillOnly, /*noDrillthrough*/false, {
									isHover: true
								});
								if (infoShown) {
									_this3._lastTooltip = _this3._tooltipItems;
								}
								_this3._lastHoverEvent = null;
								_this3._tooltipItems = null;
							}
						}
					}, 500);
				}
			} else {
				this._targetElement.removeClass('cursor-pointer');
				if (this._lastTooltip) {
					this._lastTooltip = null;
					this._clearHoverTimer();
					this._oTimerOut = setTimeout(function () {
						_this3._clearHoverTimer();
						if (!_this3._stopHoverover) {
							_this3._visActionHelper.hideToolbarActions();
						}
					}, 500);
				}
			}
		};

		/**
   * Handle hightlight event.
   * Highlight the chart element upon hover.
   *
   * @param items	Event target items
   * @param bAppend Append items in this._lastHighlighted to items if true
   */


		VisEventHandler.prototype._handleHighlights = function _handleHighlights(items, bAppend) {
			var prevHighlight = this._lastHighlighted ? this._lastHighlighted : [];

			if (bAppend) {
				var nonSelected = _.filter(this._lastHighlighted, function (obj) {
					return obj.source && obj.source.getDecoration && !obj.source.getDecoration('selected');
				});
				items.push.apply(items, nonSelected);
			}

			if (items && items.length > 0) {
				if (!this._isItemsHighlighed(items)) {
					if (this._target && this._target.decorateTarget) {
						// clear previous highlights
						this._clearLastHighlight();
						if (this._target.decorateTarget(items, 'highlight')) {
							//Keep track of the newly highlighted items
							this._lastHighlighted = items;
						}
					}
				}
			} else if (prevHighlight.length > 0) {
				// clear the previous selection
				this._clearLastHighlight();
			}

			// re-render with the new highlight
			if (!this._compareItems(prevHighlight, this._lastHighlighted) && this._target.completeDecorations) {
				this._target.completeDecorations();
			}
		};

		/**
   * Handle selection event.
   * Selection highlight the chart element upon click.
   *
   * @param items	Event target items
   * @param {boolean} [options.forceCtrlClick=fasle] - True forces a control + click selection
   * @param {boolean} [options.forceRightClick=fasle] - True forces a right click selection
   * @param {boolean} [options.isMultiSelect=false] - True indicates multiple datapoints are selected.
   */


		VisEventHandler.prototype._handleSelections = function _handleSelections(event, items, options) {
			var bCtrlClick = options.forceCtrlClick || event.ctrlKey || event.metaKey;
			var bRightClick = options.forceRightClick || event.type === 'contextmenu';
			this._skipHoverover = false;
			var resolved = null;
			if (bRightClick) {
				// Skip the hover to avoid tooltips winning over contextmenu
				this._skipHoverover = true;

				// handle right click
				var oNewSelection = this._getDataPayload(items, VisTooltipInfo);
				resolved = this._resolveItems(items);
				if (resolved.length > 0) {
					var _options = items[0].selectionContext ? {
						slots: items[0].selectionContext.slots,
						mapIndex: items[0].selectionContext.mapIndex,
						actions: items[0].selectionContext.actions
					} : {};
					_options.area = items[0].area;
					_options.isAreaSelected = items[0].isAreaSelected;
					var includeApplyFilter = void 0,
					    noFilters = void 0,
					    noDrillthrough = void 0;
					if (items[0].onlyGlobalActions) {
						// Only want to show actions applicable to the full visualization, e.g. TextAction
						includeApplyFilter = false;
						noFilters = true;
						noDrillthrough = true;
					} else {
						includeApplyFilter = bRightClick;
						noFilters = false;
						noDrillthrough = false;
					}
					this._showSelectionInfo(oNewSelection, event, includeApplyFilter, noFilters, undefined, noDrillthrough, _options);
					this._triggerWidgetEvent('visevent:select', event, this._target, items, { selectionInfo: oNewSelection, append: bCtrlClick, lasso: options.isMultiSelect });

					this._handleHighlights(items, bCtrlClick);

					return this._select(resolved, /*select*/true, bCtrlClick, /*pending*/true, items[0].isEdgeSelect, options.isMultiSelect);
				} else {
					// show title actions on right-click
					this._titleActions(event, items);
				}
			} else {
				resolved = this._resolveItems(items);
				this._handleHighlights(items);
				if (resolved.length > 0) {
					var isSelected = this._isSelected(resolved, items[0].isEdgeSelect);

					// When the's select is IDENTICAL to the existing selection, cancel selection (doSelect=false)
					// otherwise, set selection to match what the user clicked - even if what the user clicked contains or is contained by existing selection
					var doSelect = !isSelected;
					var doAppend = bCtrlClick || this.lassoCtrl.isLassoMode() && isSelected;
					var oSelectionInfo = this._getDataPayload(items);
					this._triggerWidgetEvent('visevent:select', event, this._target, items, { selectionInfo: oSelectionInfo, append: doAppend, lasso: options.isMultiSelect });
					return this._select(resolved, doSelect, doAppend, /*pending*/false, items[0].isEdgeSelect, options.isMultiSelect);
				} else if (items && items.length >= 1) {
					var type = items[0].type;

					switch (type) {
						case 'customdata':
							return this._handleCustomDataSelections(items, event);
						case 'itemclass':
							return this._handleImpliedSelections(items, bCtrlClick, true);
						case 'summary':
							return this._handleImpliedSelections(items, bCtrlClick, true);
						default:
							break;
					}
				}
			}
		};

		VisEventHandler.prototype._isSelectionsWithCustomData = function _isSelectionsWithCustomData(itemsArray) {
			var aResult = _.filter(itemsArray, function (item) {
				return item.type !== 'customdata';
			});
			return itemsArray.length > 0 && (!aResult || aResult.length === 0); //Means all selections are custom data selections
		};

		/**
   * Handle implied selection event. (Similar to _handleHighlights; used to decorate
   *	target with implied_selection instead of highlight). On hover, if it's not a multi-select,
   *	remove the implied_selection styling on the previous implied selections.
   *
   * @param items	Event target items
   * @param isMultiSelect {Boolean} True if multiselect action is occuring (ctrl + click)
   * @param apply {Boolean} True if the decoration should be applied
   */


		VisEventHandler.prototype._handleImpliedSelections = function _handleImpliedSelections(items, isMultiSelect, apply) {
			var prevImpliedSelection = this._lastImpliedSelection ? this._lastImpliedSelection : [];

			if (apply && items && items.length > 0) {
				if (!this._isItemsImpliedSelected(items)) {
					// Add target items to array of implied selections
					this._lastImpliedSelection.push(items);
					this._target.decorateTarget(items, 'implied_selection', apply, isMultiSelect);
				}
			} else if (prevImpliedSelection.length > 0 && !isMultiSelect) {
				// Clear the previous implied selections
				this._lastImpliedSelection = [];
				this._clearDecorations(items, 'implied_selection');
			}
		};

		VisEventHandler.prototype._handleCustomDataSelections = function _handleCustomDataSelections(items, event) {
			this._triggerWidgetEvent('visevent:customdataselection', event, this._target, items);
			if (this._target && this._target.applyCustomDataSelection) {
				var currentSelectedIds = _.pluck(this._visAPI.getDecoratedCustomData('selected'), 'id');
				this._target.applyCustomDataSelection(_.filter(items, function (item) {
					return !_.contains(currentSelectedIds, item.key);
				}));
			}
		};

		/**
   * Resolve the event target items to data item ids and tuples items
   *
   * @param items		Event target items
   * @return resolved array of object containing the data item ids and tuple items
   */


		VisEventHandler.prototype._resolveItems = function _resolveItems(items) {
			var _this4 = this;

			var resolved = [];
			//The value index corresponds to the slot index
			_.each(items, function (item) {
				if (item.type === 'datapoint') {
					resolved = resolved.concat(_this4._resolveDataPointItem(item));
				} else if (item.type === 'item') {
					resolved = resolved.concat(_this4._resolveEdgeItem(item));
				}
			});
			return resolved;
		};

		/**
   * Add the event target item to the selections info object
   *
   * @param selections	Selection info object
   * @param item			Event target item
   */


		VisEventHandler.prototype._processDataPointSelection = function _processDataPointSelection(selections, item) {
			var _this5 = this;

			var slots = this._getMappedSlotListByItem(item);
			_.each(item.values, function (rowdata, valueIndex) {
				var slot = item.slotAPIs ? item.slotAPIs[valueIndex] : slots[valueIndex];
				if (slot) {
					var valueSpans = 0;
					_.each(rowdata, function (value, itemInSlotIndex) {
						if (value === null) {
							value = Formatter.format(null);
						}
						var slotDataItem = _this5._getSlotDataItem(slot, item, itemInSlotIndex + valueSpans);
						if (value.span) {
							//Any parts of a value that span multiple levels are considered "unbalanced".
							//values in the tuple beyond the span should be associated with items beyond the span
							valueSpans += value.span - 1;
						}
						if (slotDataItem && ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) !== 'object' || !SlotAPIHelper.isMultiMeasuresSeriesOrValueDataItem(slotDataItem))) {
							selections.addSelection(slot, slotDataItem, value, item.isEdgeSelect);
						}
					});
				}
			});
		};

		VisEventHandler.prototype._resolveDataPointItem = function _resolveDataPointItem(item) {
			var slots = this._getMappedSlotListByItem(item);
			var resolved = [];
			_.each(item.values, function (value, valueIndex) {
				var slot = item.slotAPIs ? item.slotAPIs[valueIndex] : slots[valueIndex];
				if (slot) {
					var dataItems = slot.getDataItemList();
					_.each(value, function (v, itemInSlotIndex) {
						var dataItem = dataItems[itemInSlotIndex];
						if (dataItem && dataItem.getType() === 'attribute' && !SlotAPIHelper.isMultiMeasuresSeriesOrValueDataItem(dataItem)) {
							resolved.push({
								dataItemId: dataItem.getId(),
								itemId: dataItem.getColumnId(),
								tupleItem: v
							});
						}
					});
				}
			});
			return [resolved];
		};

		/**
   * Expand categories assigned of an edge to include dataItem information.
   */


		VisEventHandler.prototype._resolveEdgeItem = function _resolveEdgeItem(item) {
			var slots = this._getMappedSlotListByItem(item);
			var resolved = [];
			//The values array within each item should be length 2 for edge items (first item is category, second is measure value)
			_.each(item.values, function (value, valueIndex) {
				var slot = slots[valueIndex];
				if (slot) {
					var dataItems = slot.getDataItemList();
					_.each(value, function (v, itemIndex) {
						var dataItem = dataItems[itemIndex];
						if (!SlotAPIHelper.isMultiMeasuresSeriesOrValueDataItem(dataItem) && dataItem.getType() === 'attribute') {
							resolved.push({
								dataItemId: dataItem.getId(),
								itemId: dataItem.getColumnId(),
								tupleItem: v
							});
						}
					});
				}
			});
			return resolved;
		};

		/**
   * Add the event target item to the selections info object
   *
   * @param selections	Selection info object
   * @param item			Event target item
   */


		VisEventHandler.prototype._processEdgeSelection = function _processEdgeSelection(selections, item) {
			var _this6 = this;

			var slots = this._getMappedSlotListByItem(item);
			if (item.values) {
				_.each(item.values, function (value, valueIndex, values) {
					if (value) {
						for (var i = 0; i < value.length; i++) {
							if (typeof value[i] !== 'undefined') {
								var slot = slots[valueIndex];
								//Current selection items could have undefined/unset dataItem in the payload
								var slotDataItem = void 0;
								if (slot.getDefinition().getProperty('multiMeasure') && slot.getDataItemList().length > 0) {
									(function () {
										// This is for a specific case where a multi measures value is not in the correct index
										// The multiMeasuresSeriesSlot data item id is compared to the id's of this slot's data items to find the correct one

										var dataset = slot.getDefinition().getDatasetIdList()[0];
										var multiMeasuresSeriesSlot = _.find(slots, function (slot) {
											return SlotAPIHelper.isMultiMeasuresSeriesSlot(slot) && slot.getDefinition().getDatasetIdList()[0] === dataset;
										});
										if (!multiMeasuresSeriesSlot) {
											multiMeasuresSeriesSlot = SlotAPIHelper.getMultiMeasureSeriesSlot(_this6.visualization, dataset);
										}

										var multiMeasuresSlotIndex = _.indexOf(_.map(slots, function (slot) {
											return slot.getId();
										}), multiMeasuresSeriesSlot.getId());

										var multiMeasuresDataItemIndex = _.indexOf(_.map(multiMeasuresSeriesSlot.getDataItemList(), function (dataItemApi) {
											return dataItemApi.getColumnId();
										}), SlotAPIHelper.MULTI_MEASURES_SERIES);
										if (multiMeasuresSlotIndex >= 0 && multiMeasuresDataItemIndex >= 0 && values[multiMeasuresSlotIndex] && values[multiMeasuresSlotIndex][multiMeasuresDataItemIndex] && values[multiMeasuresSlotIndex][multiMeasuresDataItemIndex].u) {
											var dataItemUId = values[multiMeasuresSlotIndex][multiMeasuresDataItemIndex].u;
											slotDataItem = _.find(slot.getDataItemList(), function (dataItem) {
												return dataItem.getId() === dataItemUId;
											});
											// The data item id should be in the form '_multiMeasuresSeries(SHEET1.Income)'
											// The value within parentheses is parsed to find the data item sharing that id
										}
									})();
								}
								if (slotDataItem === undefined) {
									slotDataItem = slot.getDataItemList()[i];
								}
								if (slotDataItem) {
									selections.addSelection(slot, slotDataItem, value[i], item.isEdgeSelect);
								}
							}
						}
					}
				});
			}
		};

		/**
   * Add the event target item to the selection as a custom tooltip
   * @param selections	Selection info object
   * @param item			Event target item
   * @example
   * a custom data selection allows exposing tooltips for customdata
   * custom data tooltips can be provided in 2 different forms, titles and labels(name/value pairs).
   * {
   * 		value: 'Example of a tooltip title'
   * },
   * {
   * 		label: 'Predictive strength',
   * 		value: '98%'
   * },
   * {
   * 		label: 'Example of a label without a value'
   * }
   */


		VisEventHandler.prototype._processCustomDataSelection = function _processCustomDataSelection(selections, item) {
			if (this._target && this._target.getCustomDataSelections && selections.addCustomSelection) {
				var customSelections = this._target.getCustomDataSelections(item);
				_.each(customSelections, function (s) {
					return selections.addCustomSelection((typeof s === 'undefined' ? 'undefined' : _typeof(s)) === 'object' ? s.label : s, (typeof s === 'undefined' ? 'undefined' : _typeof(s)) === 'object' ? s.value : undefined);
				});
			}
		};

		/**
   * Add the event target item tooltip decoration as a custom tooltip
   * @param selections	Selection info object
   * @param items			Event target items
   */


		VisEventHandler.prototype._processDecorationTooltips = function _processDecorationTooltips(selections, items) {
			try {
				// Used by SA and forecast only currently. Only display annotation decos if one datapoint is selected
				if (items && items.length === 1) {
					if (this._target && this._target.getDecoration && selections.addCustomSelection) {
						var tooltip = this._target.getDecoration(items[0], VisEventHandler.DECORATIONS.TOOLTIP);
						if (tooltip && typeof tooltip === 'string') {
							selections.addCustomSelection(tooltip);
						} else if (tooltip && Array.isArray(tooltip)) {
							// Each element of tooltip is Array of length 2.
							tooltip.forEach(function (tip) {
								if (Array.isArray(tip) && tip.length === 2) {
									selections.addCustomSelection(tip[0], tip[1]);
								}
							});
						}
					}
				}
			} catch (e) {
				this.logger.error(e);
			}
		};

		/**
   * Identify a data item from a slot by index.
   * Usually this is straight forward as slot.getDataItemList()[index].
   * However when measures are nested, the measure is give a series,
   * the series needs to be backtraced to the original measure data item
   *
   * example:
   * Each measures are added to a series data item as a tuple items as below:
   * {
   * 	itemClass: {
   * 		id: '_multiMeasuresSeries'
   *		h: [{
   *			u: '_multiMeasuresSeries',
   *			d: 'Measures'
   *		}, {
   *			u: 'uploadfile.csv.Year',
   *			d: 'Year'
   *		}]
   * 	},
   * 	items:[{
   *		t: [{
   * 			u: 'id_uploadfile.csv.Revenue'
   * 			d: 'Revenue'
   * 		}, {
   * 			u: 'uploadfile.csv.Year->[2016]',
   * 			d: '2017'
   * 		}]
   * 	}, {
   * 		t:[{
   * 			u: 'id_uploadfile.csv.Planned_Revenue'
   * 			d: 'Planned Revenue'
   * 		}, {
   * 			u: 'uploadfile.csv.Year->[2017]',
   * 			d: '2017'
   * 		}]
   * 	}, {
   *		t: [{
   * 			u: 'id_uploadfile.csv.Revenue'
   * 			d: 'Revenue'
   * 		}, {
   * 			u: 'uploadfile.csv.Year->[2016]',
   * 			d: '2017'
   * 		}]
   * 	},	{
   * 		t:[{
   * 			u: 'id_uploadfile.csv.Planned_Revenue'
   * 			d: 'Planned Revenue'
   * 		}, {
   * 			u: 'uploadfile.csv.Year->[2017]',
   * 			d: '2017'
   * 		}]
   * 	}]
   * }
   *
   * @param slot	The target slot of the data item
   * @param item 	Event target item
   * @param index data item index within the slot
   */


		VisEventHandler.prototype._getSlotDataItem = function _getSlotDataItem(slot, item, index) {
			var dataItemList = slot.getDataItemList();
			// nested measures?
			if (SlotAPIHelper.isMultiMeasuresValueSlot(slot)) {
				var slots = this._getMappedSlotListByItem(item);
				var slotIndex = void 0,
				    dataItemIndex = void 0;
				// find the multimeasures slot index
				_.find(slots, function (slot, index) {
					if (SlotAPIHelper.isMultiMeasuresSeriesSlot(slot)) {
						slotIndex = index;
						return true;
					}
					return false;
				});
				// find the multimeasures series dataitem index
				if (slotIndex) {
					_.find(slots[slotIndex].getDataItemList(), function (dataItem, index) {
						if (SlotAPIHelper.isMultiMeasuresSeriesOrValueDataItem(dataItem)) {
							dataItemIndex = index;
							return true;
						}
						return false;
					});

					var multiMeasuresSeries = item.values[slotIndex][dataItemIndex];
					var measureId = multiMeasuresSeries.u;
					// return the measure data item
					return _.find(slot.getDataItemList(), function (dataItem) {
						return dataItem.getId() === measureId;
					});
				} else {
					return slot.getDataItemList()[index];
				}
			} else {
				return dataItemList[index];
			}
		};

		/**
   * Check whether the given resolved items are selected
   *
   * @param resolvedItems		Array of resolved items
   */


		VisEventHandler.prototype._isSelected = function _isSelected(resolvedItems, isEdgeSelect) {
			var payload = this._getSelectionPayload(resolvedItems, false, isEdgeSelect);
			return this._selector.isSelected(payload, this._edgeSelection || isEdgeSelect);
		};

		/**
   * Determines whether the given target items are custom data
   * @param items		event target items
   * @return true if the target items are custom data, otherwise false
   */


		VisEventHandler.prototype._isCustomDataItems = function _isCustomDataItems(items) {
			return _.chain(items).pluck('type').contains('customdata').value();
		};

		VisEventHandler.prototype._isCustomDataSelected = function _isCustomDataSelected(items) {
			var currentSelectionIds = _.pluck(this._visAPI.getDecoratedCustomData('selected'), 'id');
			// check if there is something currently selected
			if (currentSelectionIds.length > 0) {
				var customItemIds = _.chain(items).pluck('values').flatten().pluck('payload').pluck('id').value();
				if (customItemIds.length > 0) {
					return _.intersection(currentSelectionIds, customItemIds).length === customItemIds.length;
				}
			}
			return false;
		};

		/**
   * Determines whether the given target items are currently selected
   *
   * @param items		event target items
   * @return true if the target items are highlighted, otherwise false
   */


		VisEventHandler.prototype._isItemsSelected = function _isItemsSelected(items) {
			var isSelected = false;
			if (this._isCustomDataItems(items)) {
				isSelected = this._isCustomDataSelected(items);
			} else {
				var resolved = this._resolveItems(items);
				isSelected = resolved.length > 0 ? this._isSelected(resolved, items[0].isEdgeSelect) : false;
			}
			return isSelected;
		};

		/**
   * Add/Remove the selected items to the selection controller
   *
   * @param resolved		Array of resolved items
   * @param doSelect		boolean flag indicating whether to add or remove selection
   * @param append		boolean flag indicating whether to append to the existing selections
   * @param pending		boolean indicating that the selection should be deferred (applied on keep/exclude/filter later on).
   * @param multiTuple		boolean indicating multiple tuples are selected
   */


		VisEventHandler.prototype._select = function _select(resolved, doSelect, append, pending, isEdgeSelect, multiTuple) {
			var transactionToken = this._transaction.startTransaction();
			var selections = pending ? this.content.getFeature('DataPointSelections.pending') : this.content.getFeature('DataPointSelections');
			var payload = this._getSelectionPayload(resolved, multiTuple, isEdgeSelect);

			if (!append) {
				selections.clearAll(transactionToken);
			}

			if (doSelect) {
				selections.select(payload, transactionToken);
			} else {
				selections.deselect(payload, transactionToken);
			}
			this._transaction.endTransaction(transactionToken);
		};

		VisEventHandler.prototype._getSelectionPayload = function _getSelectionPayload(selections, multiTuple) {
			var edgeSelect = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;

			if (multiTuple) {
				if (edgeSelect) {
					return _.map(_.flatten(selections), function (s) {
						return {
							categories: [{
								dataItemId: s.dataItemId,
								columnId: s.itemId,
								label: s.tupleItem && s.tupleItem.d,
								parentId: s.tupleItem && s.tupleItem.p && s.tupleItem.p.u,
								value: s.tupleItem && s.tupleItem.u
							}],
							edgeSelect: edgeSelect
						};
					});
				} else {
					return _.map(selections, function (datapoint) {
						return {
							categories: _.map(datapoint, function (s) {
								return {
									dataItemId: s.dataItemId,
									columnId: s.itemId,
									label: s.tupleItem && s.tupleItem.d,
									parentId: s.tupleItem && s.tupleItem.p && s.tupleItem.p.u,
									value: s.tupleItem && s.tupleItem.u
								};
							}),
							edgeSelect: edgeSelect
						};
					});
				}
			} else {
				return [{
					categories: _.chain(selections).flatten().map(function (s) {
						return {
							dataItemId: s.dataItemId,
							columnId: s.itemId,
							label: s.tupleItem && s.tupleItem.d,
							parentId: s.tupleItem && s.tupleItem.p && s.tupleItem.p.u,
							value: s.tupleItem && s.tupleItem.u
						};
					}).value(),
					edgeSelect: edgeSelect
				}];
			}
		};

		/**
   * Reduce items to itemIds and tuples
   *
   * @param resolved Array of resolved items
   */


		VisEventHandler.prototype.reduceItems = function reduceItems(resolved) {
			var selection = {
				itemIds: [],
				tuple: []
			};

			resolved.reduce(function (accumulator, item) {
				var _itemsId = void 0;
				var _tuple = void 0;

				if (Array.isArray(item)) {
					//datapoint selection
					_itemsId = _.map(item, function (o) {
						return o.itemId;
					});
					_tuple = _.map(item, function (o) {
						if (_.isUndefined(o.tupleItem && o.tupleItem.u)) {
							return { u: o.tupleItem, d: o.tupleItem };
						}
						return o.tupleItem;
					});
				} else {
					//edge selection
					_itemsId = [item.itemId];
					_tuple = item.tupleItem;
				}

				accumulator.itemIds = _.uniq(accumulator.itemIds.concat(_itemsId));
				accumulator.tuple.push(Array.isArray(_tuple) ? _.uniq(_tuple, false, function (t) {
					return t && t.u || t;
				}) : _tuple);
				return accumulator;
			}, selection);

			return selection;
		};

		/**
   * Show the title action toolbar for the given items
   */


		VisEventHandler.prototype._titleActions = function _titleActions(event, items) {
			var _this7 = this;

			var aSlots = [];
			var itemsArea = items && items[0] ? items[0].area : undefined;
			if (items && items.length) {
				for (var i = 0; i < items.length; i++) {
					var item = items[i];
					if (item.type !== 'customdata') {
						(function () {
							var slots = _this7.visualization.getSlots().getMappedSlotList();
							item.values.forEach(function (value, index) {
								if (!aSlots.find(function (slot) {
									return slot.getId() === slots[index].getId();
								})) {
									aSlots.push(slots[index]);
								}
							});
						})();
					}
				}

				var mapIndex = items[0].selectionContext ? items[0].selectionContext.mapIndex : undefined;
				var actions = items[0].selectionContext && items[0].selectionContext.actions || [];
				// Array for when there are multiple data items selected within a slot
				this.setEventLocalProperty(event, 'preventDefaultContextBar', true);
				this._visActionHelper.showTitleActions(aSlots, { 'bounds': this._getBoundsInfoFromEvent(event) }, null, // selected slot items are null for non-focus mode actions
				mapIndex, itemsArea, actions);
			}
		};

		/**
   * Clear all selections from the selection controller
   */


		VisEventHandler.prototype._clearAll = function _clearAll(event) {
			var _this8 = this;

			this._triggerWidgetEvent('visevent:clearselection', event);

			// clear selection controller
			return this.content.getFeature('DataPointSelections').clearAll().then(function () {
				return _this8.content.getFeature('DataPointSelections.pending').clearAll();
			}).then(function () {
				// clear custom data selection
				return _this8.clearCustomDataSelection();
			});
		};

		VisEventHandler.prototype.clearCustomDataSelection = function clearCustomDataSelection(options) {
			this._target.applyCustomDataSelection([], options);
		};

		/**
   * Obtain the selection info object based on the event.
   *
   * @param {Object} event - Event object
   * @param {Class} InfoClass - VisSelectionInfo (default) or VisTooltipInfo
   */


		VisEventHandler.prototype._getDataPayload = function _getDataPayload(items, InfoClass) {
			var _this9 = this;

			var VisInfoClass = InfoClass || VisSelectionInfo;
			var selections = new VisInfoClass();

			_.each(items, function (item, itemIdx) {
				switch (item.type) {
					case 'datapoint':
						return _this9._processDataPointSelection(selections, item);
					case 'item':
						return _this9._processEdgeSelection(selections, item, itemIdx);
					case 'customdata':
						return _this9._processCustomDataSelection(selections, item);
					default:
						return;
				}
			});

			this._processDecorationTooltips(selections, items);

			return selections;
		};

		/**
   * Display the selection tooltip.
   */


		VisEventHandler.prototype._showSelectionInfo = function _showSelectionInfo(oSelectionObj, event, includeApplyFilter, noFilters, drillOnly, noDrillthrough, options) {
			var infoShown = false;
			if (oSelectionObj && oSelectionObj.getCategorySelections()) {
				this._visActionHelper.showDataPointActions(oSelectionObj, {
					// Create fake bound information so that the tooltip shows up where the mouse is
					'bounds': this._getBoundsInfoFromEvent(event),
					'targetNode': event.currentTarget
				}, includeApplyFilter, noFilters, drillOnly, noDrillthrough, options);
				infoShown = true;
				this.setEventLocalProperty(event, 'preventDefaultContextBar', true);
			}
			return infoShown;
		};

		/**
   * Api method wrapper for #toggleLassoSelect
   * @param {boolean} state
   * @param {string} viewId
   */


		VisEventHandler.prototype.setLassoSelectState = function setLassoSelectState(state) {
			var viewId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : LassoController.DEFAULT_VIEW_ID;

			if (this.lassoCtrl.getCurrentViewId() && this.lassoCtrl.getCurrentViewId() !== viewId) {
				this._endLassoSelection();
				state = true;
			}

			return this.toggleLassoSelect(state, viewId);
		};

		/**
  * Toggle the state of lasso mode
  * @param {boolean} [state] - The final state of lasso mode for the Vis. True means
  * switch on, false means switch off. When not provided, the current state is inverted.
  */


		VisEventHandler.prototype.toggleLassoSelect = function toggleLassoSelect(state, viewId) {
			var enabled = arguments.length === 0 ? !this.lassoCtrl.isLassoMode() : !!state;
			if (this.lassoCtrl.isLassoMode() !== enabled) {
				if (enabled) {
					this.lassoCtrl.activateLassoMode(true, viewId);
					this.lassoCtrl.on('lassoSelect:done', this._handleLassoSelection, this);
					this.lassoCtrl.on('lassoSelect:cordsChange', this._handleLassoCordChange, this);
					this.lassoCtrl.on('lassoSelect:cancel', this._endLassoSelection, this);
				} else {
					this._endLassoSelection();
				}
			}
			return enabled;
		};

		VisEventHandler.prototype.getLassoViewId = function getLassoViewId() {
			return this.lassoCtrl.getCurrentViewId();
		};

		VisEventHandler.prototype._handleLassoSelection = function _handleLassoSelection(payload) {
			try {

				var items = [];
				if (!_.isUndefined(payload.radius)) {
					items = this._target && this._target.getEventTargets ? this._target.getEventTargets(payload.event, payload.radius) : [];
				} else {
					items = this._target && this._target.getTargetsByCords ? this._target.getTargetsByCords(payload.cords, /*sumarize*/false) : [];
				}

				if (items) {
					var options = {
						isMultiSelect: true
					};
					// iPad has no ctrl click, default is append always by design
					// force ctrl click if selection was triggered by mobile end touch event
					// https://github.ibm.com/BusinessAnalytics/WACA-UXDR/issues/2656

					if (payload.event.type === 'touchend') {
						options.forceCtrlClick = true;
					}

					this._handleHighlights(items);
					this._handleSelections(payload.event, items, options);
				}
			} catch (e) {
				this.logger.error(e);
			}
		};

		VisEventHandler.prototype._handleLassoCordChange = function _handleLassoCordChange(payload) {

			try {
				var items = [];
				if (!_.isUndefined(payload.radius)) {
					items = this._target && this._target.getEventTargets ? this._target.getEventTargets(payload.event, payload.radius) : [];
				} else {
					items = this._target && this._target.getTargetsByCords ? this._target.getTargetsByCords(payload.cords, /*sumarize*/false) : [];
				}
				if (items) {
					this._handleHighlights(items);
				}
			} catch (e) {
				this.logger.error(e);
			}
		};

		VisEventHandler.prototype._endLassoSelection = function _endLassoSelection() {
			this.lassoCtrl.off('lassoSelect:done', this._handleLassoSelection, this);
			this.lassoCtrl.off('lassoSelect:cordsChange', this._handleLassoCordChange, this);
			this.lassoCtrl.off('lassoSelect:cancel', this._endLassoSelection, this);
			this.lassoCtrl.activateLassoMode(false);
		};

		VisEventHandler.prototype.isLassoSelectActive = function isLassoSelectActive() {
			return this.lassoCtrl.isLassoMode();
		};

		/**
   * Api method wrapper for #isLassoSelectActive
   */


		VisEventHandler.prototype.getLassoSelectState = function getLassoSelectState() {
			return this.isLassoSelectActive();
		};

		//Send a "visevent" to notify listeners (features etc.) that a visualization-specific event has occurred
		//Eg: Supply information when the user is moving over the visualization or selecting a visualization element.


		VisEventHandler.prototype._triggerWidgetEvent = function _triggerWidgetEvent(name, event, target, items, additionalPayloadItems) {
			var payload = {
				sender: this._widgetApi,
				event: event,
				target: target,
				items: items
			};
			_.each(additionalPayloadItems, function (additionalPayloadItems, key) {
				payload[key] = additionalPayloadItems;
			});
			this._ownerWidget.trigger(name, payload);
		};

		/**
   * Set _stopHoverover flag.
   * @param {Object} payload - indicate if the event is triggered by hover. e.g. {isHover: true}.
   */


		VisEventHandler.prototype.setPopoverClosed = function setPopoverClosed(payload) {
			this._isHoverFlyoutShowing = false;
			if (!payload || !payload.isHover) {
				this._stopHoverover = false;
			}
		};

		/**
   * Set _stopHoverover flag. Stop hover when the close event is not triggered by hover.
   * @param {Object}  payload - indicate if the event is triggered by hover or widget ODT. e.g. {isHover: true}.
   */


		VisEventHandler.prototype.setPopoverOpened = function setPopoverOpened(payload) {
			if (!payload || !payload.isHover) {
				this._stopHoverover = true;
				if (payload && payload.isWidgetOdt) {
					this._stopHoverover = false;
				}
			}
		};

		VisEventHandler.prototype._getMappedSlotListByItem = function _getMappedSlotListByItem(item) {
			if (item && item.datasetId) {
				return SlotAPIHelper.getMappedSlotListByDataset(this.visualization, item.datasetId);
			} else {
				return this.visualization.getSlots().getMappedSlotList();
			}
		};

		return VisEventHandler;
	}();

	/**
  * Decorations supported by VisEventHandler
  */


	VisEventHandler.DECORATIONS = {
		HIGHLIGHT: 'highlight',
		SELECT: 'select',
		TOOLTIP: 'tooltip'
	};

	return VisEventHandler;
});
//# sourceMappingURL=VisEventHandler.js.map