'use strict';

/**
 *+------------------------------------------------------------------------+
 *| Licensed Materials - Property of IBM
 *| IBM Cognos Products: Dashboard
 *| (C) Copyright IBM Corp. 2017, 2022
 *|
 *| US Government Users Restricted Rights - Use, duplication or disclosure
 *| restricted by GSA ADP Schedule Contract with IBM Corp.
 *+------------------------------------------------------------------------+
 */
/* global Blob FileReader */
define(['jquery', 'underscore', './VIPREventTarget', '../VisView', '../VisEventHandler', 'dashboard-analytics/visualizations/vipr/VIPR', 'dashboard-analytics/visualizations/vipr/VIPRUtils', 'dashboard-analytics/visualizations/vipr/VIPRDataRequestHandler', 'dashboard-analytics/visualizations/vipr/VIPRCachedDataRequestHandler', 'dashboard-analytics/visualizations/vipr/properties/VIPRProperties', 'dashboard-analytics/visualizations/vipr/properties/ColorPropertiesCreator', 'dashboard-analytics/visualizations/vipr/VIPRConfig', '../../../lib/@waca/core-client/js/core-client/utils/BrowserUtils', '../../../widgets/livewidget/nls/StringResources', 'dashboard-analytics/visualizations/interactions/BinningActionsUtils', '../../../lib/@waca/core-client/js/core-client/utils/PerfUtils', '../../../widgets/livewidget/SdkShowError', 'react-dom', 'react', './components/CustomVisPreviewMessage', '../../../util/ContentUtil', 'dashboard-analytics/widgets/livewidget/util/VisUtil'], function ($, _, VIPREventTarget, VisView, VisEventHandler, VIPR, VIPRUtils, VIPRDataRequestHandler, VIPRCachedDataRequestHandler, VIPRProperties, ColorPropertiesCreator, VIPRConfig, BrowserUtils, stringResources, BinningActionsUtils, PerfUtils, SdkShowError, ReactDOM, React, CustomVisPreviewMessage, ContentUtil, VisUtil) {
	'use strict';

	var DEFAULT_THUMBNAIL_PROPERTIES = {
		'optimizeSize': true,
		'gridLines.visible': false,
		'markers.visible': false,
		'widget.legend.size': 0,
		'labels.visible': false,
		'itemAxis.line.visible': false,
		'valueAxis.line.visible': false,
		'valueAxis.ticks.visible': false,
		'itemAxis.ticks.visible': false,
		'itemAxis.gridLines.visible': false,
		'valueAxis.gridLines.visible': false
	};

	/**
  * List of ignored decorations when generating a thumbnails
  * @type {string[]}
  */
	var IGNORED_THUMBNAIL_DECORATIONS = [
	// TODO: right now livewidget does not know what vis-crosshairs is
	// we should probably contribute this list of decorations to be ignored from outside
	'compareLine', // old vis-crosshairs - to be removed when the new crosshair feature flag is removed
	'lines' // new vis-crosshairs
	];

	var VIPRView = VisView.extend({
		initVizPromise: null,

		init: function init(options) {
			VIPRView.inherited('init', this, arguments);

			this.logger = options.logger;

			this.$el.attr('id', this.viewId);
			this.$el.addClass('dataview vipr-view');
			this.dashboard = options.dashboardApi;
			this.colorsService = this.dashboard.getFeature('Colors');
			this.visAPI = options.visModel;
			this.predictHandler = this._createPredictHandler(VIPRDataRequestHandler);
			this.highContrastEnabled = $(document.body).hasClass('highcontrast');
			this.content = options.content;

			this.visualization = this.content.getFeature('Visualization');

			this.internalVisDefinitions = this.dashboard.getFeature('VisDefinitions.internal');

			//Initialize members choosing which title to show (name/titleHtml vs annotation)
			if (this.visModel.getShowTitle()) {
				this._hasUserTitle = true;
				this._hasAnnotationTitle = false;
			}

			this.visController = {};
			this.viprDecorations = {};

			this.dataItemDecorations = {};

			//A list of widgets marked to be destroyed after the current viprWidget control render completes (part of a reRender)
			this._deferredDestroyViprWidgetsList = [];

			this.viprControlRenderInProgress = false;
		},

		/**
   * Setup the view and events based on the model
   * @override
   */
		createView: function createView() {
			VIPRView.inherited('createView', this, arguments);

			// Listen for theme changes
			this.visModel.on('change:theme', this.onChangeTheme, this);
			if (this._isSmartTitleEnabled()) {
				this.visModel.on('change:titleMode', this.onShowTitle, this);
			} else {
				this.visModel.on('change:showTitle', this.onShowTitle, this);
			}

			this.visModel.on('change:customData', this.onChangeCustomData, this);

			this._createVisTabs();
		},

		/**
   * Create a predict handler
   *
   * @param {Function} Class - class to use for the predict handler
   * @param {[type]} [handler] - original predict handler, if any; This is used for thumbnails for caching API requests
   *
   * @return {object} predict handler instance
   */
		_createPredictHandler: function _createPredictHandler(Class, handler) {
			var options = {
				visAPI: this.visAPI,
				visView: this,
				logger: this.logger
			};
			if (handler) {
				options.handler = handler;
			}
			return new Class(options);
		},

		/**
  *
  * @param event - payload for a customData model change event
  * 			 event.prevValue - previous selections of custom data
  * 			 event.value - current selections of custom data
  */
		onChangeCustomData: function onChangeCustomData(event) {
			var prevSelections = event.prevValue && event.prevValue.selected || [];
			var prevIds = prevSelections.map(function (decoration) {
				return decoration.id;
			});
			var prevSelectedCustomData = this.visAPI.getCustomData(prevIds);

			//clear decorations for previous selections;
			prevSelectedCustomData.forEach(function (item) {
				item.setDecoration && item.setDecoration('selected', false);
			});

			var curSelectedCustomData = this.visAPI.getDecoratedCustomData('selected');
			// set decorations for current selections;
			curSelectedCustomData.forEach(function (item) {
				item.setDecoration && item.setDecoration('selected', true);
			});

			return this.renderVIPRControl({ callRenderComplete: true });
		},

		/**
   * Create the visualization tabs if the definition require the tabs
   * The tabs definition consists with the following:
   * tabs : {
   *		entries:[{
   *			id: <unique identifier of the tab>,
   *			resource: <resource key of the tab display label>
   *		}]
   *		default: <identifier of the default select tab entry>
   * }
   */
		_createVisTabs: function _createVisTabs() {
			var _this = this;

			if (this._tabs) {
				// ensure we start from scratch
				this._tabs.reset();

				// check the tabs definitions
				var definition = this.visModel.getDefinition();
				if (definition.tabs) {
					// create the tabs
					this._tabs.createTabs(_.map(definition.tabs.entries, function (tab) {
						return {
							id: tab.id,
							label: stringResources.get(tab.resource),
							onChange: function (tabId) {
								this.visModel.ownerWidget.updateVisProperties({
									id: 'actions',
									value: tabId
								});
							}.bind(_this)
						};
					}), this.visModel.getPropertyValue('actions') || definition.tabs.default);
				}

				// hide the tabs until necessary to show
				this._tabs.hide();
			}
		},

		/**
   * return a public API to a decorator for the nth vipr dataset (default dataset=0)
   * the decorator implements:
   *		- decorate (decorate the dataset),
   *		- decorateItem (decorate an item),
   *		- decoratePoint(decorate a point)
   *		- clearItemDecoration (clear a given item decoration)
   *		All decoratorAPIs implement the same updateDecorations method to render the decorations.
   * Most visualizations have only 1 decoratorAPI (ie: 1 vipr dataset).  Maps, for example have 3.
   * @param n - the nth decorator (dataset)....default 0
   * @returns a decoratorAPI for the whole decorator, items or points
   */
		getDecoratorAPI: function getDecoratorAPI(n) {
			var _this2 = this;

			var setDecorateItem = function setDecorateItem(itemIndex, decoration, value) {
				// Save the decoration so that they are applied when the decoration is refreshed
				// We are only saving the dataitem decorations
				// TODO: Do we need to save datapoints and dataset decorations ??
				if (!_this2.dataItemDecorations[n]) {
					_this2.dataItemDecorations[n] = {};
				}
				if (!_this2.dataItemDecorations[n][itemIndex]) {
					_this2.dataItemDecorations[n][itemIndex] = {};
				}

				var dataItemN = oDataSet.getDataItem(itemIndex);
				/**
     * decorationValue from viprData is usually the same as the value passed in EXCEPT FOR the 'lines' decoration.
     * The lines decoration returns an array of VIDA decoration instances that are the elements of the line that correspond to the value spec passed in . eg: { baseLine: {}, grabber: {}}
     */
				var decorationValue = dataItemN && dataItemN.decorate(decoration, value);
				_this2.dataItemDecorations[n][itemIndex][decoration] = decorationValue;
				return;
			};

			n = n || 0;
			if (!this.viprData || n > this.viprData.getDataSetCount() || !this._isViprWidgetValid()) {
				return null;
			}
			var oDataSet = this.viprData.getDataSetAt(n);
			if (oDataSet) {
				var datapoints = oDataSet.getDataPointIterator() && oDataSet.getDataPointIterator().datapoints;
				return {
					decorateItem: setDecorateItem,
					decoratePoint: function decoratePoint(row, decoration, value) {
						return datapoints && datapoints[row] && datapoints[row].decorate && datapoints[row].decorate(decoration, value);
					},
					decorate: function decorate(decoration, value) {
						return oDataSet.decorate(decoration, value);
					},
					clearItemDecoration: function clearItemDecoration(decoration) {
						if (_this2.dataItemDecorations[n]) {
							var dataItemIndex = void 0;
							for (dataItemIndex in _this2.dataItemDecorations[n]) {
								var dataItemN = oDataSet.getDataItem(dataItemIndex);
								if (dataItemN) {
									dataItemN.decorate(decoration, '');
								}
								delete _this2.dataItemDecorations[n][dataItemIndex][decoration];
							}
						}
					},
					updateDecorations: function updateDecorations() {
						return _this2.renderVIPRControl();
					}
				};
			}
			return null;
		},

		/**
   * return a public interface to all decorators in the visualization
   * (ie: one decoratorAPI per dataset as an array)
   * @returns an array of 0 or more getDecoratorAPIs
   */
		getDecoratorAPIs: function getDecoratorAPIs() {
			var decoratorAPIs = [];
			if (this.viprData && this.viprData.getDataSetCount) {
				for (var i = 0; i < this.viprData.getDataSetCount(); ++i) {
					var decoratorAPI = this.getDecoratorAPI(i);
					if (decoratorAPI) {
						decoratorAPIs.push(decoratorAPI);
					}
				}
			}
			return decoratorAPIs;
		},

		/**
  * @returns {boolean} true iff and only if this view supports annotations
  * The base view always returns false, the child view shoud override if different
  */
		supportsAnnotations: function supportsAnnotations() {
			return !this.doesVisPropertyMatchExpected('doesNotSupportAnnotations', true);
		},
		supportsForecasts: function supportsForecasts() {
			var forecastFeature = this.content && this.content.getFeature('Forecast');
			return forecastFeature && forecastFeature.supportsForecasts();
		},


		/**
   * Handle visualization definition change
   */
		onChangeDefinition: function onChangeDefinition() {
			this.initVizPromise = null;
			VIPRView.inherited('onChangeDefinition', this, arguments);
			//Clean Info Indicator and we update the indicator after Data Query is executed
			//for the new view.
			if (this.infoIndicator) {
				this.infoIndicator.reset();
			}

			//clean listener flag for vida prop changes
			if (this.propChangeslistener) {
				this.propChangeslistener = undefined;
			}

			// recreate the tabs for the new vistype
			this._createVisTabs();
		},

		/**
   * Handle removal of the View and/or its embedded viprWidget control.
   * @param finalRemove - (default) remove is 'final', the embedded viprWidget control is destroyed immediately
   *		remove can also be called as part of a reRender (final=false).  In this case, the embedded viprWidget is marked
   *		to be destroyed after the current control render.
   *		(the embedded control render is async and can't be destroyed mid-render)
   */
		remove: function remove() {
			var finalRemove = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;


			this.visModel.off('change:theme', this.onChangeTheme, this);
			if (this._isSmartTitleEnabled()) {
				this.visModel.off('change:titleMode', this.onShowTitle, this);
			} else {
				this.visModel.off('change:showTitle', this.onShowTitle, this);
			}
			this.visModel.off('change:customData', this.onChangeCustomData, this);

			this._cleanupVIPRWidget(finalRemove);
			// The super.destroy will clear all members of this class.
			// this is why we do it at the end
			VIPRView.inherited('remove', this, arguments);
		},

		/**
   * Clean up the VIPR widget and event handler
   * @param finalRemove - (default) remove is 'final', false - part of reRender (see remove)
   */
		_cleanupVIPRWidget: function _cleanupVIPRWidget() {
			var finalRemove = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;

			if (this.eventHandler) {
				this.eventHandler.remove();
			}
			if (this.$customVisPreviewMessage) {
				ReactDOM.unmountComponentAtNode(this.$customVisPreviewMessage[0]);
				this.$customVisPreviewMessage.remove();
				this.$customVisPreviewMessage = null;
			}

			this._destroyCustomVisShowError();

			if (this.viprWidget) {
				if (finalRemove && !this.viprControlRenderInProgress) {
					//Destroy immmediately
					this.viprWidget.destroy();
					this.viprWidget = null;
				} else if (this._deferredDestroyViprWidgetsList.indexOf(this.viprWidget) === -1) {
					//Set the viprWidget control to be destroyed at the end of control's async render.
					this._deferredDestroyViprWidgetsList.push(this.viprWidget);
				}
			}
		},

		_destroyCustomVisShowError: function _destroyCustomVisShowError() {
			if (this.sdkShowError) {
				this.sdkShowError.destroy();
				this.sdkShowError = null;
			}
		},


		/**
   * This function is called after the viprWidget render to destroy any viprWidget controls that were marked to be destroyed.
   */
		_deferredDestroyVIPRWidgets: function _deferredDestroyVIPRWidgets() {
			_.each(this._deferredDestroyViprWidgetsList, function (viprWidgetToDestroy) {
				viprWidgetToDestroy.destroy();
			});
			this._deferredDestroyViprWidgetsList = [];
		},

		/**
   * Create a VIPRWidget instance and event handler
   */
		_createVIPRWidget: function _createVIPRWidget() {
			var _this3 = this;

			if (!this.viprWidget) {
				// Create a VIPR container.
				// NOTE. VIPR will literally owns this container and will remove on viprWidget.destroy()
				var containerId = 'VIPR_' + this.viewId;
				this.$el.append($('<div id="' + containerId + '" style="width:100%; height:100%" role="application"></div>'));

				var cursors = VIPRUtils.getCursor(this.visAPI.getVisId());

				// The last supported cursor wins. For example, only -webkit-grab works
				// on chrome. This means that if cursors = ['-webkit-grab', 'grab', '-moz-grab'],
				// the style.cursor of the element will be set to '-webkit-grab'.
				if (cursors) {
					_.forEach(cursors, function (cursor) {
						_this3.$el.get(0).style.cursor = cursor;
					});
				}
				//If there is a passed in predictData during initilization, we will create a VIPRCachedDataRequestHandler that caches the predictData
				if (this.predictData) {
					this.predictHandler.setLastRequestData('Catalyst', this.predictData);
					this.predictHandler = this._createPredictHandler(VIPRCachedDataRequestHandler, this.predictHandler);
				}
				this.viprWidget = VIPR.createWidget(containerId, this.predictHandler);

				var userProfileService = this.dashboard.getGlassCoreSvc('.UserProfile');
				if (userProfileService) {
					this.viprWidget.formatLocale = userProfileService.preferences.contentLocale;
				}
				this.eventHandler = new VisEventHandler({
					target: new VIPREventTarget({
						$el: this.$el,
						viprWidget: this.viprWidget,
						visAPI: this.visAPI,
						visualization: this.visualization,
						content: this.content
					}),
					transaction: this.transactionApi,
					ownerWidget: this.ownerWidget,
					visAPI: this.visAPI,
					logger: this.logger,
					dashboard: this.dashboard
				});
			}
		},

		/**
   * Handle when visualization is ready
   */
		whenVisControlReady: function whenVisControlReady() {
			var _this4 = this;

			if (!this.initVizPromise) {
				this._createVIPRWidget();
				this.initVizPromise = this.viprWidget.newViz(this.visAPI.getVisId(), 'client').then(function (vizBundle) {
					// force to create a new vipr data
					_this4.viprData = null;
					_this4.vizBundle = vizBundle;
					return _this4.visController;
				}).catch(function (error) {
					//Remove this checking in R5 for Custom Vis, this is no longer needed
					if (_this4.visAPI.getVisId() === 'visualizationPreview') {
						_this4.logger.error(error, _this4);
						return _this4.visController;
					} else {
						throw error;
					}
				});
			}
			return this.initVizPromise;
		},

		/**
   * Create an instance of VIPR data from query data
   *
   * @param {object} data - query data
   *
   * @return {object} VIPR data
   */
		createViprData: function createViprData(data) {
			var viprData = data.getQueryResults ? VIPRUtils.createData(data.getQueryResults(), this.visualization, this.content) : VIPRUtils.createQueryResultData(data, this.visualization, this.content);
			viprData.hasMoreData = data.hasMoreData;
			viprData.queryThreshold = data.queryThreshold;
			return viprData;
		},

		/**
   * When there is a connection error we should not deal with preparing data anymore.
   */
		canRender: function canRender() {
			return this._hasMappedSlots() && this.isMappingComplete();
		},


		/**
   * Handle when data is ready
   */
		whenDataReady: function whenDataReady() {
			var _this5 = this;

			return VIPRView.inherited('whenDataReady', this, arguments).then(function (_ref) {
				var data = _ref.data,
				    sameQueryData = _ref.sameQueryData;

				var result = void 0;
				if (_this5.canRender() && !_this5.hasUnavailableMetadataColumns() && !_this5.hasMissingFilters()) {
					// preserve the query results
					_this5._dataObject = data;

					if (sameQueryData) {
						result = {
							data: data,
							sameQueryData: sameQueryData
						};
					} else {
						_this5.predictHandler.resetCustomData();
						_this5._viprSlotMapping = VIPRUtils.createSlotMapping(_this5.visualization);
						result = {
							data: data
						};
					}
				} else {
					result = {
						data: {}
					};
				}

				return result;
			});
		},

		/**
  * Set a model property if its valid.
  * @param {String} propertyName - name of the property of interest.
  * @param {String} visId - id of the visualization of interest.
  */
		_setModelProperty: function _setModelProperty(propertyName, visId) {
			if (this._shouldPropertyBeApplied(propertyName)) {
				var val = this._getPropValueWhenSaving(visId, propertyName);
				var propInfo = {
					id: propertyName,
					value: val
				};
				this.setProperty(propInfo);
			}
		},


		/**
  * set properties needed before data is set.
  */
		_setPreDataProperties: function _setPreDataProperties() {
			var _this6 = this;

			// Check for such properties in the vipr config
			var visId = this.visAPI.getVisId();
			var config = VIPRConfig.getConfig(visId);
			if (config && config.propertiesToSetBeforeData && config.propertiesToSetBeforeData.length > 0) {
				config.propertiesToSetBeforeData.forEach(function (propertyName) {
					_this6._setModelProperty(propertyName, visId);
				});
			}
		},


		/**
   * handles when data is ready before setting
   * @param {Object} renderContext
   */
		whenSetDataReady: function whenSetDataReady(renderContext) {
			var result = Promise.resolve(true);
			if (this.canRender() && !this.hasUnavailableMetadataColumns() && !this.hasMissingFilters()) {
				var autoRefresh = !!(renderContext && renderContext.extraInfo && renderContext.extraInfo.queryRefresh);
				// we only care if its the same query IFF the query is an auto refresh query
				// otherwise the query is considered valid to construct a new VIPRData
				var sameQueryData = autoRefresh ? !!renderContext.sameQueryData : false;
				var requiresNewVIPRData = this.viprData ? !sameQueryData : true;
				if (requiresNewVIPRData) {
					this._isAnnotated = false;
					// Some properties, such as custom polygons, need to be set before data is set. Do so now.
					this._setPreDataProperties();
					// preserve the query results
					this._dataObject = renderContext.data;

					this._viprSlotMapping = VIPRUtils.createSlotMapping(this.visualization);
					this.viprData = this.createViprData(this._dataObject);

					if (this.viprData) {
						try {
							this.viprWidget.setData(this.viprData, this._viprSlotMapping);
						} catch (e) {
							result = Promise.reject(e);
						}
					} else {
						result = Promise.reject(new Error('Unable to create VIPR data in whenSetDataReady'));
					}
				}
			} else {
				//for cases that that we can render without complete mapping and there is no required slots like schematic, we need to check the slots,
				// if not data item is mapped, so we need to clear data
				var isMappingComplete = this.isMappingComplete() && this.visualization.getSlots().getMappingInfoList().length > 0;
				if (this.isRenderWithoutCompletingMapping() && !isMappingComplete) {
					this.viprWidget.setData(null);
					if (this.infoIndicator) {
						this.infoIndicator.reset();
					}
				}
			}
			return result;
		},
		_getAnnotationMessages: function _getAnnotationMessages(facetData) {
			return facetData.getMessages() || {};
		},


		whenAnnotatedResultsReady: function whenAnnotatedResultsReady() {
			var _this7 = this;

			return VIPRView.inherited('whenAnnotatedResultsReady', this, arguments).then(function (data) {
				if (data) {
					// preserve the result object which holds the annotation titles
					_this7._dataObject = data._aQueryResults && data._aQueryResults.length ? data : _this7._dataObject;

					if (!_.isEmpty(_this7.content.getFeature('Forecast').getForecastInfo().annotations)) {
						_this7.viprData = _this7.createViprData(_this7._dataObject);
						_this7.viprWidget.setData(_this7.viprData, _this7._viprSlotMapping);
					}

					// preserve the query results
					_this7._queryResults = data.getQueryResults();
					_this7._isAnnotated = true;

					_this7.visAPI.setAnnotationMessages(_this7._getAnnotationMessages(data));
				}
				return data;
			}).catch(function (e) {
				throw e;
			});
		},

		onShowTitle: function onShowTitle(event) {
			this._hasUserTitle = event.value !== 'noTitle';

			if (this._hasUserTitle) {
				this._hasAnnotationTitle = false;
			}
		},

		/**
   * VIPR view should use VIPR's built-in animations and not do the default animations.
   */
		// @Override
		animate: function animate() {},

		_readyForRender: function _readyForRender() {
			return (this.canRender() || this.isRenderWithoutCompletingMapping()) && !this.hasUnavailableMetadataColumns() && !this.hasMissingFilters();
		},

		_updateViprProp: function _updateViprProp(viprPropId, viprPropValue) {
			// we need to explicitly update property value so that dirty flag would be set and users will be aware.
			var options = {
				merge: true,
				remove: false,
				silent: false
			};
			if (viprPropId !== undefined && viprPropValue !== undefined) {
				this.visModel.ownerWidget.updateVisProperties({
					'id': viprPropId,
					'value': viprPropValue && viprPropValue.name ? viprPropValue.name : viprPropValue
				}, options);
			}
		},
		_onViprPropValueChange: function _onViprPropValueChange(viprProp) {
			var viprPropId = viprProp.property.getName();
			var viprPropValue = viprProp.property.getValue();
			/*
   * It seems like dashboard is storing the object structures when persisting length properties.
   * This will (e.g. for Length) return an object { value:15, unit:"px" }. However, if vida starts adding
   * internal variables on the object, these will unnecessarily end up in the serialization.
   * If we want to store property values in a spec, need to serialize them by calling 'Prop.toString()'.
   * This will yield e.g. "15px" for the above object.
   * They can then simply be reconstituted into objects by calling Length.parse("15px").
   * This applies to all complex properties, e.g. Color, Palettes, Length, Font etc.
   * NOTE: Have a discussion with Shawn, this is a temporary fix to have the minimum impact on the product,
   * need a generic fix in future
   */
			if (viprProp.property.type === 'length') {
				viprPropValue = viprPropValue ? viprPropValue.toString() : viprPropValue;
			}
			this._updateViprProp(viprPropId, viprPropValue);
		},
		_allowShowTabs: function _allowShowTabs() {
			return this.ownerWidget.allowShowTabs && this.ownerWidget.allowShowTabs();
		},
		_renderTabs: function _renderTabs() {
			// ensure the a tabs are visible with the selected tab
			if (this._tabs.getTabsCount() > 0 && this._allowShowTabs()) {
				this._tabs.show();
				this._tabs.selectTab(this.visModel.getPropertyValue('actions'));
			} else {
				this._tabs.hide();
			}
		},


		/**
   * Render the VIPR visualization
   */
		render: function render(renderInfo) {
			var _this8 = this;

			VIPRView.inherited('render', this, [renderInfo, /*callRenderComplete*/false]);

			this._destroyCustomVisShowError();

			if (!this.viprWidget) {
				return Promise.resolve();
			}

			if (this._renderCustomVisContext()) {
				renderInfo.widgetSize.height = this.$el.closest('.liveWidgetContent').height();
			}

			this.resizeToWidget(renderInfo);
			if (!this._readyForRender()) {
				return VisUtil.validateVisDefinition(this.content, this.dashboardApi, { visId: this.ownerWidget.model.visId }).then(function (isValid) {
					if (isValid) {
						_this8.renderIconView();
					} else {
						var $originalContentBeforeError = _this8.$el.children();
						$originalContentBeforeError.detach();

						_this8.sdkShowError = new SdkShowError(_this8.$el[0], stringResources.get('extVisNoSDKConnection'), stringResources.get('extVisNoSDKPreviewMsg'), 'disconnect');
						_this8.sdkShowError.showError();
					}
				});
			}

			this._renderTabs();

			this.removeIconView();

			//Listen for property value change if any, add a flag to prevent registering the same listener multiple times
			if (!this.propChangeslistener) {
				this._registerListenerForPropChanges();
			}

			return this._applySavedModelProperties(renderInfo).then(function () {
				if (!_this8.visModel.getRenderSequence().isActive()) {
					return;
				}
				var augmentations = _this8.visAPI.ownerWidget.model.augmentation; //TODO: add getAugmentations to visAPI
				if (augmentations && augmentations.length > 0) {
					for (var i = 0; i < augmentations.length; i++) {
						var augmentation = augmentations[i];
						_this8._appendDVI(augmentation);
					}
				}

				return _this8.renderSelected(renderInfo);
			});
		},
		/**
  * Render items that are specific to custom visualizations (ex. Custom Vis Preview Message)
  */
		_renderCustomVisContext: function _renderCustomVisContext() {
			if (this.ownerWidget.model.visId === 'visualizationPreview') {
				var iconsFeature = this.dashboard.getFeature('Icons');
				var $parent = $(this.$el.closest('.widgetContentWrapper'));
				if ($parent.find('.customVisPreviewMessage').length < 1) {
					this.$customVisPreviewMessage = $('<div>');
					this.$customVisPreviewMessage.addClass('customVisPreviewMessage');
					$parent.prepend(this.$customVisPreviewMessage);
					ReactDOM.render(React.createElement(CustomVisPreviewMessage, { iconsFeature: iconsFeature }), this.$customVisPreviewMessage[0]);
				}
				return true;
			}
			return false;
		},

		reveal: function reveal() {
			var _this9 = this;

			if (BrowserUtils.isIE11() || !this._readyForRender() || !this._getEntryDurationProperty() || !this.viprData) {
				this.visAPI.renderCompleteBeforeAnimation();
				return Promise.resolve();
			}
			// reveal is a render which triggers the widget's entry level animation.
			// This is done by clearing and resetting the data as there is no API for it atm.

			var emptyData = this.viprWidget.processData(this.viprData, this._viprSlotMapping).empty();
			this.viprWidget.setData(emptyData, this._viprSlotMapping);
			this._applyEntryDurationProperty('zero');
			return this.renderVIPRControl().then(function () {
				_this9._applyEntryDurationProperty('model');
				_this9.viprWidget.setData(_this9.viprData, _this9._viprSlotMapping);
				_this9.renderVIPRControl();
				return {
					// renderControlApi is a thenable object (has a then method),
					// but it's not a pure promise since it has other methods added by VIDA.
					// if we return it directly those api's get lost.
					renderControlApi: _this9.renderControlApi
				};
			}).catch(function (e) {
				_this9.viprWidget.setData(_this9.viprData, _this9._viprSlotMapping);
				throw e;
			});
		},

		getCurrentViewSelector: function getCurrentViewSelector() {
			return this.eventHandler;
		},

		/**
   * @private
   * TODO: SmartsExecution requests are using a legacy QueryResultsObject.
   * Annotations should be moved to a feature and updated with proper api's for decorations.
   * In the meantime, the response value indexes and decorations can be accessed via the "pt" array and "deco" member.
   * @param {Object} value - datapoint value
   * @return {String} unique string representing the datapoint
   */
		_getDatapointKeyFromSmartAnnotationsResult: function _getDatapointKeyFromSmartAnnotationsResult(value) {
			// generate a unique key that consists of the category indices
			// without the ordinal values
			// INPUT: [2, 1, {v: 886.970000016}]
			// OUTPUT: '2,1'
			return value ? _.filter(value.pt, function (pt) {
				return typeof pt === 'number';
			}).toString() : '';
		},

		_decorateDatapoints: function _decorateDatapoints(dataset, datasetIndex) {
			var it = dataset.getDataPointIterator();
			var selection = this.getSelector().getSelection(ContentUtil.getColumnIdList(dataset.slots || this.visualization.getSlots().getMappedSlotList(), 'attribute'));
			var queryResult = this._isAnnotated ? this._queryResults[datasetIndex] : null;
			var facetData = queryResult ? queryResult.getFacetData() : null;
			var annotatedDatapoints = facetData ? facetData.getDatapoints() : null;

			var nextValue = it.nextValue();
			var index = 0;
			while (nextValue !== null) {
				if (nextValue.decorate && nextValue.hasDecoration) {
					if (annotatedDatapoints && annotatedDatapoints[index] &&
					// When dashboard submits a stats query for smart annotations,
					// certain rows may be removed when attempting to calculate using columns with no values.
					// As a result the stats query (and SA execution query) may have less rows compared to the main data query.
					// This code ensures they match.
					nextValue.getDataPointKey() === this._getDatapointKeyFromSmartAnnotationsResult(annotatedDatapoints[index])) {
						var decorations = annotatedDatapoints[index++].deco;
						if (decorations !== undefined) {

							_.each(decorations, function (value, key) {
								nextValue.decorate(key, value);
							});
						} else {
							nextValue.clearDecorations();
						}
					} else {
						nextValue.clearDecorations();
					}

					// Decorate only if needed. Avoid re-decorating with the same decorations.
					if (selection && selection.isSelected(it.getTupleItems())) {
						nextValue.decorate('selected', true);
					} else if (nextValue.hasDecoration('selected')) {
						nextValue.decorate('selected', false);
					}
				}
				nextValue = it.nextValue();
			}

			return !!selection;
		},

		_decorateDataItems: function _decorateDataItems(dataset, datasetIndex) {
			var _this10 = this;

			var decorated = false;
			var queryResult = this._isAnnotated ? this._queryResults[datasetIndex] : null;
			var annotatedData = queryResult ? queryResult.getFacetData() : null;

			var _loop = function _loop(iDataItem) {
				var dataItem = dataset.getDataItem(iDataItem);
				// decorate annotations
				if (annotatedData) {
					var annotatedDataItem = annotatedData.getResultDataItem(iDataItem);
					if (annotatedDataItem) {
						var decoration = annotatedDataItem.getDecoration();
						if (_.isEmpty(decoration)) {
							dataItem.clearDecorations();
						} else {
							_.each(decoration, function (value, key) {
								dataItem.decorate(key, value);
							});
						}
					}
				} else {
					dataItem.clearDecorations();
				}

				// Maintain any decoration that were added using the widget decorator API
				if (_this10.dataItemDecorations[datasetIndex] && _this10.dataItemDecorations[datasetIndex][iDataItem]) {
					var dataItemDecorations = _this10.dataItemDecorations[datasetIndex][iDataItem];
					var decoName = void 0;
					for (decoName in dataItemDecorations) {
						if (dataItemDecorations[decoName] !== null && dataItemDecorations[decoName] !== undefined) {
							dataItem.decorate(decoName, dataItemDecorations[decoName]);
						}
					}
				}

				// decorate selection
				if (dataItem.getType() === 'cat') {
					(function () {
						// collect the ItemIds (single or nested)
						var itemIds = [];
						var aItemClassSet = dataItem.getItemClassSet();

						for (var iItemClass = 0; iItemClass < aItemClassSet.getItemClassCount(); iItemClass++) {
							itemIds.push(aItemClassSet.getItemClass(iItemClass).getUniqueName());
						}

						var selection = _this10.getSelector().getSelection(itemIds);
						var selectedVIPRItem = {};
						var allVIPRItem = {};
						for (var iTuple = 0; iTuple < dataItem.getTupleCount(); iTuple++) {
							var tuple = dataItem.getTuple(iTuple);

							tuple.items.forEach(function (VIPRItem) {
								return allVIPRItem[VIPRItem.getUniqueName()] = VIPRItem;
							});

							var tupleItems = [];
							for (var iTupleItem = 0; iTupleItem < tuple.getItemCount(); iTupleItem++) {
								tupleItems.push(tuple.getItem(iTupleItem).item);
							}

							// unlike the datapoint decorations, each legend items need to be explictly selected/unselected
							if (selection) {
								var isTupleSelected = !!selection.isSelected(tupleItems);
								tuple.decorate('selected', isTupleSelected);

								isTupleSelected && tuple.items.forEach(function (VIPRItem) {
									selectedVIPRItem[VIPRItem.getUniqueName()] = VIPRItem;
								});

								decorated = true;
							} else {
								tuple.decorate('selected', false);
								tuple.items.forEach(function (VIPRItem) {
									VIPRItem.decorate('selected', false);
								});
							}
						}

						// handle VIPRItem selection
						Object.values(selectedVIPRItem).forEach(function (viprItem) {
							return viprItem.decorate('selected', true);
						});
						Object.keys(allVIPRItem).filter(function (key) {
							return !selectedVIPRItem[key];
						}).forEach(function (itemKey) {
							allVIPRItem[itemKey].decorate('selected', false);
						});
					})();
				}
			};

			for (var iDataItem = 0; iDataItem < dataset.getDataItemCount(); iDataItem++) {
				_loop(iDataItem);
			}
			return decorated;
		},

		decorateData: function decorateData(viprData) {
			var decorations = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
				dataPoints: true,
				dataItems: true,
				selection: true,
				annotations: true
			};

			var dataset = void 0;
			for (var i = 0; i < viprData.getDataSetCount(); i++) {
				dataset = viprData.getDataSetAt(i);
				if (dataset) {
					var dataItemsDecorated = false;
					if (decorations.dataItems) {
						dataItemsDecorated = this._decorateDataItems(dataset, i);
					}

					var datapointsDecorated = false;
					if (decorations.dataPoints) {
						datapointsDecorated = this._decorateDatapoints(dataset, i);
					}

					if (decorations.selection) {
						// The hasSelection decoration tells the visualization if there is any selection in play, and changes
						// how items are rendered by default.
						dataset.decorate('hasSelection', datapointsDecorated || dataItemsDecorated);
					}

					if (decorations.annotations) {
						// Check for annotation type
						var meaningfulDiff = _.where(this.visAPI.getEnabledAnnotations(), { type: 'MEANINGFUL_DIFFERENCES' });
						dataset.decorate('hasAnnotations', !!meaningfulDiff.length);
					}
				}
			}
		},

		renderSelected: function renderSelected(renderInfo) {
			var _this11 = this;

			if (!this.visModel.getRenderSequence().isActive()) {
				return Promise.resolve();
			}
			if (this.viprData) {
				this.decorateData(this.viprData);
			}
			// restore the visualization state
			if (this.eventHandler) {
				this.eventHandler.restoreState();
			}
			var perfMarkName = 'WidgetRender_' + this.visModel.getWidgetId();
			PerfUtils.createPerformanceMark({
				'component': 'dashboard',
				'name': perfMarkName,
				'state': 'start'
			});

			return this.renderVIPRControl({ renderInfo: renderInfo, callRenderComplete: true }).then(function () {
				PerfUtils.createPerformanceMark({
					'component': 'dashboard',
					'name': perfMarkName,
					'state': 'end'
				});
				if (!_this11.visModel.getRenderSequence().isActive()) {
					return;
				}
			});
		},

		renderVIPRControl: function renderVIPRControl() {
			var _this12 = this;

			var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { callRenderComplete: false };
			var renderInfo = options.renderInfo,
			    callRenderComplete = options.callRenderComplete;
			// complete the previous render if it's still running.

			if (this.renderControlApi && this.renderControlApi.complete) {
				this.renderControlApi.complete();
			}
			this.viprControlRenderInProgress = true;
			this.renderControlApi = this.viprWidget.render('client');
			this.renderControlApi.onprogress = function (_message) {
				if (_message.status === 'animating') {
					_this12.visAPI.renderCompleteBeforeAnimation();
				}
			};
			return this.renderControlApi.then(function () {
				callRenderComplete && _this12.visAPI.renderComplete(renderInfo);
				_this12._deferredDestroyVIPRWidgets();
				_this12.viprControlRenderInProgress = false;
			}).catch(function (e) {
				throw e;
			});
		},


		/**
   * check if the value of a property matched expected
   * @param  {String} propertyName
   * @param  {object} expectedValue
   * @return {boolean} true if it matches
   */
		doesVisPropertyMatchExpected: function doesVisPropertyMatchExpected(propertyName, expectedValue) {
			if (propertyName && this.viprWidget) {
				return VIPRUtils.doesConfigPropertyMatchExpected(this.visualization.getDefinition().getId(), propertyName, expectedValue);
			}
			return false;
		},

		/**
   * check if auto binning is set for this view.
   * @return {boolean} true if anto binning can be appied for this view
   */
		canApplyAutoBin: function canApplyAutoBin() {
			var doesConfigSupportAutobinning = VIPRUtils.canApplyAutoBin(this.visualization.getDefinition().getId());

			var definition = this.visualization.getDefinition();
			var slots = this.visualization.getSlots().getMappedSlotList();
			var slotOneRole = 'explanatory';
			var colorSlotRole = 'group';
			var doesWidgetHaveBinnableDataItem = false;
			if (definition.getProperty('canApplyBinning')) {
				_.find(slots, function (slot) {
					var role = slot.getDefinition().getRole();
					if (slot.getDefinition().getType() !== 'ordinal' && (role === slotOneRole || role === colorSlotRole)) {
						var fact = slot.getDataItemList().find(function (dataItem) {
							return dataItem.getMetadataColumn().getType() === 'fact';
						});
						doesWidgetHaveBinnableDataItem = !!fact;
						return doesWidgetHaveBinnableDataItem;
					}
				});
			}

			return doesConfigSupportAutobinning && doesWidgetHaveBinnableDataItem;
		},

		/**
   * @returns the default binning config  for this widget.
   */
		getBinningConfig: function getBinningConfig() {
			return VIPRUtils.getBinning(this.visualization.getDefinition().getId());
		},

		/**
  * @returns the default palette from the colorsService
  */
		_getDefaultContinuousPaletteName: function _getDefaultContinuousPaletteName() {
			return this.colorsService.getDefaultPaletteName('HeatPalette');
		},

		/**
  * @returns the default palette from the colorsSerivce
  */
		_getDefaultCategoricalPaletteName: function _getDefaultCategoricalPaletteName() {
			return this.colorsService.getDefaultPaletteName('ColorPalette');
		},

		/**
  * @param palette - palette property item describing a specific vipr palette
  * @param possibleDataSlots - all possible data slots for the current vis type.
  * @returns a continuous heat palette description needed to set a palette resolver in VIPR
  * eg.
  * {
  *	type: "cont"
  *	colors: description of colors (either continuous or categorical)
  * defaultIndex: index into the palette for an element when the color by slot is empty.
  * }
  */
		_getContinuousColorPaletteDesc: function _getContinuousColorPaletteDesc(viprPalette, possibleDataSlots) {
			var _this13 = this;

			// Get either the overriden palette or the default palette
			var colorPropsCreatorInstance = ColorPropertiesCreator.getInstance();
			var dataLayerId = colorPropsCreatorInstance.getDataLayerIdFromPaletteAndSlots(viprPalette, possibleDataSlots);
			var propertyName = colorPropsCreatorInstance.getContinuousColorPalettePropertyNameWithLayer(dataLayerId);
			var paletteName = this.visModel.getPropertyValue(propertyName) || this._getDefaultContinuousPaletteName();

			return this.colorsService.getPalette({
				paletteId: paletteName,
				type: 'HeatPalette'
			}).then(function (palette) {
				var paletteDef = {
					type: ''
				};
				if (palette) {
					paletteDef.type = 'cont';
					paletteDef.dataLayerId = dataLayerId;
					var colors = [];
					/* VIPR had an original issue with the typical way we express our color
     * stops. We could wait for their fix (its on its way) or work around it.
     * This is the work around. We want to change our stops to return in this
     * format:
     *	 ["color %", "color %", ...]
     */
					// Order the colors depending on the heat scale color order property.
					var heatScalePropId = colorPropsCreatorInstance.getHeatScalePalettePropertyNameWithLayer(dataLayerId);
					var colorOrder = _this13.visModel.getPropertyValue(heatScalePropId);
					var paletteFills = palette.fills;
					var paletteLength = paletteFills.length;
					for (var index = 0; index < paletteLength; index++) {
						// If the colorOrder is dark to light then reverse the colors.
						var color = colorOrder !== 'DarkerForLowerValue' ? paletteFills[index].fill : paletteFills[paletteLength - index - 1].fill;
						var at = paletteFills[index].at;
						var colorStr = color + ' ' + String(at * 100) + '%';
						colors.push(colorStr);
					}
					paletteDef.colors = colors;
				}
				return paletteDef;
			});
		},

		/**
  * @param {Object} palette - viprWidget palette
  * @returns secondary color index is needed, undefined otherwise.
  */
		_getSecondaryColorIndexForCatColorPalettes: function _getSecondaryColorIndexForCatColorPalettes(palette) {
			var defaultSecondaryColor = void 0;
			var secondaryColorProp = void 0;
			var singlePaletteProps = VIPRUtils.getSinglePaletteProperties(this.visAPI.getVisId());
			if (singlePaletteProps) {
				Object.values(singlePaletteProps).forEach(function (propDesc) {
					if (propDesc.defaultColorIndex === 1) {
						secondaryColorProp = propDesc;
					}
				});
			}
			if (secondaryColorProp) {
				var secondaryValue = this.visModel.getPropertyValue(secondaryColorProp.id);
				defaultSecondaryColor = secondaryValue !== null ? secondaryValue : 1;
				// Ensure defaultIndex is not above the number of colors in the current palette
				defaultSecondaryColor = defaultSecondaryColor < palette.fills.length ? defaultSecondaryColor : 1;
			}
			return defaultSecondaryColor;
		},

		/**
  * @param {Object} palette - viprWidget palette
  * @returns {Object} primary and secondary color index
  */
		_getColorIndicesForCatColorPalettes: function _getColorIndicesForCatColorPalettes(palette, propertyName) {
			var defaultIndex = this.visModel.getPropertyValue('defaultPaletteIndex') || this.visModel.getPropertyValue(propertyName + '_defaultIndex') || 0;
			// Ensure defaultIndex is not above the number of colors in the current palette
			defaultIndex = defaultIndex < palette.fills.length ? defaultIndex : 0;

			var defaultSecondaryColor = this._getSecondaryColorIndexForCatColorPalettes(palette);

			return {
				defaultIndex: defaultIndex,
				secondaryIndex: defaultSecondaryColor
			};
		},

		/**
  * @param viprPalette - viprWidget.palettes item describing a specific vipr palette
  * @param possibleDataSlots - all possible data slots for the current vis type.
  * @returns a categorical palette description needed to set a palette resolver in VIPR
  * eg.
  * {
  *	type: "cat"
  *	colors: description of colors (either continuous or categorical)
  * defaultIndex: index into the palette for an element when the color by slot is empty.
  * }
  */
		_getCategoricalColorPaletteDesc: function _getCategoricalColorPaletteDesc(viprPalette, possibleDataSlots) {
			var _this14 = this;

			// Get either the overriden palette or the default palette
			var colorPropsCreatorInstance = ColorPropertiesCreator.getInstance();
			var dataLayerId = colorPropsCreatorInstance.getDataLayerIdFromPaletteAndSlots(viprPalette, possibleDataSlots);
			var propertyName = colorPropsCreatorInstance._getCategoricalColorPalettePropertyNameWithLayer(dataLayerId);
			var paletteName = this.visModel.getPropertyValue(propertyName) || this._getDefaultCategoricalPaletteName();

			return this.colorsService.getPalette({
				paletteId: paletteName,
				type: 'ColorPalette'
			}).then(function (palette) {
				var paletteDef = {
					type: '',
					colors: ''
				};

				var colorIndices = _this14._getColorIndicesForCatColorPalettes(palette, propertyName);

				if (palette) {
					paletteDef.type = 'cat';
					paletteDef.colors = palette.fills;
					paletteDef.defaultIndex = colorIndices.defaultIndex;
					paletteDef.secondaryColorIndex = colorIndices.secondaryIndex;
				}
				return paletteDef;
			});
		},

		/**
  * @param viprPalette - viprWidget.palettes item describing a specific vipr palette
  * @param possibleDataSlots - all possible data slots for the current vis type.
  * @returns a palette description needed to set a palette resolver in VIPR
  * eg.
  * {
  *	type: "" // cat or cont
  *	colors: description of colors (either continuous or categorical)
  * defaultIndex: index into the palette for an element when the color by slot is empty. This is only supplied for categorical palettes.
  * }
  */
		_getPaletteDescForPalette: function _getPaletteDescForPalette(viprPalette, possibleDataSlots) {
			var getColorPaletteDesc = void 0;
			switch (viprPalette.paletteType) {
				case 'cont':
				case 'customcont':
					getColorPaletteDesc = this._getContinuousColorPaletteDesc.bind(this);
					break;
				case 'cat':
				case 'single':
				case 'customcat':
					getColorPaletteDesc = this._getCategoricalColorPaletteDesc.bind(this);
					break;
				default:
					getColorPaletteDesc = null;
			}

			return getColorPaletteDesc ? getColorPaletteDesc(viprPalette, possibleDataSlots).then(function (desc) {
				desc.type = viprPalette.paletteType;
				return desc;
			}) : Promise.resolve({});
		},

		_getEntryDurationProperty: function _getEntryDurationProperty() {
			var name = null;
			var configuration = VIPRConfig.getConfig(this.visAPI.getVisId());
			if (configuration && configuration.entryDurationProperty) {
				name = configuration.entryDurationProperty;
			} else if (this.viprWidget.properties.get('effect.duration')) {
				var val = VIPRUtils.getOverridenDefaultForProperty(this.visAPI.getVisId(), 'effect.duration').defaultValue;
				if (val !== null && val >= 0) {
					// backward compatible value, if we actually have a value to set (I.e defaultValue is configured)
					name = 'effect.duration';
				}
			}
			return name;
		},

		_isDurationPropertyValuePropValid: function _isDurationPropertyValuePropValid(value) {
			return !(value === undefined || value < 0);
		},


		_applyEntryDurationProperty: function _applyEntryDurationProperty(durationOverwrite) {
			var propertyName = this._getEntryDurationProperty();
			if (!propertyName) {
				return;
			}

			/*
   * 3 possible values for the duration:
   * 1) zero - set the value to 0 so there is no animation
   * 2) model - ask the model for the value to use. If the user has set a value
   * then it will be used.
   * 3) if value is still undefined, check our local config for overrides.
   *
   * zero is a special case where we set all the animations values to zero.
   * for now this only includes effect.duration.
   *
   */
			var value = void 0;
			var effectValue = VIPRUtils.getOverridenDefaultForProperty(this.visAPI.getVisId(), 'effect.duration').defaultValue || 0;
			if (durationOverwrite === 'zero') {
				value = 0;
				effectValue = 0;
			} else if (durationOverwrite === 'model') {
				value = this.visModel.getPropertyValue(propertyName);
			}
			//default (and if model has no value) - check for config override.
			if (!this._isDurationPropertyValuePropValid(value)) {
				value = VIPRUtils.getOverridenDefaultForProperty(this.visAPI.getVisId(), propertyName).defaultValue;
			}
			// If we still don't have a value, forget it, we'll use the default.
			if (this._isDurationPropertyValuePropValid(value)) {
				this.setProperty({ id: 'effect.duration', value: effectValue });
				this.setProperty({ id: propertyName, value: value });
			}
		},

		/**
  * Single palettes are handled differently than categorical or continuous
  * For one, there is always two of them. Secondly, both the single palette
  * resolvers must use the same categorical palette (or at least same colors)
  */
		_applySavedSingleColorPalette: function _applySavedSingleColorPalette() {
			var _this15 = this;

			// Create the categorical palette resolver needed for the single palette.
			return this._getCategoricalColorPaletteDesc().then(function (catPaletteDesc) {
				return VIPRProperties.getInstance().setSinglePaletteResolvers(_this15.viprWidget, catPaletteDesc);
			});
		},

		/**
  * VIPR has some properties in its bundles that are handled in a different way.
  * We have to protect against setting them using the properties API as there
  * could be unexpected side affects.
  * @param property - specific vipr defined property in question
  * @returns true iff the property should be applied
  * @// TODO: replace this list with dontApply: true in the VIPR config
  */
		_shouldPropertyBeApplied: function _shouldPropertyBeApplied(propertyName) {
			var blackList = ['legend.display', 'legend.position', 'effect.duration', 'effect.entry.line.duration', 'effect.entry.bar.duration'];
			blackList = blackList.concat(this._getDontApplyProperties());
			return !(blackList.indexOf(propertyName) !== -1);
		},

		_getDontApplyProperties: function _getDontApplyProperties() {
			var dontApplyProperties = [];
			//Since the VIDA 2.10 change, VIDA is now treating color palettes like properties.
			//They are now included as part of the VIPRWidget properties.
			//Therefore, we dont want to override the palettes with the default value
			//Add them to the list of dontApplyProperties
			var colorPalettes = VIPRUtils.getPalettes(this.viprWidget.properties, this.visualization.getDefinition().getId());
			colorPalettes.forEach(function (palette) {
				if (palette.name !== 'colors.series') {
					//colors.series is marked in VIPRConfig to not set the default value
					dontApplyProperties.push(palette.name);
				}
			});
			return dontApplyProperties;
		},


		/**
  * Set the palette resolvers for the categorical and continuous palettes.
  */
		_applySavedNonSingleColorPalette: function _applySavedNonSingleColorPalette() {
			var _this16 = this;

			var promises = [];

			// Only apply the palettes associated with the current visualization.
			var possibleDataSlots = this.visualization.getSlots().getSlotList();
			VIPRUtils.getPalettes(this.viprWidget.properties, this.visualization.getDefinition().getId()).forEach(function (palette) {
				promises.push(_this16._getPaletteDescForPalette(palette, possibleDataSlots).then(function (paletteDef) {
					// Make sure there is avalid defintion for the palette type.
					if (paletteDef) {
						VIPRProperties.getInstance().setPaletteResolvers(palette, paletteDef, possibleDataSlots, _this16.visAPI.getFredIsRed());
					}
					return paletteDef;
				}));
			});

			return Promise.all(promises);
		},

		/**
  * Apply all palette properties relevant to the current visualization
  */
		_applySavedColorPalette: function _applySavedColorPalette() {
			var isThereSinglePalettes = VIPRUtils.getSinglePalettes(this.viprWidget.properties, this.visualization.getDefinition().getId()).length > 0;
			if (isThereSinglePalettes) {
				return this._applySavedSingleColorPalette();
			} else {
				return this._applySavedNonSingleColorPalette();
			}
		},

		/**
  * VIPR will optimize the size of a visualization when told (i.e. remove axis labels
  * when it thinks more space should be given to the visual elements). We want
  * to enable this be default or if we are showing small multiples. It should
  * only be disabled for upgraded content.
  */
		_setOptimizedSizeProperty: function _setOptimizedSizeProperty(visId) {
			var propertyId = 'optimizeSize';
			var defaultValue = this._getPropValueWhenSaving(visId, propertyId);
			if (!defaultValue) {
				// Do we have small multiple mapped slots?
				var aMultipliers = _.filter(this.visualization.getSlots().getMappedSlotList(), function (dataSlot) {
					return dataSlot.getDefinition().getProperty('multiplier') === true;
				});
				// If we have small multiple mapped slots then lets optimize.
				if (aMultipliers && aMultipliers.length > 0 || this.visAPI.isOptimizeForSize()) {
					defaultValue = true;
				}
			}
			var propInfo = {
				id: propertyId,
				value: defaultValue === undefined || defaultValue === null ? true : defaultValue // true by default
			};

			this.setProperty(propInfo);
		},

		/**
  * @return the default background color in hex
  */
		_getDefaultBackgroundColorInHex: function _getDefaultBackgroundColorInHex() {
			var defaultSpecColor = this.colorsService.getPropertyForUIElement('widget', 'backgroundColor').value;
			// The value may be equal to a simple hex or variable name or it may be an object which we have to dig into a little more.
			if (_.isObject(defaultSpecColor)) {
				defaultSpecColor = defaultSpecColor.color;
			}
			// I love themes and the theme definition object <insert sarcasm smirk here>. The color may come back
			// as a nice hex value or as a variable that needs to be mapped. If its a variable it will begin with
			// a '$' and we have to map it to get the hex value.
			if (defaultSpecColor.substring(0, 1) === '$') {
				defaultSpecColor = this.colorsService.getValueForVariable('Color', defaultSpecColor.substr(1));
			}
			// Ensure we have a valid hex value or transparent
			if (defaultSpecColor.substring(0, 1) === '#' && !isNaN(parseInt(defaultSpecColor.substr(1), 16)) || defaultSpecColor === 'transparent') {
				return defaultSpecColor;
			}
			return null;
		},

		/**
  * Set the background color of the viprwidget
  */
		_setBackgroundColor: function _setBackgroundColor() {
			var _this17 = this;

			if (this.highContrastEnabled) {
				//Clear background and foreground element colors in high-contrast mode and allow idvis to determine them.
				this.setProperty({
					id: 'backgroundColor',
					value: undefined
				});

				var fgElementProps = this.colorsService.getForegroundPropertiesForUIElement();
				var propMap = {};
				this.visModel.getDefinition().themeMapping.forEach(function (prop) {
					propMap[prop.id] = prop.mapping;
				});
				fgElementProps.forEach(function (prop) {
					if (propMap[prop.id]) {
						propMap[prop.id].forEach(function (mappedPropName) {
							_this17.setProperty({ id: mappedPropName, value: undefined });
						});
					} else {
						_this17.setProperty({ id: prop.id, value: undefined });
					}
				});
			} else {
				// Get the current fill color stored in our model
				var fillColor = this.visModel.ownerWidget.model.fillColor;
				var hexFillColor = void 0;

				if (fillColor) {
					// If there is a defined fill color its in the form of 'colorx', we need
					// it in the hex form. Get the hex value and set the property.
					hexFillColor = this.colorsService.getHexColorFromDashboardColorSet(fillColor);
				} else {
					// Get the default value and  set the property.
					hexFillColor = this._getDefaultBackgroundColorInHex();
				}

				var propInfo = {
					id: 'backgroundColor',
					value: hexFillColor
				};
				this.setProperty(propInfo);
			}
		},

		_getColorToSave: function _getColorToSave(savedValue, isCustomVis) {
			var colorToSave = void 0;
			if (_.isString(savedValue)) {
				if (this.colorsService.isCustomColor(savedValue)) {
					this.colorsService.addCustomColor(this.colorsService.getHexColorFromClassName(savedValue));
				}
				colorToSave = this.colorsService.getHexColorFromDashboardColorSet(savedValue);
			} else {
				if (isCustomVis) {
					savedValue = this.colorsService.makeSureColorIsValidInModel(savedValue);
				}
				colorToSave = savedValue;
			}
			return colorToSave;
		},


		/**
  * Apply the color set property of the specified property id
  * @param {String} visId - visualization type id
  * @param {String} propId - id of the color set property to set
  */
		_applyColorSetProperty: function _applyColorSetProperty(visId, propId, isCustomVis) {
			var savedValue = this._getPropValueWhenSaving(visId, propId);

			// Only apply the color if we have something saved
			if (savedValue) {
				var colorToSave = this._getColorToSave(savedValue, isCustomVis);
				// Put the information in a structure Vida understands
				var colorInfo = {
					id: propId,
					value: colorToSave
				};
				this.setProperty(colorInfo);
			}
		},


		/**
  * @param {String} visId - id of the current visualization.
  * @param {Array} properties - array of property names for the specified vis
  */
		_applyColorSetProperties: function _applyColorSetProperties(visId, properties, isCustomVis) {
			var _this18 = this;

			properties.forEach(function (propertyName) {
				var property = _this18.visModel.getPropertyById(propertyName);
				if (property && property.colorClass) {
					_this18._applyColorSetProperty(visId, propertyName, isCustomVis);
				}
			});
		},
		_getLengthPropWithPxUnit: function _getLengthPropWithPxUnit(value) {
			if (typeof value !== 'undefined' && value !== null) {
				return value && value.endsWith && value.endsWith('px') ? value : value + 'px';
			}
			return null;
		},


		_applyTargetMarkerThicknessProperties: function _applyTargetMarkerThicknessProperties(visId) {
			var targetThickness = this._getPropValueWhenSaving(visId, 'target.marker.thickness');
			if (VIPRProperties.getInstance().isTargetThicknessInputValid(targetThickness)) {
				var valueToSet = this._getLengthPropWithPxUnit(targetThickness);
				var targetThicknessInfo = {
					id: 'target.marker.thickness',
					value: valueToSet
				};
				this.setProperty(targetThicknessInfo);
			}
		},

		/**
  * Apply any saved layer transparencies.
  * Only applies to visualizations that support layers.
  */
		_applySavedLayerTransparencies: function _applySavedLayerTransparencies() {
			var configuration = VIPRConfig.getConfig(this.visAPI.getVisId());
			if (configuration && configuration.supportsLayers) {
				configuration.layerDescriptions.forEach(function (layerDesc) {
					var val = this.visModel.getPropertyValue(layerDesc.colorRelatedProps.transparencyProp);
					if (val !== undefined && val !== null) {
						// Can be false
						var propInfo = {
							id: layerDesc.colorRelatedProps.transparencyProp,
							value: val * 100 // Property is saved from 0.0 to 1.0, we want 0-100
						};
						this.setProperty(propInfo);
					}
				}.bind(this));
			}
		},

		/**
  * @returns true iff the value is not undefined.
  */
		_isPropValueValid: function _isPropValueValid(value, propDesc) {
			var isPropValidByDescCheck = propDesc.checkForValidValue ? propDesc.checkForValidValue(value) : true;
			return isPropValidByDescCheck && value !== undefined;
		},


		/**
  * @returns true if the prop is inactive, readonly and has a valid default value
  */
		_isValidReadOnlyProperty: function _isValidReadOnlyProperty(propDesc) {
			return propDesc && propDesc.isActive === false && propDesc.isReadOnly === true && this._isPropValueValid(propDesc.defaultValue, propDesc);
		},


		/**
  * So, Vida will provide us with defaults for value and default value among
  * other things. We need these for properties such as themeable props. One
  * thing we don't need is all the vida property methods that come alogn with
  * the property. So, we want to overide our model prop with wanted object
  * properties only. Note, calling this will actually override the property
  * in the model!
  * @param {String} propertyName - id of the property of interest.
  */
		overrideWithVidaProperty: function overrideWithVidaProperty(propertyName) {
			var modelProp = this.visModel.getPropertyById(propertyName);
			var vidaProp = this.viprWidget.properties.get(propertyName);
			if (vidaProp && modelProp) {
				// Should never happen but Vida is missing some deprecated props right now.
				var propsWeWantFromVidaProp = {
					active: vidaProp.active,
					caption: vidaProp.caption,
					defaultValue: vidaProp.defaultValue,
					description: vidaProp.description,
					name: vidaProp.name,
					type: vidaProp.type,
					possibleValues: vidaProp.possibleValues
				};
				if (modelProp.value !== undefined) {
					_.extend(propsWeWantFromVidaProp, { value: vidaProp.value });
				}
				_.defaults(modelProp, propsWeWantFromVidaProp);
			}
		},


		/**
  * When saving the values for a property it can be a little tricky. For example,
  * if you set the stacked parameter to false in an area chart and then change the
  * vis type to a stacked bar chart we still want it to stack. So, we should
  * check the property to see if its active. If it isn't then we have to do a
  * little priority checking.
  * @param {String} visId - bundle id
  * @param {String} propertyName - name of the property of interest
  * @returns value of the property to use when saving.
  */
		_getPropValueWhenSaving: function _getPropValueWhenSaving(visId, propertyName) {
			var result = null;
			var propDesc = VIPRUtils.getOverridenDefaultForProperty(visId, propertyName);

			/*
   * Do we have a readonly property (i.e. one that has a default and can't be
   * overriden by a user or programmatically)? If so, use the defaultValue
   */
			if (this._isValidReadOnlyProperty(propDesc)) {
				result = propDesc.defaultValue;
			} else {
				// Get any vida defaults we may need.
				this.overrideWithVidaProperty(propertyName);

				// Now we should look at the model. If there is a valid (non undefined) value there
				// use it, otherwise check for the defaultValue in the config.
				var modelValue = this.visModel.getPropertyValue(propertyName);
				if (this._isPropValueValid(modelValue, propDesc)) {
					result = modelValue;
				} else {
					result = propDesc.defaultValue;
				}
			}

			return result;
		},
		_registerListenerForPropChanges: function _registerListenerForPropChanges() {
			var _this19 = this;

			var listenForPropChanges = this.visAPI.getListenForPropChangesFromDefinition();
			if (listenForPropChanges) {
				_.each(listenForPropChanges, function (prop) {
					_this19.viprWidget.properties.get(prop).on('value', _this19._onViprPropValueChange.bind(_this19));
				});
				//should update the flag when there are actual listeners have been registered
				this.propChangeslistener = true;
			}
		},


		/**
  * When a visualization is about to be rendered we want to ensure that the
  * visualization properties are all set to the user overriden value, if any.
  */
		_applySavedModelProperties: function _applySavedModelProperties(renderInfo) {
			var _this20 = this;

			return this._applySavedColorPalette().then(function () {
				var visId = _this20.visAPI.getVisId();
				var configuration = VIPRConfig.getConfig(visId);
				var configInclude = configuration && configuration.config && configuration.config.include ? configuration.config.include : [];
				if (configuration && configuration.isCustomVis && configuration.bundleInclude) {
					configInclude = configuration.bundleInclude;
				}
				configInclude.forEach(function (propertyName) {
					_this20._setModelProperty(propertyName, visId);
				});
				_this20._applySavedLayerTransparencies();

				if (renderInfo && renderInfo.extraInfo && renderInfo.extraInfo.preserveDrawingBuffer === true) {
					_this20.setProperty({ id: 'webGL.preserveDrawingBuffer', value: true });
				} else {
					_this20.setProperty({ id: 'webGL.preserveDrawingBuffer', value: false });
				}
				// Now that the default value for optimize size is set, override it to what we really want.
				_this20._setOptimizedSizeProperty(visId);
				_this20._setBackgroundColor();
				_this20._applyEntryDurationProperty(renderInfo && renderInfo.extraInfo && renderInfo.extraInfo.entryDuration);
				_this20._applyTargetMarkerThicknessProperties(visId);
				// Apply all color set property values as denoted in VIPRConfig
				configuration && _this20._applyColorSetProperties(visId, configuration.config.include, configuration.isCustomVis);
			});
		},

		/**
  * @returns array of run time properties in a format usable by the properties panel.
  */
		getProperties: function getProperties() {
			var _this21 = this;

			return this.whenVisControlReady().then(function () {
				return VIPRProperties.getInstance().getProperties(_this21.viprWidget.properties, _this21.visualization.getSlots().getSlotList(), _this21.visualization.getDefinition().getId());
			}).then(function (properties) {
				//todo ???widget.getFeature? is it dead code??  livewidget_cleanup
				var propertiesFilter = _this21.visModel.ownerWidget.getFeature('visPropertiesFilter');
				return propertiesFilter ? propertiesFilter.filter(properties) : properties;
			});
		},

		getPropertyList: function getPropertyList() {
			return VIPRProperties.getInstance().getPropertyList(this.viprWidget.properties, this.visualization.getSlots().getSlotList(), this.content);
		},

		getPropertyLayoutList: function getPropertyLayoutList() {
			return VIPRProperties.getInstance().getPropertyLayoutList(this.viprWidget.properties, this.visualization.getSlots().getSlotList(), this.content, this.dashboard);
		},

		/**
   * When a theme changes we expect that all the models and their theme definitions have been
   * updated before we are notified. Then all we have to do is re-render.
   *
   * NOTE: Only Rave and VIPR Views have to do this as all other (current views) use css.
   */
		onChangeTheme: function onChangeTheme() {
			this.visModel.getRenderSequence().reRender();
		},

		/**
  * Set the specified property to the specified value
  * @param - info -> property of interest
  *	{
  *		id - property id,
  *		value - new value for the prop
  *	}
  */
		setProperty: function setProperty(info) {
			VIPRProperties.getInstance().setProperty(this.viprWidget, info);
		},

		/**
   * Generate thumbnail
   *
   * @param {Object} [options] - generate options
   * @param {Object} [options.size] - aspect ratio to generate
   * @param {number} [options.size.width=150] - aspect ratio width
   * @param {number} [options.size.height=100] - aspect ratio height
   * @param {string} [options.type=svg] - type of thumbnail to generate
   *
   * @return {Promise}
   */
		generateThumbnail: function generateThumbnail() {
			var _this22 = this;

			var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
			    _ref2$size = _ref2.size,
			    size = _ref2$size === undefined ? {} : _ref2$size,
			    _ref2$type = _ref2.type,
			    type = _ref2$type === undefined ? 'svg' : _ref2$type;

			var _getThumbnailConfig = this.getThumbnailConfig(),
			    _getThumbnailConfig$e = _getThumbnailConfig.enabled,
			    enabled = _getThumbnailConfig$e === undefined ? true : _getThumbnailConfig$e;

			var isMappingIncomplete = false;
			var isDisabled = !enabled;

			if (!this.isMappingComplete()) {
				isMappingIncomplete = true;
			}

			var promise = void 0;
			if (isDisabled || isMappingIncomplete) {
				promise = Promise.resolve();
			} else {
				var _getThumbnailConfig2 = this.getThumbnailConfig(),
				    configType = _getThumbnailConfig2.type,
				    _getThumbnailConfig2$ = _getThumbnailConfig2.properties,
				    configProperties = _getThumbnailConfig2$ === undefined ? {} : _getThumbnailConfig2$;

				var width = size.width,
				    height = size.height;


				if (configType) {
					type = configType;
				}

				var handler = this._createPredictHandler(VIPRCachedDataRequestHandler, this.predictHandler);

				promise = this.viprWidget.createHidden(width, height, type, true, IGNORED_THUMBNAIL_DECORATIONS, handler).then(function (_hiddenViprWidgetInstance) {

					var properties = Object.assign({}, DEFAULT_THUMBNAIL_PROPERTIES, configProperties);

					Object.keys(properties).forEach(function (key) {
						var value = properties[key];
						_hiddenViprWidgetInstance.setProperty(key, value);
					});

					return _hiddenViprWidgetInstance.render(type).then(function (_renderOperation) {
						var result = void 0;
						if (_renderOperation && _renderOperation.completed) {
							result = _this22.processThumbnailData(_renderOperation.data);
						}
						_hiddenViprWidgetInstance.destroy();
						return result;
					});
				});
			}

			return promise.then(function (thumbnail) {
				return {
					thumbnail: thumbnail,
					isMappingIncomplete: isMappingIncomplete,
					isDisabled: isDisabled
				};
			});
		},

		processThumbnailData: function processThumbnailData(data) {
			var _this23 = this;

			var result = void 0;
			if (data instanceof Blob) {
				result = new Promise(function (resolve, reject) {
					try {
						var reader = new FileReader();
						var altThumbnailImage = stringResources.get('loadedThumbnailImage', { visType: _this23.visModel.getDefinition().label });
						reader.addEventListener('loadend', function () {
							resolve('<img src="' + reader.result + '" alt="' + altThumbnailImage + '"></img>');
						});
						reader.readAsDataURL(data);
					} catch (error) {
						reject(error);
					}
				});
			} else {
				result = Promise.resolve(data);
			}
			return result;
		},

		/**
   * Thumbnail config
   *
   * @return {Object} thumbnail config
   */
		getThumbnailConfig: function getThumbnailConfig() {
			var _ref3 = VIPRConfig.getConfig(this.visAPI.getVisId()) || {},
			    _ref3$thumbnail = _ref3.thumbnail,
			    thumbnail = _ref3$thumbnail === undefined ? {} : _ref3$thumbnail;

			return thumbnail;
		},

		_isSmartTitleEnabled: function _isSmartTitleEnabled() {
			var featureChecker = this.dashboard && this.dashboard.getGlassCoreSvc('.FeatureChecker');
			if (featureChecker && featureChecker.checkValue) {
				return !featureChecker.checkValue('dashboard', 'SmartTitle', 'disabled');
			}
			return false;
		},

		/**
   * check if vipr widget is valid (its id does not exists in the destroyed widgets array)
   *
   * @return {Boolean} True, if valid, False otherwise
   */
		_isViprWidgetValid: function _isViprWidgetValid() {
			var _this24 = this;

			return !this._deferredDestroyViprWidgetsList.find(function (widget) {
				return widget.id === _this24.viprWidget.id;
			});
		}
	});

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