/*!
 * ${copyright}
 */

sap.ui.define([
	"sap/ui/thirdparty/jquery",
	"sap/ui/core/Control",
	"sap/ui/core/CustomData",
	"sap/ui/core/IconPool",
	"sap/ui/core/HTML",
	"sap/ui/core/Icon",
	"./Button",
	"./Toolbar",
	"./ToolbarSpacer",
	"./List",
	"./MessageListItem",
	"./library",
	"./Text",
	"./SegmentedButton",
	"./Page",
	"./NavContainer",
	"./Link",
	"./MessageItem",
	"./GroupHeaderListItem",
	"sap/ui/core/library",
	"sap/ui/base/ManagedObject",
	"./MessageViewRenderer",
	"sap/ui/events/KeyCodes",
	"sap/base/Log",
	"sap/base/security/URLListValidator",
	"sap/ui/thirdparty/caja-html-sanitizer"
], function(
	jQuery,
	Control,
	CustomData,
	IconPool,
	HTML,
	Icon,
	Button,
	Toolbar,
	ToolbarSpacer,
	List,
	MessageListItem,
	library,
	Text,
	SegmentedButton,
	Page,
	NavContainer,
	Link,
	MessageItem,
	GroupHeaderListItem,
	coreLibrary,
	ManagedObject,
	MessageViewRenderer,
	KeyCodes,
	Log,
	URLListValidator
) {
	"use strict";

	// shortcut for sap.ui.core.ValueState
	var ValueState = coreLibrary.ValueState;

	// shortcut for sap.ui.core.MessageType
	var MessageType = coreLibrary.MessageType;

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

	/**
	 * Constructor for a new MessageView
	 *
	 * @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
	 * It is used to display a summarized list of different types of messages (error, warning, success, and information messages).
	 *
	 * <h3>Overview</h3>
	 * It is meant to be embedded into container controls (such as {@link sap.m.Popover}, {@link sap.m.ResponsivePopover}, {@link sap.m.Dialog}).
	 * It provides a handy and systematized way to navigate and explore details for every message.
	 * If the <code>MessageView</code> contains only one item, which has either description, markupDescription or longTextUrl, its details page will be displayed initially.
	 * It also exposes the {@link sap.m.MessageView#event:activeTitlePress} event, which can be used for navigation from a message to its source.
	 * <h3>Notes:</h3>
	 * <ul>
	 * <li>If your application changes its model between two interactions with the <code>MessageView</code>, this could lead to outdated messages being shown.
	 * To avoid this, you need to call <code>navigateBack</code> on the <code>MessageView</code> BEFORE opening its container.</li>
	 * <li> Messages can have descriptions preformatted with HTML markup. In this case, the <code>markupDescription</code> has to be set to <code>true</code>. </li>
	 * <li> If the message cannot be fully displayed, or includes a long description, the <code>MessageView</code> provides navigation to the detailed description. </li>
	 * </ul>
	 * <h3>Structure</h3>
	 * The <code>MessageView</code> stores all messages in an association of type {@link sap.m.MessageItem}, named <code>items</code>.
	 * <br>
	 * A set of properties determines how the items are rendered:
	 * <ul>
	 * <li> counter - An integer that is used to indicate the number of errors for each type. </li>
	 * <li> type - The type of message. </li>
	 * <li> title/subtitle - The title and subtitle of the message.</li>
	 * <li> description - The long text description of the message.</li>
	 * <li> activeTitle - Determines whether the title of the item is interactive.</li>
	 * </ul>
	 * <h3>Usage</h3>
	 * <h4>When to use:</h4>
	 * <ul>
	 * <li>When you want a way to centrally manage messages and show them to the user without additional work for the developer.
	 * If needed the navigation between the message item and the source of the error can be created by the application.
	 * This can be done by setting the <code>activeTitle</code> property to true and providing a handler for the <code>activeTitlePress</code> event.</li>
	 * </ul>
	 * <h3>Responsive Behavior</h3>
	 * The responsiveness of the <code>MessageView</code> is determined by the container in which it is embedded. For that reason the control could not be visualized if the
	 * container’s sizes are not defined.
	 * @author SAP SE
	 * @version ${version}
	 *
	 * @extends sap.ui.core.Control
	 * @constructor
	 * @public
	 * @since 1.46
	 * @alias sap.m.MessageView
	 * @see {@link fiori:https://experience.sap.com/fiori-design-web/message-view/ Message View}
	 */
	var MessageView = Control.extend("sap.m.MessageView", /** @lends sap.m.MessageView.prototype */ {
		metadata: {
			library: "sap.m",
			properties: {
				/**
				 * Callback function for resolving a promise after description has been asynchronously loaded inside this function.
				 * @callback sap.m.MessageView~asyncDescriptionHandler
				 * @param {object} config A single parameter object
				 * @param {MessagePopoverItem} config.item Reference to respective MessagePopoverItem instance
				 * @param {object} config.promise Object grouping a promise's reject and resolve methods
				 * @param {function} config.promise.resolve Method to resolve promise
				 * @param {function} config.promise.reject Method to reject promise
				 */
				asyncDescriptionHandler: {type: "function", group: "Behavior", defaultValue: null},

				/**
				 * Callback function for resolving a promise after a link has been asynchronously validated inside this function.
				 * @callback sap.m.MessageView~asyncURLHandler
				 * @param {object} config A single parameter object
				 * @param {string} config.url URL to validate
				 * @param {string|int} config.id ID of the validation job
				 * @param {object} config.promise Object grouping a promise's reject and resolve methods
				 * @param {function} config.promise.resolve Method to resolve promise
				 * @param {function} config.promise.reject Method to reject promise
				 */
				asyncURLHandler: {type: "function", group: "Behavior", defaultValue: null},

				/**
				 * Defines whether the MessageItems are grouped or not.
				 */
				groupItems: { type: "boolean", group: "Behavior", defaultValue: false },

				/**
				 * Defines whether the header of details page will be shown.
				 */
				showDetailsPageHeader: { type: "boolean", group: "Behavior", defaultValue: true }
			},
			defaultAggregation: "items",
			aggregations: {
				/**
				 * A list with message items.
				 * If only one item is provided, the initial page will be the details page for the item.
				 */
				items: { type: "sap.m.MessageItem", multiple: true, singularName: "item" },

				/**
				 * Sets a custom header button.
				 */
				headerButton: { type: "sap.m.Button", multiple: false },

				/**
				 * A navContainer that contains both details and list pages.
				 */
				_navContainer: { type: "sap.m.NavContainer", multiple: false, visibility : "hidden" }
			},
			events: {
				/**
				 * Event fired after the popover is opened.
				 * @deprecated As of version 1.72. Use the appropriate event from the wrapper control, instead.
				 */
				afterOpen: {
					parameters: {
						/**
						 * This refers to the control which opens the popover.
						 */
						openBy: {type: "sap.ui.core.Control"}
					},
					deprecated: true
				},
				/**
				 * Event fired when description is shown.
				 */
				itemSelect: {
					parameters: {
						/**
						 * Refers to the message item that is being presented.
						 */
						item: {type: "sap.m.MessageItem"},
						/**
						 * Refers to the type of messages being shown.
						 * See sap.ui.core.MessageType values for types.
						 */
						messageTypeFilter: {type: "sap.ui.core.MessageType"}
					}
				},
				/**
				 * Event fired when one of the lists is shown when (not) filtered  by type.
				 */
				listSelect: {
					parameters: {
						/**
						 * This parameter refers to the type of messages being shown.
						 */
						messageTypeFilter: {type: "sap.ui.core.MessageType"}
					}
				},
				/**
				 * Event fired when the long text description data from a remote URL is loaded.
				 */
				longtextLoaded: {},
				/**
				 * Event fired when a validation of a URL from long text description is ready.
				 */
				urlValidated: {},

				/**
				 * Event fired when an activeTitle of a MessageItem is pressed.
				 * @since 1.58
				 */
				activeTitlePress: {
					parameters: {
						/**
						 * Refers to the message item that contains the activeTitle.
						 */
						item: { type: "sap.m.MessageItem" }
					}
				}
			}
		},

		renderer: MessageViewRenderer
	});

	var CSS_CLASS = "sapMMsgView";

	var ICONS = {
		back: IconPool.getIconURI("nav-back"),
		close: IconPool.getIconURI("decline"),
		information: IconPool.getIconURI("information"),
		warning: IconPool.getIconURI("alert"),
		error: IconPool.getIconURI("error"),
		success: IconPool.getIconURI("sys-enter-2")
	};

	var LIST_TYPES = ["all", "error", "warning", "success", "information"];

	// Property names array
	var ASYNC_HANDLER_NAMES = ["asyncDescriptionHandler", "asyncURLHandler"];

	// Private class variable used for static method below that sets default async handlers
	var DEFAULT_ASYNC_HANDLERS = {
		asyncDescriptionHandler: function (config) {
			var sLongTextUrl = config.item.getLongtextUrl();
			if (sLongTextUrl) {
				jQuery.ajax({
					type: "GET",
					url: sLongTextUrl,
					success: function (data) {
						config.item.setDescription(data);
						config.promise.resolve();
					},
					error: function () {
						var sError = "A request has failed for long text data. URL: " + sLongTextUrl;
						Log.error(sError);
						config.promise.reject(sError);
					}
				});
			}
		}
	};

	/**
	 * Setter for default description and URL validation callbacks across all instances of MessageView
	 * @static
	 * @protected
	 * @param {object} mDefaultHandlers An object setting default callbacks
	 * @param {function} mDefaultHandlers.asyncDescriptionHandler The description handler
	 * @param {function} mDefaultHandlers.asyncURLHandler The URL handler
	 */
	MessageView.setDefaultHandlers = function (mDefaultHandlers) {
		ASYNC_HANDLER_NAMES.forEach(function (sFuncName) {
			if (mDefaultHandlers.hasOwnProperty(sFuncName)) {
				DEFAULT_ASYNC_HANDLERS[sFuncName] = mDefaultHandlers[sFuncName];
			}
		});
	};

	/**
	 * Initializes the control
	 *
	 * @override
	 * @private
	 */
	MessageView.prototype.init = function () {

		var that = this;
		this._bHasHeaderButton = false;

		this._oResourceBundle = sap.ui.getCore().getLibraryResourceBundle("sap.m");

		this._createNavigationPages();
		this._createLists();

		// Check for default async handlers and set them appropriately
		ASYNC_HANDLER_NAMES.forEach(function (sFuncName) {
			if (DEFAULT_ASYNC_HANDLERS.hasOwnProperty(sFuncName)) {
				that.setProperty(sFuncName, DEFAULT_ASYNC_HANDLERS[sFuncName]);
			}
		});
	};

	/**
	 * Handles navigate event of the NavContainer
	 *
	 * @private
	 */
	MessageView.prototype._afterNavigate = function () {
		setTimeout(this["_restoreFocus"].bind(this), 0);
		setTimeout(this["_restoreItemsType"].bind(this), 0);
	};

	/**
	 * Restores the focus after navigation
	 *
	 * @private
	 */
	MessageView.prototype._restoreFocus = function () {
		if (this._isListPage() && this.getItems().length) {
			this._oLists[this._sCurrentList || 'all'].focus();
		} else if (this._oBackButton){
			this._oBackButton.focus();
		}
	};

	/**
	 * Restores the items type after navigation
	 *
	 * @private
	 */
	MessageView.prototype._restoreItemsType = function () {
		if (this._isListPage() && this.getItems().length > 1) {
			var that = this;
			this._oLists[this._sCurrentList || 'all'].getItems().forEach(function (oListItem) {
				if (oListItem.isA("sap.m.MessageListItem")) {
					that._setItemType(oListItem);
				}
			});
		}
	};

	/**
	 * Sets the item type to navigation if the text is too long
	 *
	 * @param {sap.m.MessageListItem} oListItem The list item
	 * @private
	 */
	MessageView.prototype._setItemType = function (oListItem) {
		var sSelector,
			bActiveTitle = oListItem.getActiveTitle();

		if (!oListItem.getTitle() || !oListItem.getDescription()) {
			if (bActiveTitle) {
				sSelector = ".sapMSLITitleOnly a";
			} else {
				sSelector = ".sapMSLITitleOnly";
			}
		} else if (bActiveTitle) {
			sSelector = ".sapMSLITitle a";
		} else {
			sSelector = ".sapMSLITitle";
		}

		var oItemDomRef = oListItem.getDomRef().querySelector(sSelector);

		if (oItemDomRef.offsetWidth < oItemDomRef.scrollWidth) {
			oListItem.setType(ListType.Navigation);
			if (this.getItems().length === 1) {
				this._fnHandleForwardNavigation(oListItem, "show");
			}
		}
	};

	MessageView.prototype.onBeforeRendering = function () {
		var oGroupedItems,
			aListItems,
			aItems = this.getItems();

		this._clearLists();
		this._detailsPage.setShowHeader(this.getShowDetailsPageHeader());

		if (this.getGroupItems()) {
			oGroupedItems = this._groupItems(aItems);

			this._fillGroupedLists(oGroupedItems);
		} else {
			this._fillLists(aItems);
		}

		var headerButton = this.getHeaderButton();

		if (headerButton) {
			this._bHasHeaderButton = true;
			this._oListHeader.insertContent(headerButton, 2);
		}

		this._clearSegmentedButton();
		this._fillSegmentedButton();
		this._fnFilterList(this._getCurrentMessageTypeFilter() || "all");

		aListItems = this._oLists.all.getItems().filter(function (oItem) {
			return oItem.isA("sap.m.MessageListItem");
		});

		if (aListItems.length === 1 && aListItems[0].getType()  === ListType.Navigation) {

			this._fnHandleForwardNavigation(aListItems[0], "show");

			// TODO: adopt this to NavContainer's public API once a parameter for back navigation transition name is available
			this._navContainer._pageStack[this._navContainer._pageStack.length - 1].transition = "slide";
		}

		// Bind automatically to the MessageModel if no items are bound
		this._makeAutomaticBinding();
	};

	/**
	 * Fills grouped items in the lists
	 * @param {sap.m.MessageItem[]} aGroupedItems An array of items
	 * @private
	 */
	MessageView.prototype._fillGroupedLists = function(aGroupedItems) {
		var aGroups = Object.keys(aGroupedItems),
			iUngroupedIndex = aGroups.indexOf(""),
			aUngrouped;

		if (iUngroupedIndex !== -1) {
			aUngrouped = aGroupedItems[""];

			this._fillLists(aUngrouped);

			delete aGroupedItems[""];
			aGroups.splice(iUngroupedIndex, 1);
		}

		aGroups.forEach(function(sGroupName) {
			this._fillListsWithGroups(sGroupName, aGroupedItems[sGroupName]);
		}, this);
	};

	MessageView.prototype._fillListsWithGroups = function(sGroupName, aItems) {
		var oHeader = new GroupHeaderListItem({
			title: sGroupName
		});

		this._oLists["all"].addAggregation("items", oHeader, true);

		["error", "warning", "success", "information"].forEach(function (sListType) {
			if (this._hasGroupItemsOfType(aItems, sListType)) {
				this._oLists[sListType].addAggregation("items", oHeader.clone(), true);
			}
		}, this);

		this._fillLists(aItems);
	};

	MessageView.prototype._hasGroupItemsOfType = function (aItems, sListType) {
		return aItems.some(function (oItem) {
			return oItem.getType().toLowerCase() === sListType;
		});
	};

	/**
	 * Called when the control is destroyed
	 *
	 * @private
	 */
	MessageView.prototype.exit = function () {
		if (this._oLists) {
			this._destroyLists();
		}

		if (this._oMessageItemTemplate) {
			this._oMessageItemTemplate.destroy();
		}

		this._oResourceBundle = null;
		this._oListHeader = null;
		this._oDetailsHeader = null;
		this._oSegmentedButton = null;
		this._oBackButton = null;
		this._navContainer = null;
		this._listPage = null;
		this._detailsPage = null;
		this._sCurrentList = null;
	};

	/**
	 * If there's no items binding, attach the MessageView to the sap.ui.getCore().getMessageManager().getMessageModel()
	 *
	 * @private
	 * @ui5-restricted sap.m.MessagePopover
	 */
	MessageView.prototype._makeAutomaticBinding = function () {
		var aItems = this.getItems();

		if (!this.getBindingInfo("items") && !aItems.length) {
			this._bindToMessageModel();
		}
	};

	/**
	 * Makes automatic binding to the Message Model with default template
	 *
	 * @private
	 */
	MessageView.prototype._bindToMessageModel = function () {
		var that = this;

		this.setModel(sap.ui.getCore().getMessageManager().getMessageModel(), "message");

		this._oMessageItemTemplate = new MessageItem({
			type: "{message>type}",
			title: "{message>message}",
			description: "{message>description}",
			longtextUrl: "{message>longtextUrl}"
		});

		this.bindAggregation("items",
			{
				path: "message>/",
				template: that._oMessageItemTemplate
			}
		);
	};

	/**
	 * Groups items in an object of keys and correspoding array of items
	 * @param {sap.m.MessageItem[]} aItems An array of items
	 * @returns {object} Item object
	 * @private
	 */
	MessageView.prototype._groupItems = function (aItems) {
		var oGroups = {}, sItemGroup;

		aItems.forEach(function(oItem) {
			sItemGroup = oItem.getGroupName();
			oGroups[sItemGroup] = oGroups[sItemGroup] || [];

			oGroups[sItemGroup].push(oItem);
		});

		return oGroups;
	};

	/**
	 * Handles keyup event
	 *
	 * @param {jQuery.Event} oEvent - keyup event object
	 * @private
	 */
	MessageView.prototype._onkeypress = function (oEvent) {
		if (oEvent.shiftKey && oEvent.keyCode == KeyCodes.ENTER) {
			this.navigateBack();
		}
	};

	/**
	 * Returns header of the MessageView's ListPage
	 *
	 * @returns {sap.m.Toolbar} ListPage header
	 * @private
	 */
	MessageView.prototype._getListHeader = function () {
		return this._oListHeader || this._createListHeader();
	};

	/**
	 * Returns header of the MessageView's ListPage
	 *
	 * @returns {sap.m.Toolbar} DetailsPage header
	 * @private
	 */
	MessageView.prototype._getDetailsHeader = function () {
		return this._oDetailsHeader || this._createDetailsHeader();
	};

	/**
	 * Creates header of MessageView's ListPage
	 *
	 * @returns {sap.m.Toolbar} ListPage header
	 * @private
	 */
	MessageView.prototype._createListHeader = function () {
		var sCloseBtnDescr = this._oResourceBundle.getText("MESSAGEPOPOVER_CLOSE");
		var sCloseBtnDescrId = this.getId() + "-CloseBtnDescr";
		var oCloseBtnARIAHiddenDescr = new HTML(sCloseBtnDescrId, {
			content: "<span id=\"" + sCloseBtnDescrId + "\" class=\"sapMMsgViewHiddenContainer\">" + sCloseBtnDescr + "</span>"
		});

		var sHeadingDescr = this._oResourceBundle.getText("MESSAGEPOPOVER_ARIA_HEADING");
		var sHeadingDescrId = this.getId() + "-HeadingDescr";
		var oHeadingARIAHiddenDescr = new HTML(sHeadingDescrId, {
			content: "<span id=\"" + sHeadingDescrId + "\" class=\"sapMMsgViewHiddenContainer\" role=\"heading\">" + sHeadingDescr + "</span>"
		});

		this._oSegmentedButton = new SegmentedButton(this.getId() + "-segmented", {}).addStyleClass("sapMSegmentedButtonNoAutoWidth");

		this._oListHeader = new Toolbar({
			content: [this._oSegmentedButton, new ToolbarSpacer(), oCloseBtnARIAHiddenDescr, oHeadingARIAHiddenDescr]
		});

		return this._oListHeader;
	};

	/**
	 * Creates header of MessageView's ListPage
	 *
	 * @returns {sap.m.Toolbar} DetailsPage header
	 * @private
	 */
	MessageView.prototype._createDetailsHeader = function () {
		var sCloseBtnDescr = this._oResourceBundle.getText("MESSAGEPOPOVER_CLOSE");
		var sCloseBtnDescrId = this.getId() + "-CloseBtnDetDescr";
		var oCloseBtnARIAHiddenDescr = new HTML(sCloseBtnDescrId, {
			content: "<span id=\"" + sCloseBtnDescrId + "\" class=\"sapMMsgViewHiddenContainer\">" + sCloseBtnDescr + "</span>"
		});

		var sBackBtnTooltipDescr = this._oResourceBundle.getText("MESSAGEPOPOVER_ARIA_BACK_BUTTON_TOOLTIP");
		var sBackBtnDescr = this._oResourceBundle.getText("MESSAGEPOPOVER_ARIA_BACK_BUTTON");
		var sBackBtnDescrId = this.getId() + "-BackBtnDetDescr";
		var oBackBtnARIAHiddenDescr = new HTML(sBackBtnDescrId, {
			content: "<span id=\"" + sBackBtnDescrId + "\" class=\"sapMMsgViewHiddenContainer\">" + sBackBtnDescr + "</span>"
		});

		this._oBackButton = new Button({
			icon: ICONS["back"],
			press: this.navigateBack.bind(this),
			ariaLabelledBy: oBackBtnARIAHiddenDescr,
			tooltip: sBackBtnTooltipDescr
		}).addStyleClass(CSS_CLASS + "BackBtn");

		this._oDetailsHeader = new Toolbar({
			content: [this._oBackButton, new ToolbarSpacer(), oCloseBtnARIAHiddenDescr, oBackBtnARIAHiddenDescr]
		});

		return this._oDetailsHeader;
	};

	/**
	 * Creates navigation pages
	 *
	 * @returns {this} Reference to the 'this' for chaining purposes
	 * @private
	 */
	MessageView.prototype._createNavigationPages = function () {
		// Create two main pages
		this._listPage = new Page(this.getId() + "listPage", {
			customHeader: this._getListHeader()
		});

		this._detailsPage = new Page(this.getId() + "-detailsPage", {
			customHeader: this._getDetailsHeader()
		}).addStyleClass("sapMMsgViewDetailsPage");

		// TODO: check if this is the best location for this
		// Disable clicks on disabled and/or pending links
		this._detailsPage.addEventDelegate({
			onclick: function (oEvent) {
				var target = oEvent.target;

				if (target.nodeName.toUpperCase() === "A" &&
					(target.className.indexOf("sapMMsgViewItemDisabledLink") !== -1 ||
					target.className.indexOf("sapMMsgViewItemPendingLink") !== -1)) {

					oEvent.preventDefault();
				}
			}
		});

		// Initialize nav container with two main pages
		this._navContainer = new NavContainer(this.getId() + "-navContainer", {
			initialPage: this.getId() + "listPage",
			pages: [this._listPage, this._detailsPage],
			afterNavigate: this._afterNavigate.bind(this)
		});

		this.setAggregation("_navContainer", this._navContainer);

		return this;
	};

	/**
	 * Creates Lists of the MessageView
	 *
	 * @returns {this} Reference to the 'this' for chaining purposes
	 * @private
	 */
	MessageView.prototype._createLists = function () {
		this._oLists = {};

		LIST_TYPES.forEach(function (sListName) {
			this._oLists[sListName] = new List({
				itemPress: this._fnHandleItemPress.bind(this),
				visible: false
			});

			// no re-rendering
			this._listPage.addAggregation("content", this._oLists[sListName], true);
		}, this);

		return this;
	};

	/**
	 * Destroy items in the MessageView's Lists
	 *
	 * @returns {this} Reference to the 'this' for chaining purposes
	 * @private
	 */
	MessageView.prototype._clearLists = function () {
		LIST_TYPES.forEach(function (sListName) {
			if (this._oLists[sListName]) {
				this._oLists[sListName].destroyAggregation("items", true);
			}
		}, this);

		return this;
	};

	/**
	 * Destroys internal Lists of the MessageView
	 *
	 * @private
	 */
	MessageView.prototype._destroyLists = function () {
		LIST_TYPES.forEach(function (sListName) {
			this._oLists[sListName] = null;
		}, this);

		this._oLists = null;
	};

	/**
	 * Fill the list with items
	 *
	 * @param {array} aItems An array with items type of sap.ui.core.Item.
	 * @private
	 */
	MessageView.prototype._fillLists = function (aItems) {
		aItems.forEach(function (oMessageItem) {
			var oListItem = this._mapItemToListItem(oMessageItem),
				oCloneListItem = this._mapItemToListItem(oMessageItem);

			// add the mapped item to the List
			this._oLists["all"].addAggregation("items", oListItem, true);
			this._oLists[oMessageItem.getType().toLowerCase()].addAggregation("items", oCloneListItem, true);
		}, this);
	};

	/**
	 * Map a MessageItem to MessageListItem
	 *
	 * @param {sap.m.MessageItem} oMessageItem Base information to generate the list items
	 * @returns {sap.m.MessageListItem | null} oListItem List item which will be displayed
	 * @private
	 */
	MessageView.prototype._mapItemToListItem = function (oMessageItem) {
		if (!oMessageItem) {
			return null;
		}

		var sType = oMessageItem.getType(),
			that = this,
			listItemType = this._getItemType(oMessageItem),
			oListItem = new MessageListItem({
				title: ManagedObject.escapeSettingsValue(oMessageItem.getTitle()),
				description: ManagedObject.escapeSettingsValue(oMessageItem.getSubtitle()),
				counter: oMessageItem.getCounter(),
				icon: this._mapIcon(sType),
				infoState: this._mapInfoState(sType),
				info: "\r", // There should be a content in the info property in order to use the info states
				type: listItemType,
				messageType: oMessageItem.getType(),
				activeTitle: oMessageItem.getActiveTitle(),
				activeTitlePress: function () {
					that.fireActiveTitlePress({ item: oMessageItem });
				}
			}).addStyleClass(CSS_CLASS + "Item")
				.addStyleClass(CSS_CLASS + "Item" + sType)
				.toggleStyleClass(CSS_CLASS + "ItemActive", oMessageItem.getActiveTitle());

		if (listItemType !== ListType.Navigation) {
			oListItem.addEventDelegate({
				onAfterRendering: function () {
					that._setItemType(oListItem);
				}
			}, this);
		}

		oListItem._oMessageItem = oMessageItem;

		return oListItem;
	};

	/**
	 * Map ValueState according the MessageType of the message.
	 *
	 * @param {sap.ui.core.MessageType} sType Type of Message
	 * @returns {sap.ui.core.ValueState | null} The ValueState
	 * @private
	 */
	MessageView.prototype._mapInfoState = function (sType) {
		if (!sType) {
			return null;
		}

		switch (sType) {
			case MessageType.Warning:
				return ValueState.Warning;
			case MessageType.Error:
				return ValueState.Error;
			case MessageType.Success:
				return ValueState.Success;
			case MessageType.Information:
			case MessageType.None:
				return ValueState.None;
			default:
				Log.warning("The provided MessageType is not mapped to a specific ValueState", sType);
				return null;
		}
	};

	/**
	 * Map a MessageType to the Icon URL.
	 *
	 * @param {sap.ui.core.ValueState} sIcon Type of Error
	 * @returns {string | null} Icon string
	 * @private
	 */
	MessageView.prototype._mapIcon = function (sIcon) {
		if (!sIcon) {
			return null;
		}

		return ICONS[sIcon.toLowerCase()];
	};

	MessageView.prototype._getItemType = function (oMessageItem) {
		return (oMessageItem.getDescription() || oMessageItem.getMarkupDescription() || oMessageItem.getLongtextUrl()) ?
			ListType.Navigation : ListType.Inactive;
	};

	/**
	 * Destroy the buttons in the SegmentedButton
	 *
	 * @returns {this} Reference to the 'this' for chaining purposes
	 * @private
	 */
	MessageView.prototype._clearSegmentedButton = function () {
		if (this._oSegmentedButton) {
			this._oSegmentedButton.destroyAggregation("buttons", true);
		}

		return this;
	};

	/**
	 * Fill SegmentedButton with needed Buttons for filtering
	 *
	 * @returns {this} Reference to the 'this' for chaining purposes
	 * @private
	 */
	MessageView.prototype._fillSegmentedButton = function () {
		var that = this;
		var pressClosure = function (sListName) {
			return function () {
				that._fnFilterList(sListName);
			};
		};

		LIST_TYPES.forEach(function (sListName) {
			var oList = this._oLists[sListName],
				sBundleText = sListName == "all" ? "MESSAGEPOPOVER_ALL" : "MESSAGEVIEW_BUTTON_TOOLTIP_" + sListName.toUpperCase(),
				iCount = oList.getItems().filter(function(oItem) {
					return (oItem instanceof MessageListItem);
				}).length, oButton;

			if (iCount > 0) {
				oButton = new Button(this.getId() + "-" + sListName, {
					text: sListName == "all" ? this._oResourceBundle.getText(sBundleText) : iCount,
					tooltip: this._oResourceBundle.getText(sBundleText),
					icon: ICONS[sListName],
					press: pressClosure(sListName)
				}).addStyleClass(CSS_CLASS + "Btn" + sListName.charAt(0).toUpperCase() + sListName.slice(1));

				this._oSegmentedButton.addButton(oButton, true);
			}
		}, this);

		// If there is only the always-present 'all' button and a single group button
		// no need for a segmented button
		var bSegmentedButtonVisible = this._oSegmentedButton.getButtons().length > 2;
		this._oSegmentedButton.setVisible(bSegmentedButtonVisible);
		// If there's only one group reset filter and highlight the "All" button from the SegmentedButton list.
		// Otherwise if the user has filtered and the model changes, he could be stuck to a "no data" page without a way
		// to navigate back and see the remaining messages
		if (!bSegmentedButtonVisible) {
			this._oSegmentedButton.setSelectedButton(this._oSegmentedButton.getButtons()[0]);
			this._fnFilterList('all');
		}

		// If SegmentedButton should not be visible,
		// and there is no custom button - hide the initial page's header
		var bListPageHeaderVisible = bSegmentedButtonVisible || this._bHasHeaderButton;
		this._listPage.setShowHeader(bListPageHeaderVisible);


		return this;
	};

	/**
	 * Sets icon in details page
	 * @param {sap.m.MessageItem} oMessageItem The message item
	 * @param {sap.m.MessageListItem} oListItem The list item
	 * @private
	 */
	MessageView.prototype._setIcon = function (oMessageItem, oListItem) {
		this._previousIconTypeClass = CSS_CLASS + "DescIcon" + oMessageItem.getType();
		this._oMessageIcon = new Icon({
			src: oListItem.getIcon()
		})
			.addStyleClass(CSS_CLASS + "DescIcon")
			.addStyleClass(this._previousIconTypeClass);

		this._detailsPage.addContent(this._oMessageIcon);
	};

	/**
	 * Sets title part of details page
	 * @param {sap.m.MessageItem} oMessageItem The message item
	 * @private
	 */
	MessageView.prototype._setTitle = function (oMessageItem, oListItem) {
		var bActive = oMessageItem.getActiveTitle(),
			oDetailsContent, that = this,
			sText = ManagedObject.escapeSettingsValue(oMessageItem.getTitle()),
			sId = this.getId() + "MessageTitleText";

		if (bActive) {
			oDetailsContent = new Link(sId, {
				text: sText,
				ariaDescribedBy: oListItem.getId() + "-link",
				press: function () {
					that.fireActiveTitlePress({ item: oMessageItem });
				}
			});
		} else {
			oDetailsContent = new Text(sId, {
				text: sText
			});
		}

		oDetailsContent.addStyleClass("sapMMsgViewTitleText");
		this._detailsPage.addAggregation("content", oDetailsContent);
	};

	/**
	 * Sets description text part of details page
	 * When markup description is used it is sanitized within it's container's setter method (MessageItem)
	 * @param {sap.m.MessageItem} oMessageItem The message item
	 * @private
	 */
	MessageView.prototype._setDescription = function (oMessageItem) {
		var oLink = oMessageItem.getLink();
		var sDescription = oMessageItem.getDescription();

		if (oMessageItem.getMarkupDescription()) {
			var tagPolicy = this._getTagPolicy();
			/*global html*/
			sDescription = html.sanitizeWithPolicy(sDescription, tagPolicy);
		}

		this._oLastSelectedItem = oMessageItem;
		if (oMessageItem.getMarkupDescription()) {
			this._oMessageDescriptionText = new HTML(this.getId() + "MarkupDescription", {
				content: "<div class='sapMMsgViewDescriptionText'>" + ManagedObject.escapeSettingsValue(sDescription) + "</div>"
			});
		} else {
			this._oMessageDescriptionText = new Text(this.getId() + "MessageDescriptionText", {
				text: ManagedObject.escapeSettingsValue(sDescription)
			}).addStyleClass("sapMMsgViewDescriptionText");
		}

		this._detailsPage.addContent(this._oMessageDescriptionText);

		if (oLink) {
			var oLinkClone = this._createLinkCopy(oLink);
			this._detailsPage.addContent(oLinkClone);
			oLinkClone.addStyleClass("sapMMsgViewDescriptionLink");
		}
	};

	MessageView.prototype._createLinkCopy = function (oLink) {
		var aLinkProperties,
			oLinkClone = oLink.clone("", "", {
				cloneChildren: false,
				cloneBindings: false
			}),
			aCustomData = oLink.getCustomData() || [];

		aLinkProperties = Object.keys(oLink.getMetadata().getProperties());
		aLinkProperties.forEach(function(sProp){
			oLinkClone.setProperty(sProp, oLink.getProperty(sProp));
		});

		oLinkClone.destroyCustomData();
		aCustomData.forEach(function(oCustomData){
			var oCustomDataCopy = new CustomData({
				key: oCustomData.getKey(),
				value: oCustomData.getValue()
			});

			oLinkClone.addCustomData(oCustomDataCopy);
		});

		return oLinkClone;
	};

	MessageView.prototype._iNextValidationTaskId = 0;

	MessageView.prototype._validateURL = function (sUrl) {
		if (URLListValidator.validate(sUrl)) {
			return sUrl;
		}

		Log.warning("You have entered invalid URL");

		return "";
	};

	MessageView.prototype._queueValidation = function (href) {
		var fnAsyncURLHandler = this.getAsyncURLHandler();
		var iValidationTaskId = ++this._iNextValidationTaskId;
		var oPromiseArgument = {};

		var oPromise = new Promise(function (resolve, reject) {

			oPromiseArgument.resolve = resolve;
			oPromiseArgument.reject = reject;

			var config = {
				url: href,
				id: iValidationTaskId,
				promise: oPromiseArgument
			};

			fnAsyncURLHandler(config);
		});

		oPromise.id = iValidationTaskId;

		return oPromise;
	};

	MessageView.prototype._getTagPolicy = function () {
		var that = this,
			i;

		/*global html*/
		var defaultTagPolicy = html.makeTagPolicy(this._validateURL());

		return function customTagPolicy(tagName, attrs) {
			var href,
				validateLink = false;

			if (tagName.toUpperCase() === "A") {

				for (i = 0; i < attrs.length;) {
					// if there is href the link should be validated, href's value is on position(i+1)
					if (attrs[i] === "href") {
						validateLink = true;
						href = attrs[i + 1];
						attrs.splice(0, 2);
						continue;
					}

					i += 2;
				}

			}

			// let the default sanitizer do its work
			// it won't see the href attribute
			attrs = defaultTagPolicy(tagName, attrs);

			// if we detected a link before, we modify the <A> tag
			// and keep the link in a dataset attribute
			if (validateLink && typeof that.getAsyncURLHandler() === "function") {

				attrs = attrs || [];

				// first check if there is a class attribute and enrich it with 'sapMMsgViewItemDisabledLink'
				// else, add proper class
				var sClasses = "sapMMsgViewItemDisabledLink sapMMsgViewItemPendingLink";
				var indexOfClass = attrs.indexOf("class");
				if (indexOfClass > -1) {
					attrs[indexOfClass + 1] += sClasses;
				} else {
					attrs.unshift(sClasses);
					attrs.unshift("class");
				}

				// check for existing id
				var indexOfId = attrs.indexOf("id");
				if (indexOfId > -1) {
					// we start backwards
					attrs.splice(indexOfId + 1, 1);
					attrs.splice(indexOfId, 1);
				}

				var oValidation = that._queueValidation(href);

				// add other attributes
				attrs.push("href");
				// the link is deactivated via class names later read by event delegate on the description page
				attrs.push(href);

				// let the page open in another window, so state is preserved
				attrs.push("target");
				attrs.push("_blank");

				// use id here as data attributes are not passing through caja
				attrs.push("id");
				attrs.push("sap-ui-" + that.getId() + "-link-under-validation-" + oValidation.id);

				oValidation
					.then(function (result) {
						// Update link in output
						var $link = jQuery(document.getElementById("sap-ui-" + that.getId() + "-link-under-validation-" + result.id));

						if (result.allowed) {
							Log.info("Allow link " + href);
						} else {
							Log.info("Disallow link " + href);
						}

						// Adapt the link style
						$link.removeClass("sapMMsgViewItemPendingLink");
						$link.toggleClass("sapMMsgViewItemDisabledLink", !result.allowed);

						that.fireUrlValidated();
					})
					.catch(function () {
						Log.warning("Async URL validation could not be performed.");
					});
			}

			return attrs;
		};
	};

	/**
	 * Handles click on a list item
	 *
	 * @param {sap.m.MessageListItem} oListItem ListItem that is pressed
	 * @param {string} sTransiotionName name of transition could be slide, show, flip or fade
	 * @private
	 */
	MessageView.prototype._fnHandleForwardNavigation = function (oListItem, sTransiotionName) {
		var oMessageItem = oListItem._oMessageItem,
			aDetailsPageContent = this._detailsPage.getContent() || [],
			asyncDescHandler = this.getAsyncDescriptionHandler();

		this._previousIconTypeClass = this._previousIconTypeClass || "";

		this.fireItemSelect({
			item: oMessageItem,
			messageTypeFilter: this._getCurrentMessageTypeFilter()
		});

		this._clearDetailsPage.call(this, aDetailsPageContent);

		if (typeof asyncDescHandler === "function" && oMessageItem.getLongtextUrl()) {
			// Set markupDescription to true as markup description should be processed as markup
			oMessageItem.setMarkupDescription(true);

			var oPromiseArgument = {};

			var oPromise = new Promise(function (resolve, reject) {
				oPromiseArgument.resolve = resolve;
				oPromiseArgument.reject = reject;
			});

			var proceed = function () {
				this._detailsPage.setBusy(false);
				this._navigateToDetails.call(this, oMessageItem, oListItem, sTransiotionName, true);
			}.bind(this);

			oPromise
				.then(proceed)
				.catch(function () {
					Log.warning("Async description loading could not be performed.");
					proceed();
				});

			this._navContainer.to(this._detailsPage);

			this._detailsPage.setBusy(true);

			asyncDescHandler({
				promise: oPromiseArgument,
				item: oMessageItem
			});
		} else {
			this._navigateToDetails.call(this, oMessageItem, oListItem, sTransiotionName, false);
		}

		this._listPage.$().attr("aria-hidden", "true");
	};

	/**
	 * Handles click of the ListItems
	 *
	 * @param {jQuery.Event} oEvent ListItem click event object
	 * @private
	 */
	MessageView.prototype._fnHandleItemPress = function (oEvent) {
		this._fnHandleForwardNavigation(oEvent.getParameter("listItem"), "slide");
	};

	MessageView.prototype._navigateToDetails = function(oMessageItem, oListItem, sTransiotionName, bSuppressNavigate) {
		this._setTitle(oMessageItem, oListItem);
		this._setDescription(oMessageItem);
		this._setIcon(oMessageItem, oListItem);
		this._detailsPage.invalidate();
		this.fireLongtextLoaded();

		if (!bSuppressNavigate) {
			this._navContainer.to(this._detailsPage, sTransiotionName);
		}
	};

	/**
	 * Destroys the content of details page
	 * @param {sap.ui.core.Control} aDetailsPageContent The details page content
	 * @private
	 */
	MessageView.prototype._clearDetailsPage = function (aDetailsPageContent) {
		aDetailsPageContent.forEach(function (oControl) {
			oControl.destroy();
		}, this);
	};

	/**
	 * Navigates back to the list page
	 *
	 * @public
	 */
	MessageView.prototype.navigateBack = function () {
		this._listPage.$().removeAttr("aria-hidden");
		this._navContainer.back();
	};

	/**
	 * Handles click of the SegmentedButton
	 *
	 * @param {string} sCurrentListName ListName to be shown
	 * @private
	 */
	MessageView.prototype._fnFilterList = function (sCurrentListName) {
		LIST_TYPES.forEach(function (sListIterName) {
			if (sListIterName != sCurrentListName && this._oLists[sListIterName].getVisible()) {
				// Hide Lists if they are visible and their name is not the same as current list name
				this._oLists[sListIterName].setVisible(false);
			}
		}, this);

		this._sCurrentList = sCurrentListName;
		this._oLists[sCurrentListName].setVisible(true);

		this.fireListSelect({messageTypeFilter: this._getCurrentMessageTypeFilter()});
	};

	/**
	 * Returns current selected List name
	 *
	 * @returns {string} Current list name
	 * @private
	 */
	MessageView.prototype._getCurrentMessageTypeFilter = function () {
		return this._sCurrentList == "all" ? "" : this._sCurrentList;
	};

	/**
	 * Checks whether the current page is ListPage
	 *
	 * @returns {boolean} Whether the current page is ListPage
	 * @private
	 */
	MessageView.prototype._isListPage = function () {
		return this._navContainer.getCurrentPage() == this._listPage;
	};

	return MessageView;

});