/**
 * EGroupware clientside API object
 *
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
 * @package etemplate
 * @subpackage api
 * @link http://www.egroupware.org
 * @author Ralf Becker <RalfBecker@outdoor-training.de>
 * @version $Id$
 */

/*egw:uses
	egw_core;
*/

/**
 * Methods to display a success or error message and the app-header
 *
 * @augments Class
 * @param {string} _app application name object is instanciated for
 * @param {object} _wnd window object is instanciated for
 */
egw.extend('message', egw.MODULE_WND_LOCAL, function(_app, _wnd)
{
	"use strict";

	_app;	// not used, but required by function signature
	var error_reg_exp;
	var a_href_reg = /<a href="([^"]+)">([^<]+)<\/a>/img;
	var new_line_reg = /<\/?(p|br)\s*\/?>\n?/ig;
	// keeps alive messages stored
	var alive_messages = [];
	// Register an 'error' plugin, displaying using the message system
	this.registerJSONPlugin(function(type, res, req) {
		if (typeof res.data == 'string')
		{
			egw.message(res.data,'error');
			return true;
		}
		throw 'Invalid parameters';
	}, null, 'error');

	/**
	 * Decode html entities so they can be added via .text(_str), eg. html_entity_decode('&amp;') === '&'
	 *
	 * @param {string} _str
	 * @returns {string}
	 */
	function html_entity_decode(_str)
	{
		return _str && _str.indexOf('&') != -1 ? jQuery('<span>'+_str+'</span>').text() : _str;
	}

	return {
		/**
		 * Display an error or regular message
		 *
		 * Alle messages but type "success" are displayed 'til next message or user clicks on it.
		 *
		 * @param {string} _msg message to show or empty to remove previous message
		 * @param {string} _type 'help', 'info', 'error', 'warning' or 'success' (default)
		 * @param {string} _discardID unique string id (appname:id) in order to register
		 * the message as discardable. If no appname given, the id will be prefixed with
		 * current app. The discardID will be stored in local storage.
		 *
		 * @return {object} returns an object containing data and methods related to the message
		 */
		message: function(_msg, _type, _discardID)
		{
			var jQuery = _wnd.jQuery;
			var wrapper = jQuery('.egw_message_wrapper').length > 0 ? jQuery('.egw_message_wrapper')
				: jQuery(_wnd.document.createElement('div')).addClass('egw_message_wrapper noPrint').css('position', 'absolute');
			if (_msg && !_type)
			{
				if (typeof error_reg_exp == 'undefined') error_reg_exp = new RegExp('(error|'+egw.lang('error')+')', 'i');

				_type = _msg.match(error_reg_exp) ? 'error' : 'success';
			}

			// if we are NOT in a popup then call the message on top window
			if (!this.is_popup() && _wnd !== egw.top)
			{
				egw(egw.top).message(_msg, _type);
				return;
			}

			var parent = jQuery('div#divAppboxHeader');
			// popup has no app-header (idots) or it is hidden by onlyPrint class (jdots) --> use body
			if (!parent.length || parent.hasClass('onlyPrint'))
			{
				parent = jQuery('body');
			}

			for (var m in alive_messages)
			{
				// Do not add a same message twice if it's still not dismissed
				if (alive_messages[m] == _msg) return;
			}

			if (_msg)	// empty _msg just removes pervious message
			{
				// keeps alive messages
				alive_messages.push(_msg);
				// message index in stack
				var msg_index = alive_messages.length-1;

				// replace p and br-tags with newlines
				_msg = _msg.replace(new_line_reg, "\n");
				var msg_div = jQuery(_wnd.document.createElement('div'))
					.attr('id','egw_message')
					.text(_msg)
					.addClass(_type+'_message')
					.click(function(){
						if (_type == 'success')
						{
							delete(alive_messages[msg_index]);
							jQuery(msg_div).remove();
						}
					})
					.prependTo(wrapper);
				var msg_close = jQuery(_wnd.document.createElement('span'))
					.click(function() {
						//check if the messeage should be discarded forever
						if (_type == 'info' && _discardID
							&& msg_chkbox && msg_chkbox.is(':checked'))
						{
							var discarded = egw.getLocalStorageItem(discardAppName,'discardedMsgs');

							if (!isDiscarded(_discardID))
							{
								if (!discarded)
								{
									discarded = [_discardID];
								}
								else
								{
									if (jQuery.isArray(discarded = JSON.parse(discarded))) discarded.push(_discardID);
								}
								egw.setLocalStorageItem(discardAppName,'discardedMsgs',JSON.stringify(discarded));
							}
						}
						delete(alive_messages[msg_index]);
						jQuery(msg_div).remove();
					})
					.addClass('close')
					.appendTo(msg_div);
				if (_type == 'success')	msg_close.hide();
				// discard checkbox implementation
				if (_discardID && _type === 'info')
				{
					var discardID = _discardID.split(':');
					if (discardID.length<2)
					{
						_discardID = egw.app_name() +":"+_discardID;
					}
					var discardAppName = discardID.length>1? discardID[0]: egw.app_name();


					// helper function to check if the messaege is discarded
					var isDiscarded = function (_id)
					{

						var discarded = JSON.parse(egw.getLocalStorageItem(discardAppName,'discardedMsgs'));

						if (jQuery.isArray(discarded))
						{
							for(var i=0; i< discarded.length; i++)
							{
								if (discarded[i] === _id) return true;
							}
						}
						return false;
					};

					//discard div container
					var msg_discard =jQuery(_wnd.document.createElement('div')).addClass('discard');

					// checkbox
					var msg_chkbox = jQuery(_wnd.document.createElement('input'))
							.attr({type:"checkbox",name:"msgChkbox"})
							.click(function(e){e.stopImmediatePropagation();})
							.appendTo(msg_discard);
					// Label
					jQuery(_wnd.document.createElement('label'))
								.text(egw.lang("Don't show this again"))
								.css({"font-weight":"bold"})
								.attr({for:'msgChkbox'})
								.appendTo(msg_discard);

					if (isDiscarded(_discardID)) return;
					msg_div.append(msg_discard);
				}

				parent.prepend(wrapper);

				// replace simple a href (NO other attribute, to gard agains XSS!)
				var matches = a_href_reg.exec(_msg);
				if (matches)
				{
					var parts = _msg.split(matches[0]);
					var href = html_entity_decode(matches[1]);
					msg_div.text(parts[0]);
					msg_div.append(jQuery(_wnd.document.createElement('a'))
						.attr({href: href, target: href.indexOf(egw.webserverUrl) != 0 ? '_blank' : '_self'})
						.text(matches[2]));
					msg_div.append(jQuery(_wnd.document.createElement('span')).text(parts[1]));
				}
				// center the message
				wrapper.css('right', ((jQuery(_wnd).innerWidth()-msg_div.width())/2)+'px');

				if (_type == 'success')	// clear message again after some time, if no error
				{
					_wnd.setTimeout(function() {
						msg_div.remove();
						delete(alive_messages[msg_index]);
					}, 5000);
				}
			}
			return {
				node: msg_div,
				message: _msg,
				index: msg_index,
				close: function(){msg_close.click();}
			};
		},

		/**
		 * Are we running in a popup
		 *
		 * @returns {boolean} true: popup, false: main window
		 */
		is_popup: function ()
		{
			var popup = false;
			try {
				if (_wnd.opener && _wnd.opener != _wnd && typeof _wnd.opener.top.egw == 'function')
				{
					popup = true;
				}
			}
			catch(e) {
				// ignore SecurityError exception if opener is different security context / cross-origin
			}
			return popup;
		},

		/**
		 * Active app independent if we are using a framed template-set or not
		 *
		 * @returns {string}
		 */
		app_name: function()
		{
			return !this.is_popup() && _wnd.framework && _wnd.framework.activeApp ? _wnd.framework.activeApp.appName : _wnd.egw_appName;
		},

		/**
		 * Update app-header and website-title
		 *
		 * @param {string} _header
		 * @param {string} _app Application name, if not for the current app
		 */
		app_header: function(_header,_app)
		{
			// not for popups and only for framed templates
			if (!this.is_popup() && _wnd.framework && _wnd.framework.setWebsiteTitle)
			{
				var app = _app || this.app_name();
				var title = _wnd.document.title.replace(/[.*]$/, '['+_header+']');

				_wnd.framework.setWebsiteTitle.call(_wnd.framework, app, title, _header);
				return;
			}
			_wnd.jQuery('div#divAppboxHeader').text(_header);

			_wnd.document.title = _wnd.document.title.replace(/[.*]$/, '['+_header+']');
		},

		/**
		 * Loading prompt is for building a loading animation and show it to user
		 * while a request is under progress.
		 *
		 * @param {string} _id a unique id to be able to distinguish loading-prompts
		 * @param {boolean} _stat true to show the loading and false to remove it
		 * @param {string} _msg a message to show while loading
		 * @param {string|jQuery _node} _node DOM selector id or jquery DOM object, default is body
		 * @param {string} _mode	defines the animation mode, default mode is spinner
		 *	animation modes:
		 *		- spinner: a sphere with a spinning bar inside
		 *		- horizental: a horizental bar
		 *
		 * @returns {jquery dom object|null} returns jQuery DOM object or null in case of hiding
		 */
		loading_prompt: function(_id,_stat,_msg,_node, _mode)
		{
			var $container = '';
			var jQuery = _wnd.jQuery;

			var id = _id? 'egw-loadin-prompt_'+_id: 'egw-loading-prompt_1';
			var mode = _mode || 'spinner';
			if (_stat)
			{
				var $node = _node? jQuery(_node): jQuery('body');

				var $container = jQuery(_wnd.document.createElement('div'))
						.attr('id', id)
						.addClass('egw-loading-prompt-container ui-front');

				var $text = jQuery(_wnd.document.createElement('span'))
						.addClass('egw-loading-prompt-'+mode+'-msg')
						.text(_msg)
						.appendTo($container);
				var $animator = jQuery(_wnd.document.createElement('div'))
						.addClass('egw-loading-prompt-'+mode+'-animator')
						.appendTo($container);
				if (!_wnd.document.getElementById(id)) $container.insertBefore($node);
				return $container;
			}
			else
			{
				$container = jQuery(_wnd.document.getElementById(id));
				if ($container.length > 0) $container.remove();
				return null;
			}
		},

		/**
		 * Refresh given application _targetapp display of entry _app _id, incl. outputting _msg
		 *
		 * Default implementation here only reloads window with it's current url with an added msg=_msg attached
		 *
		 * @param {string} _msg message (already translated) to show, eg. 'Entry deleted'
		 * @param {string} _app application name
		 * @param {(string|number)} _id id of entry to refresh or null
		 * @param {string} _type either 'update', 'edit', 'delete', 'add' or null
		 * - update: request just modified data from given rows.  Sorting is not considered,
		 *		so if the sort field is changed, the row will not be moved.
		 * - update-in-place: update row, but do NOT move it, or refresh if uid does not exist
		 * - edit: rows changed, but sorting may be affected.  Requires full reload.
		 * - delete: just delete the given rows clientside (no server interaction neccessary)
		 * - add: requires full reload for proper sorting
		 * @param {string} _targetapp which app's window should be refreshed, default current
		 * @param {(string|RegExp)} _replace regular expression to replace in url
		 * @param {string} _with
		 * @param {string} _msg_type 'error', 'warning' or 'success' (default)
		 * @param {object|null} _links app => array of ids of linked entries
		 * or null, if not triggered on server-side, which adds that info
		 */
	   refresh: function(_msg, _app, _id, _type, _targetapp, _replace, _with, _msg_type, _links)
	   {
			// Log for debugging purposes
			this.debug("log", "egw_refresh(%s, %s, %s, %o, %s, %s)", _msg, _app, _id, _type, _targetapp, _replace, _with, _msg_type, _links);

			var win = typeof _targetapp != 'undefined' ? _wnd.egw_appWindow(_targetapp) : _wnd;

			this.message(_msg, _msg_type);

			if(typeof _links == "undefined")
			{
				_links = [];
			}

			// notify app observers: if observer for _app itself returns false, no regular refresh will take place
			// app's own observer can replace current app_refresh functionality
			var no_regular_refresh = false;
			for(var app_obj of _wnd.egw.window.EgwApp)	// run observers in main window (eg. not iframe, which might be opener!)
			{
				if (typeof app_obj.observer == 'function' &&
					app_obj.observer(_msg, _app, _id, _type, _msg_type, _links) === false && app_obj.appname === _app)
				{
					no_regular_refresh = true;
				}
			}
			if (no_regular_refresh) return;

			// if we have a framework template, let it deal with refresh, unless it returns a DOMwindow for us to refresh
			if (win.framework && win.framework.refresh &&
				!(win = win.framework.refresh(_msg, _app, _id, _type, _targetapp, _replace, _with, _msg_type)))
			{
				return;
			}

			// if window registered an app_refresh method or overwritten app_refresh, just call it
			if(typeof win.app_refresh == "function" && typeof win.app_refresh.registered == "undefined" ||
				typeof win.app_refresh != "undefined" && win.app_refresh.registered(_app))
			{
				win.app_refresh(_msg, _app, _id, _type);
				return;
			}

			// etemplate2 specific to avoid reloading whole page
			if(typeof win.etemplate2 != "undefined" && win.etemplate2.app_refresh)
			{
				var refresh_done = win.etemplate2.app_refresh(_msg, _app, _id, _type);

				// Refresh target or current app too
				if ((_targetapp || this.app_name()) != _app)
				{
					refresh_done = win.etemplate2.app_refresh(_msg, _targetapp || this.app_name()) || refresh_done;
				}
				//In case that we have etemplate2 ready but it's empty and refresh is not done
				if (refresh_done) return;
			}

			// fallback refresh by reloading window
			var href = win.location.href;

			if (typeof _replace != 'undefined')
			{
				href = href.replace(typeof _replace == 'string' ? new RegExp(_replace) : _replace, (typeof _with != 'undefined' && _with != null) ? _with : '');
			}

			if (href.indexOf('msg=') != -1)
			{
				href = href.replace(/msg=[^&]*/,'msg='+encodeURIComponent(_msg));
			}
			else if (_msg)
			{
				href += (href.indexOf('?') != -1 ? '&' : '?') + 'msg=' + encodeURIComponent(_msg);
			}
			//alert('egw_refresh() about to call '+href);
			win.location.href = href;
	   },


		/**
		 * Handle a push notification about entry changes from the websocket
		 *
		 * @param  pushData
		 * @param {string} pushData.app application name
		 * @param {(string|number)} pushData.id id of entry to refresh or null
		 * @param {string} pushData.type either 'update', 'edit', 'delete', 'add' or null
		 * - update: request just modified data from given rows.  Sorting is not considered,
		 *		so if the sort field is changed, the row will not be moved.
		 * - edit: rows changed, but sorting may be affected.  Requires full reload.
		 * - delete: just delete the given rows clientside (no server interaction neccessary)
		 * - add: requires full reload for proper sorting
		 * @param {object|null} pushData.acl Extra data for determining relevance.  eg: owner or responsible to decide if update is necessary
		 * @param {number} pushData.account_id User that caused the notification
		 */
		push: function(pushData)
		{
			// Log for debugging purposes
			this.debug("log", "push(%o)", pushData);

			if (typeof pushData == "undefined")
			{
				this.debug('warn', "Push sent nothing");
				return;
			}

			// notify app observers
			for (var app_obj of _wnd.egw.window.EgwApp)	// run observers in main window (eg. not iframe, which might be opener!)
			{
				if (typeof app_obj.push == 'function')
				{
					app_obj.push(pushData);
				}
			}
		}
	};

});