/*!
 * ${copyright}
 */

// Provides control sap.m.Image.
sap.ui.define([
	'./library',
	'sap/ui/core/Control',
	'sap/ui/base/DataType',
	'sap/base/security/URLListValidator',
	'./ImageRenderer',
	"sap/ui/events/KeyCodes",
	"sap/ui/thirdparty/jquery",
	"sap/base/security/encodeCSS",
	"sap/ui/core/library"
],
	function(library, Control, DataType, URLListValidator, ImageRenderer, KeyCodes, jQuery, encodeCSS, coreLibrary) {
	"use strict";



	// shortcut for sap.m.ImageMode
	var ImageMode = library.ImageMode;

	// shortcut for sap.ui.core.aria.HasPopup
	var AriaHasPopup = coreLibrary.aria.HasPopup;

	/**
	 * Constructor for a new Image.
	 *
	 * @param {string} [sId] id for the new control, generated automatically if no id is given
	 * @param {object} [mSettings] initial settings for the new control
	 *
	 * @class
	 * A wrapper around the &lt;img&gt; tag; the image can be loaded from a remote or local server.
	 *
	 * If property <code>densityAware</code> is true, a density-specific image will be loaded by constructing
	 * a density-specific image name in format <code>[imageName]@[densityValue].[extension]</code> from the
	 * given <code>src</code> and the <code>devicePixelRatio</code> of the current device. The only supported
	 * density values are 1, 1.5 and 2. If the original <code>devicePixelRatio</code> ratio isn't one of the
	 * three valid numbers, it will be rounded to the nearest one.
	 *
	 * There are various size setting options available, and the images can be combined with actions.
	 *
	 * From version 1.30, a new image mode {@link sap.m.ImageMode.Background} is added. When this mode
	 * is set, the <code>src</code> property is set using the CSS style <code>background-image</code>.
	 * The properties <code>backgroundSize</code>, <code>backgroundPosition</code>, and <code>backgroundRepeat</code>
	 * take effect only when the image is in <code>sap.m.ImageMode.Background</code> mode. In order to display
	 * the high density image correctly, the <code>backgroundSize</code> should be set to the dimension of the
	 * normal density version.
	 *
	 * @see {@link topic:f86dbe9d7f7d48dea5286003b1322165 Image}
	 * @see {@link fiori:https://experience.sap.com/fiori-design-web/image/ Image}
	 *
	 * @extends sap.ui.core.Control
	 * @implements sap.ui.core.IFormContent
	 *
	 * @author SAP SE
	 * @version ${version}
	 *
	 * @public
	 * @alias sap.m.Image
	 */
	var Image = Control.extend("sap.m.Image", /** @lends sap.m.Image.prototype */ {
		metadata : {

			interfaces : ["sap.ui.core.IFormContent"],
			library : "sap.m",
			designtime: "sap/m/designtime/Image.designtime",
			properties : {

				/**
				 * Relative or absolute path to URL where the image file is stored.
				 *
				 * The path will be adapted to the density-aware format according to the density of the device
				 * following the naming convention [imageName]@[densityValue].[extension].
				 */
				src : {type : "sap.ui.core.URI", group : "Data", defaultValue : null},

				/**
				 * When the empty value is kept, the original size is not changed.
				 *
				 * It is also possible to make settings for width or height only, in which case the original
				 * ratio between width/height is maintained. When the <code>mode</code> property is set to
				 * <code>sap.m.ImageMode.Background</code>, this property always needs to be set.
				 * Otherwise the output DOM element has a 0 size.
				 */
				width : {type : "sap.ui.core.CSSSize", group : "Appearance", defaultValue : null},

				/**
				 * When the empty value is kept, the original size is not changed.
				 *
				 * It is also possible to make settings for width or height only, in which case the original
				 * ratio between width/height is maintained. When the <code>mode</code> property is set to
				 * <code>sap.m.ImageMode.Background</code>, this property always needs to be set.
				 * Otherwise the output DOM element has a 0 size.
				 */
				height : {type : "sap.ui.core.CSSSize", group : "Appearance", defaultValue : null},

				/**
				 * A decorative image is included for design reasons; accessibility tools will ignore decorative images.
				 *
				 * Note: If the image has an image map (<code>useMap</code> is set), this property will be overridden
				 * (the image will not be rendered as decorative). A decorative image has no <code>ALT</code> attribute,
				 * so the <code>alt</code> property is ignored if the image is decorative.
				 */
				decorative : {type : "boolean", group : "Accessibility", defaultValue : true},

				/**
				 * The alternative text that is displayed in case the image is not available, or cannot be displayed.
				 *
				 * If the image is set to decorative, this property is ignored.
				 */
				alt : {type : "string", group : "Accessibility", defaultValue : null},

				/**
				 * The name of the image map that defines the clickable areas.
				 */
				useMap : {type : "string", group : "Misc", defaultValue : null},

				/**
				 * If this is set to <code>true</code>, one or more network requests will be made
				 * that try to obtain the density perfect version of the image.
				 *
				 * By default, this is set to <code>false</code>, so the <code>src</code> image is loaded
				 * directly without attempting to fetch the density perfect image for high-density devices.
				 *
				 * <b>Note:</b> Before 1.60, the default value was set to <code>true</code>, which
				 * brought redundant network requests for apps that used the default but did not
				 * provide density perfect image versions on server-side.
				 * You should set this property to <code>true</code> only if you also provide the
				 * corresponding image versions for high-density devices.
				 */
				densityAware : {type : "boolean", group : "Misc", defaultValue : false},

				/**
				 * The source property which is used when the image is pressed.
				 */
				activeSrc : {type : "sap.ui.core.URI", group : "Data", defaultValue : ""},

				/**
				 * Defines how the <code>src</code> and the <code>activeSrc</code> is output to the DOM Element.
				 *
				 * When set to <code>sap.m.ImageMode.Image</code>, which is the default value, the <code>src</code>
				 * (<code>activeSrc</code>) is set to the <code>src</code> attribute of the &lt;img&gt; tag. When
				 * set to <code>sap.m.ImageMode.Background</code>, the <code>src</code> (<code>activeSrc</code>)
				 * is set to the CSS style <code>background-image</code> and the root DOM element is rendered as a
				 * &lt;span&gt; tag instead of an &lt;img&gt; tag.
				 * @since 1.30.0
				 */
				mode : {type : "sap.m.ImageMode", group : "Misc", defaultValue : "Image"},

				/**
				 * Defines the size of the image in <code>sap.m.ImageMode.Background</code> mode.
				 *
				 * This property is set on the output DOM element using the CSS style <code>background-size</code>.
				 * It takes effect only when the <code>mode</code> property is set to <code>sap.m.ImageMode.Background</code>.
				 * @since 1.30.0
				 */
				backgroundSize : {type : "string", group : "Appearance", defaultValue : "cover"},

				/**
				* Defines the position of the image in <code>sap.m.ImageMode.Background</code> mode.
				*
				* This property is set on the output DOM element using the CSS style <code>background-position</code>.
				* It takes effect only when the <code>mode</code> property is set to <code>sap.m.ImageMode.Background</code>.
				* @since 1.30.0
				*/
				backgroundPosition : {type : "string", group : "Appearance", defaultValue : "initial"},

				/**
				* Defines whether the source image is repeated when the output DOM element is bigger than the source.
				*
				* This property is set on the output DOM element using the CSS style <code>background-repeat</code>.
				* It takes effect only when the <code>mode</code> property is set to <code>sap.m.ImageMode.Background</code>.
				* @since 1.30.0
				*/
				backgroundRepeat : {type : "string", group : "Appearance", defaultValue : "no-repeat"},
				/**
				* Enables lazy loading for images that are offscreen. If set to <code>true</code>, the property
				* ensures that offscreen images are loaded early enough so that they have finished loading once
				* the user scrolls near them.
				*
				* <b>Note:</b> Keep in mind that the property uses the loading attribute of HTML <code>&lt;img&gt;</code> element
				* which is not supported for Internet Explorer.
				*
				* @since 1.87
				*/
				lazyLoading : {type : "boolean", defaultValue : false },

				/**
				 * Defines the aria-haspopup attribute of the <code>Image</code>.
				 *
				 * <b>Guidance for choosing appropriate value:</b>
				 * <ul>
				 * <li> We recommend you to use the property only when press handler is set.</li>
				 * <li> If you use controls based on <code>sap.m.Popover</code> or <code>sap.m.Dialog</code>,
				 * then you must use <code>AriaHasPopup.Dialog</code> (both <code>sap.m.Popover</code> and
				 * <code>sap.m.Dialog</code> have role "dialog" assigned internally).</li>
				 * <li> If you use other controls, or directly <code>sap.ui.core.Popup</code>, you need to check
				 * the container role/type and map the value of <code>ariaHasPopup</code> accordingly.</li>
				 * </ul>
				 *
				 * @since 1.87.0
				 */
				ariaHasPopup : {type : "sap.ui.core.aria.HasPopup", group : "Accessibility", defaultValue : AriaHasPopup.None}
			},
			aggregations : {
				/**
				 * A <code>sap.m.LightBox</code> instance that will be opened automatically when the user interacts
				 * with the <code>Image</code> control.
				 *
				 * The <code>tap</code> event will still be fired.
				 */
				detailBox: {type: 'sap.m.LightBox', multiple: false, bindable: "bindable"}
			},
			associations : {
				/**
				 * Association to controls / ids which describe this control (see WAI-ARIA attribute aria-describedby).
				 */
				ariaDescribedBy : {type : "sap.ui.core.Control", multiple : true, singularName : "ariaDescribedBy"},

				/**
				 * Association to controls / ids which label this control (see WAI-ARIA attribute aria-labelledBy).
				 */
				ariaLabelledBy: {type : "sap.ui.core.Control", multiple : true, singularName : "ariaLabelledBy"},

				/**
				 * Association to controls / IDs which are details to this control (see WAI-ARIA attribute aria-details).
				 * @since 1.79
				 */
				ariaDetails: {type : "sap.ui.core.Control", multiple : true, singularName : "ariaDetails"}
			},
			events : {

				/**
				 * Event is fired when the user clicks on the control. (This event is deprecated, use the press event instead)
				 */
				tap : {},

				/**
				 * Event is fired when the user clicks on the control.
				 */
				press : {},

				/**
				 * Event is fired when the image resource is loaded.
				 * @since 1.36.2
				 */
				load : {},

				/**
				 * Event is fired when the image resource can't be loaded. If densityAware is set to true, the event is fired when none of the fallback resources can be loaded.
				 * @since 1.36.2
				 */
				error : {}
			},
			dnd: { draggable: true, droppable: false }
		},

		renderer: ImageRenderer
	});

	Image._currentDevicePixelRatio = (function() {

		// if devicePixelRatio property is not available, value 1 is assumed by default.
		var ratio = (window.devicePixelRatio === undefined ? 1 : window.devicePixelRatio);

		// for ratio in our library, only 1 1.5 2 are valid
		if (ratio <= 1) {
			ratio = 1;
		} else {

			// round it to the nearest valid value
			ratio *= 2;
			ratio = Math.round(ratio);
			ratio /= 2;
		}

		if (ratio > 2) {
			ratio = 2;
		}

		return ratio;
	}());

	Image.prototype.init = function () {
		this._oSvgCachedData = {};
	};

	/**
	 * Function is called when image is loaded successfully.
	 *
	 * @param {jQuery.Event} oEvent
	 * @private
	 */
	Image.prototype.onload = function(oEvent) {
		var iWidth,
			iHeight;

		// This is used to fix the late load event handler problem on ios platform, if the event handler
		// has not been called right after image is loaded, event is triggered manually in onAfterRendering
		// method.
		if (!this._defaultEventTriggered) {
			this._defaultEventTriggered = true;
		}

		// reset the flag for the next rerendering
		this._bVersion2Tried = false;

		var $DomNode = this.$(),
			oDomRef = $DomNode[0];

		// set the src to the real dom node
		if (this.getMode() === ImageMode.Background) {
			// In Background mode, the src is applied to the output DOM element only when the source image is finally loaded to the client side
			$DomNode.css("background-image", "url(\"" + encodeCSS(this._oImage.src) + "\")");
		}

		if (!this._isWidthOrHeightSet()) {
			if (this._iLoadImageDensity > 1) {
				iWidth = Math.round(oDomRef.getBoundingClientRect().width);
				iHeight = Math.round(oDomRef.getBoundingClientRect().height);

				if ((iWidth === oDomRef.naturalWidth) && (iHeight === oDomRef.naturalHeight)) {
					$DomNode.width(iWidth / this._iLoadImageDensity);
				}
			}
		}

		$DomNode.removeClass("sapMNoImg");

		this.fireLoad();
	};

	/**
	 * Function is called when error occurs during image loading.
	 *
	 * @param {jQuery.Event} oEvent
	 * @private
	 */
	Image.prototype.onerror = function(oEvent) {

		// This is used to fix the late load event handler problem on ios platform, if the event handler
		// has not been called right after image is loaded with errors, event is triggered manually in onAfterRendering
		// method.
		if (!this._defaultEventTriggered) {
			this._defaultEventTriggered = true;
		}

		var $DomNode = this.$(),
			sMode = this.getMode(),
			// In Background mode, the src property should be read from the temp Image object
			sSrc = (sMode === ImageMode.Image) ? this._getDomImg().attr("src") : this._oImage.src,
			d = Image._currentDevicePixelRatio,
			sCurrentSrc = this._isActiveState ? this.getActiveSrc() : this.getSrc();

		$DomNode.addClass("sapMNoImg");

		// if src is empty or there's no image existing, just stop
		if (!sSrc || this._iLoadImageDensity === 1) {
			// BCP: 1880526262
			if (this.getAlt() && !this.getDecorative()) {
				// remove the "sapMNoImg" in order to show the alt text
				$DomNode.removeClass("sapMNoImg");
			}
			this.fireError();
			return;
		}

		if (d === 2 || d < 1) {
			// load the default image
			this._iLoadImageDensity = 1;
			this._updateDomSrc(this._generateSrcByDensity(sCurrentSrc, 1));
		} else if (d === 1.5) {
			if (this._bVersion2Tried) {
				setTimeout(jQuery.proxy(function() {
					// if version 2 isn't on the server, load the default image
					this._iLoadImageDensity = 1;
					this._updateDomSrc(this._generateSrcByDensity(sCurrentSrc, 1));
				}, this), 0);
			} else {
				setTimeout(jQuery.proxy(function() {
					// special treatment for density 1.5
					// verify if the version for density 2 is provided or not
					this._iLoadImageDensity = 2;
					this._updateDomSrc(this._generateSrcByDensity(sCurrentSrc, 2));
					this._bVersion2Tried = true;
				}, this), 0);
			}
		}
	};

	/**
	 * Sets the <code>detailBox</code> aggregation.
	 * @param {sap.m.LightBox|undefined} oLightBox - Instance of the <code>LightBox</code> control or undefined
	 * @returns {this} <code>this</code> for chaining
	 * @override
	 * @public
	 */
	Image.prototype.setDetailBox = function (oLightBox) {
		var oCurrentDetailBox = this.getDetailBox();

		if (oLightBox) {
			// In case someone try's to set the same LightBox twice we don't do anything
			if (oLightBox === oCurrentDetailBox) {
				return this;
			}

			// If we already have a LightBox detach old one's event
			if (oCurrentDetailBox) {
				this.detachPress(this._fnLightBoxOpen, oCurrentDetailBox);
			}

			// Bind the LightBox open method to the press event of the Image
			this._fnLightBoxOpen = oLightBox.open;
			this.attachPress(this._fnLightBoxOpen, oLightBox);
		} else if (this._fnLightBoxOpen) {
			// If there was a LightBox - cleanup
			this.detachPress(this._fnLightBoxOpen, oCurrentDetailBox);
			this._fnLightBoxOpen = null;
		}

		return this.setAggregation("detailBox", oLightBox);
	};

	Image.prototype.setSrc = function (sSrc) {
		var oPreviousSrc = this.getSrc(),
			oResult = this.setProperty("src", sSrc);

		if (sSrc && oPreviousSrc !== this.getSrc() && this.getMode() === ImageMode.InlineSvg) {
			sSrc.endsWith("svg") && this._loadSvg();
		}

		return oResult;
	};

	/*
	 * @override
	 */
	Image.prototype.clone = function () {
		var oClone = Control.prototype.clone.apply(this, arguments),
			oCloneDetailBox = oClone.getDetailBox();

		// Handle press event if DetailBox is available
		if (oCloneDetailBox) {

			// Detach the old event
			oClone.detachPress(this._fnLightBoxOpen, this.getDetailBox());

			// Attach new event with the cloned detail box
			oClone._fnLightBoxOpen = oCloneDetailBox.open;
			oClone.attachPress(oClone._fnLightBoxOpen, oCloneDetailBox);

		}

		return oClone;
	};

	/**
	 * the 'beforeRendering' event handler
	 * @private
	 */
	Image.prototype.onBeforeRendering = function() {
		this._defaultEventTriggered = false;
		if (this.getMode() == ImageMode.Image) {
			var $DomNode = this.getDetailBox() ? this.$().find(".sapMImg") : this.$();
			$DomNode.off("load").off("error");
		}

		if (this.getMode() === ImageMode.InlineSvg) {
			this._loadSvg();
		}
	};

	/**
	 * This function is called to register event handlers for load and error event on the image DOM after it's rendered.
	 * It also check if the event handlers are called accordingly after the image is loaded, if not the event handlers are triggered
	 * manually.
	 *
	 * @private
	 */
	Image.prototype.onAfterRendering = function() {
		// BCP 1870456103. Error should be thrown when we have invalid src and DetailBox present.
		var $DomNode = this.getDetailBox() ? this.$().find(".sapMImg") : this.$(),
			sMode = this.getMode(),
			oDomImageRef;

		if (sMode === ImageMode.Image) {
			// bind the load and error event handler
			$DomNode.on("load", jQuery.proxy(this.onload, this));
			$DomNode.on("error", jQuery.proxy(this.onerror, this));

			oDomImageRef = $DomNode[0];
		}

		if (sMode === ImageMode.Background) {
			oDomImageRef = this._oImage;
		}

		// if image has already been loaded and the load or error event handler hasn't been called, trigger it manually.
		if (oDomImageRef && oDomImageRef.complete && !this._defaultEventTriggered) {
			// need to use the naturalWidth property instead of jDomNode.width(),
			// the later one returns positive value even in case of broken image
			if (oDomImageRef.naturalWidth > 0) {
				this.onload({/* empty event object*/});
			} else {
				this.onerror({/* empty event object*/});
			}
		}
	};

	Image.prototype.exit = function() {
		if (this._oImage) {
			// deregister the events from the window.Image object
			jQuery(this._oImage).off("load", this.onload).off("error", this.onerror);
			this._oImage = null;
		} else {
			this.$().off("load", this.onload).off("error", this.onerror);
		}

		if (this._fnLightBoxOpen) {
			this._fnLightBoxOpen = null;
		}

		this._oSvgCachedData = null;
	};

	/**
	 * This binds to the touchstart event to change the src property of the image to the activeSrc.
	 *
	 * @private
	 */
	Image.prototype.ontouchstart = function(oEvent) {
		if (oEvent.srcControl.mEventRegistry["press"] || oEvent.srcControl.mEventRegistry["tap"]) {
			// mark the event for components that needs to know if the event was handled by the Image
			oEvent.setMarked();
		}

		if (oEvent.targetTouches.length === 1 && this.getActiveSrc()) {
			// change the source only when the first finger is on the image, the following fingers doesn't affect
			this._updateDomSrc(this._getDensityAwareActiveSrc());
			this._isActiveState = true;
		}
	};

	/**
	 * This changes the src property of the image back to the src property of the image control.
	 *
	 * @private
	 */
	Image.prototype.ontouchend = function(oEvent) {
		// change the source back only when all fingers leave the image
		// avoid setting the normal state src again when there's no activeSrc property set
		if (oEvent.targetTouches.length === 0 && this.getActiveSrc()) {
			this._isActiveState = false;
			this._updateDomSrc(this._getDensityAwareSrc());
			this.$().removeClass("sapMNoImg");
		}
	};

	Image.prototype.attachPress = function() {
		Array.prototype.unshift.apply(arguments, ["press"]);
		Control.prototype.attachEvent.apply(this, arguments);

		if (this.hasListeners("press")) {
			this.$().attr("tabindex", "0");
			this.$().attr("role", "button");
		}

		return this;
	};

	Image.prototype.detachPress = function() {
		Array.prototype.unshift.apply(arguments, ["press"]);
		Control.prototype.detachEvent.apply(this, arguments);

		if (!this.hasListeners("press")) {
			this.$().removeAttr("tabindex");
			if (this.getDecorative()) {
				this.$().attr("role", "presentation");
			} else {
				this.$().removeAttr("role");
			}
		}

		return this;
	};

	/**
	 * Function is called when image is clicked.
	 *
	 * @param {jQuery.Event} oEvent
	 * @private
	 */
	Image.prototype.ontap = function(oEvent) {
		this.fireTap({/* no parameters */}); //	(This event is deprecated, use the press event instead)
		this.firePress({/* no parameters */});
	};

	/**
	 * Handle the key up event for SPACE and ENTER.
	 *
	 * @param {jQuery.Event} oEvent - the keyboard event.
	 * @private
	 */
	Image.prototype.onkeyup = function(oEvent) {
		if (oEvent.which === KeyCodes.SPACE || oEvent.which === KeyCodes.ENTER) {
			this.firePress({/* no parameters */});

			// stop the propagation it is handled by the control
			oEvent.stopPropagation();
		}
	};

	/**
	 * Handles the keydown event for SPACE on which we have to prevent the browser scrolling.
	 *
	 * @param {jQuery.Event} oEvent The event object.
	 * @private
	 */
	Image.prototype.onsapspace = function(oEvent) {
		oEvent.preventDefault();
	};

	/**
	* Loads "svg"
	* @private
	*/
	Image.prototype._loadSvg = function() {
		var that = this,
			sSrc = this.getSrc(),
			oSvg;

		if (!this._oSvgCachedData[sSrc]) {
			that._oSvgCachedData[sSrc] = {};
			jQuery.get(this.getSrc(), function(data) {
				oSvg = jQuery(data).find('svg')[0];

				if (oSvg) {
					sap.ui.require(["sap/m/_thirdparty/purify"],
					function(DOMPurify) {
							that._oSvgCachedData[sSrc].oSvgDomRef = DOMPurify.sanitize(oSvg, {RETURN_DOM: true});
							that.invalidate();
					});
				}
			})
			.done(function() {
				that.fireLoad();
			})
			.fail(function() {
				that.fireError();
			});
		}
	};

	Image.prototype._getSvgCachedData = function () {
		var sSrc = this.getSrc();

		if (this._oSvgCachedData[sSrc]
			&& typeof this._oSvgCachedData[sSrc].oSvgDomRef === "object") {
				return this._oSvgCachedData[sSrc].oSvgDomRef;
		}
	};

	/**
	* Checks if href is valid
	* @private
	*/
	Image.prototype._isHrefValid = function (sURL) {
		return URLListValidator.validate(sURL);
	};

	/**
	* Update the source image either on the output DOM element (when in sap.m.ImageMode.Image mode) or on the window.Image object (when in sap.m.ImageMode.Background mode)
	* @private
	*/
	Image.prototype._updateDomSrc = function(sSrc) {
		var $DomNode = this.$(),
			sMode = this.getMode();

		if ($DomNode.length) {
			// the src is updated on the output DOM element when mode is set to Image
			// the src is updated on the temp Image object when mode is set to Background
			if (sMode === ImageMode.Image) {
				this._getDomImg().attr("src", sSrc);
			} else {
				$DomNode.addClass("sapMNoImg");
				jQuery(this._oImage).attr("src", sSrc);
			}
		}
	};

	/**
	 * Returns the img Dom element
	 * @private
	 */
	Image.prototype._getDomImg = function() {
		var $DomNode = this.$();

		return this.getDetailBox() ? $DomNode.children("img") : $DomNode;
	};

	/**
	* When sap.m.ImageMode.Background mode is set, the availability of the source image (including the high density version) is checked via the window.Image object. Because when source
	* image is set via 'background-image' CSS style, browser doesn't fire 'load' or 'error' event anymore. These two events can still be fired when the source uri is set to an instance
	* of window.Image.
	*
	* @private
	*/
	Image.prototype._preLoadImage = function(sSrc) {
		if (this.getMode() !== ImageMode.Background) {
			return;
		}

		var $InternalImage = jQuery(this._oImage);

		if (!this._oImage) {
			this._oImage = new window.Image();
			// register to the 'load' and 'error' events
			$InternalImage = jQuery(this._oImage);
			$InternalImage.on("load", jQuery.proxy(this.onload, this)).on("error", jQuery.proxy(this.onerror, this));
		}

		this._oImage.src = sSrc;
	};

	/**
	 * Test if at least one of the width and height properties is set.
	 *
	 * @private
	 */
	Image.prototype._isWidthOrHeightSet = function() {
		return (this.getWidth() && this.getWidth() !== '') || (this.getHeight() && this.getHeight() !== '');
	};

	/**
	 * This function returns the density aware source based on the deviceDensityRatio value.
	 * The return value is in the format [src]@[densityValue].[extension] if the densityValue not equal 1, otherwise it returns the src property.
	 *
	 * @private
	 */
	Image.prototype._getDensityAwareSrc = function() {
		var sSrc = this.getSrc(),
			bDensityAware = this.getDensityAware(),
			d = bDensityAware ? Image._currentDevicePixelRatio : 1;

		// this property is used for resizing the higher resolution image when image is loaded.
		this._iLoadImageDensity = d;

		return this._generateSrcByDensity(sSrc, d);
	};

	/**
	 * This function returns the density aware version of the Active source base on the deviceDensityRatio value.
	 *
	 * @private
	 */
	Image.prototype._getDensityAwareActiveSrc = function() {
		var sActiveSrc = this.getActiveSrc(),
			bDensityAware = this.getDensityAware(),
			d = bDensityAware ? Image._currentDevicePixelRatio : 1;

		// this property is used for resizing the higher resolution image when image is loaded.
		this._iLoadImageDensity = d;

		return this._generateSrcByDensity(sActiveSrc, d);
	};

	/**
	 * This function generates the density aware version of the src property according to the iDensity provided.
	 * It returns the density aware version of the src property.
	 *
	 * @private
	 */
	Image.prototype._generateSrcByDensity = function(sSrc, iDensity) {
		if (!sSrc) {
			return "";
		}

		// if src is in data uri format, disable the density handling
		if (this._isDataUri(sSrc)) {
			this._iLoadImageDensity = 1;
			return sSrc;
		}

        // if the density equals 1, simply return the src property
		if (iDensity === 1) {
			return sSrc;
		}

		var iLastDotPos = sSrc.lastIndexOf("."),
			iLastSlashPos = sSrc.lastIndexOf("/"),
			sName = sSrc.substring(0, iLastDotPos),
			sExtension = sSrc.substring(iLastDotPos);

		// if there's no extension
		// or there's slash after the last dot, this means that the dot may come from the host name
		if (iLastDotPos === -1 || (iLastSlashPos > iLastDotPos)) {
			return sSrc + "@" + iDensity;
		}

		sName = sName + "@" + iDensity;
		return sName + sExtension;
	};

	Image.prototype._isDataUri = function(src) {
		return src ? src.indexOf("data:") === 0 : false;
	};

	/**
	 * Checks if the given value is valid for the <code>background-size</code>
	 * CSS property
	 *
	 * @param {string} sValue the value to check
	 * @protected
	 * @returns {boolean} the check result
	 */
	Image.prototype._isValidBackgroundSizeValue = function (sValue) {
		// compress whitespace
		sValue = normWS(sValue);

		return isSubSet(sValue.split(" "), ["auto", "cover", "contain", "initial"])
			|| DataType.getType("sap.ui.core.CSSSizeShortHand").isValid(sValue);
	};

	/**
	 * Checks if the given value is valid for the <code>background-position</code>
	 * CSS property
	 *
	 * @param {string} sValue the value to check
	 * @protected
	 * @returns {boolean} the check result
	 */
	Image.prototype._isValidBackgroundPositionValue = function (sValue) {
		// compress whitespace
		sValue = normWS(sValue);

		return isSubSet(sValue.split(" "), ["left", "right", "top", "center", "bottom", "initial"])
			|| DataType.getType("sap.ui.core.CSSSizeShortHand").isValid(sValue);
	};

	/**
	 * Returns the <code>sap.m.Image</code>  accessibility information.
	 *
	 * @see sap.ui.core.Control#getAccessibilityInfo
	 * @protected
	 * @returns {object} The <code>sap.m.Image</code> accessibility information
	 */
	Image.prototype.getAccessibilityInfo = function() {
		var bHasPressListeners = this.hasListeners("press");

		if (this.getDecorative() && !this.getUseMap() && !bHasPressListeners) {
			return null;
		}

		return {
			role: bHasPressListeners ? "button" : "img",
			type: sap.ui.getCore().getLibraryResourceBundle("sap.m").getText(bHasPressListeners ? "ACC_CTR_TYPE_BUTTON" : "ACC_CTR_TYPE_IMAGE"),
			description: this.getAlt() || this.getTooltip_AsString() || "",
			focusable: bHasPressListeners
		};
	};

	/**
	 * @see sap.ui.core.Element.prototype.getFocusDomRef
	 * @private
	 */
	Image.prototype.getFocusDomRef = function() {
		return this.getDomRef("inner") || this.getDomRef();
	};

	/*
	 * Image must not be stretched in Form because should have its original size.
	 */
	Image.prototype.getFormDoNotAdjustWidth = function() {
		return true;
	};

	/**
	 * Utility function that checks if the content of an array
	 * is a subset of the content of a second (reference) array
	 */
	function isSubSet (aTestArray, aRefArray) {
		function isOutsideSet(sTestValue) {
			return aRefArray.indexOf(sTestValue) < 0; // value is not part of the reference set
		}
		return aTestArray && aRefArray && !aTestArray.some(isOutsideSet);
	}

	/**
	 * Utility function to normalize whitespace in a CSS value.
	 * @param {string} sValue String value to normalize
	 * @returns {string} Normalized value
	 */
	function normWS(sValue) {
		var whitespaceRegEx = /\s+/g;
		// compress whitespace
		return sValue == null ? "" : String(sValue).trim().replace(whitespaceRegEx, " ");
	}

	return Image;

});