Ext.namespace('Zarafa');

/**
 * @class Zarafa
 * Global convenience methods.
 * @singleton
 * #core
 */
Ext.apply(Zarafa, {
	/**
	 * Ready flag which indicates that Webapp has been loaded.
	 * (See {@link #onReady}).
	 * @property
	 * @type Boolean
	 */
	isReady : false,

	/**
	 * Registration object for {@link #onReady} onto which all event
	 * handlers are being registered which want to be notified when
	 * WebApp has been intialized and ready for plugin interaction.
	 *
	 * @property
	 * @type Ext.util.Event
	 * @private
	 */
	readyEvent : new Ext.util.Event(),

	/**
	 * Ready flag which indicates that Webapp UI has been loaded.
	 * (See {@link #onUIReady}).
	 * @property
	 * @type Boolean
	 */
	uiReady : false,

	/**
	 * Registration object for {@link #uiReady} onto which all event
	 * handlers are being registered which want to be notified when
	 * WebApp has drawn the main UI and has loaded the hierarchy panel.
	 *
	 * @property
	 * @type Ext.util.Event
	 * @private
	 */
	uiReadyEvent : new Ext.util.Event(),

	/**
	 * The time that the user has not done any action
	 * (like mousemove, click, or keypress) in the WebApp.
	 *
	 * @property
	 * @type Integer
	 * @private
	 */
	idleTime : 0,

	/**
	 * True if the Wingdings font is installed on the system of the user, false
	 * otherwise

	 * @property
	 * @type {Boolean}
	 */
	wingdingsInstalled : false,

	/**
	 * True if the user is running DeskApp to view WebApp, false otherwise.
	 *
	 * @property
	 * @type {Boolean}
	 */
	isDeskApp : Ext.isDefined(window.nw),

	/**
	 * Adds a listener to be notified when WebApp is ready. This will be somewhere during {@link Ext.onReady}, when
	 * WebApp has initialized the bare essentials. When the event is fired, the {@link Zarafa.core.Container} will
	 * be available, and plugins are allowed to register.
	 *
	 * @param {Function} fn The method the event invokes.
	 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
	 * @param {Boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options
	 * <code>{single: true}</code> be used so that the handler is removed on first invocation.
	 */
	onReady : function(fn, scope, options)
	{
		this.readyEvent.addListener(fn, scope, options);

		// If the environment is already ready, can
		// should call fireReady again to fire the
		// just registered event.
		if (this.isReady) {
			this.fireReady();
		}
	},

	/**
	 * Called when {@link Ext.onReady} has been invoked, and WebApp has been initialized.
	 * All handlers registered through {@link #onReady} will now be fired and {@link #isReady}
	 * will be set.
	 *
	 * @private
	 */
	fireReady : function()
	{
		this.isReady = true;
		this.readyEvent.fire();
		this.readyEvent.clearListeners();
	},

	/**
	 * Adds a listener to be notified when WebApp UI is drawn and the hierarchy is loaded.
	 * @param {Function} fn The method the event invokes.
	 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
	 * @param {Boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options
	 * <code>{single: true}</code> be used so that the handler is removed on first invocation.
	 */
	onUIReady : function(fn, scope, options)
	{
		// Force single is true for events.
		options = options || {};
		options.single = true;

		this.uiReadyEvent.addListener(fn, scope, options);

		// If the environment is already ready, call fireUIReady again
		// to fire the just registered event.
		if (this.uiReady) {
			this.fireUIReady();
		}
	},

	/**
	 * Called when WebApp's UI has been loaded and the hiearchy is loaded.
	 * All handlers registered through {@link #onUIReady} will now be fired and {@link #uiReady}
	 * will be set.
	 *
	 * @private
	 */
	fireUIReady : function()
	{
		this.uiReady = true;
		this.uiReadyEvent.fire();
		this.uiReadyEvent.clearListeners();
	},

	/**
	 * Initialize all Global variables as used by the WebApp.
	 *
	 * This will utilize some global objects as received by the PHP
	 * side, and apply them into the proper classes, after which the
	 * global objects will be destroyed.
	 *
	 * This will instantiate {@link Zarafa.core.Container container}.
	 * @private
	 */
	initializeGlobals : function()
	{
		// Use native json handling of browser for performance benefit
		Ext.USE_NATIVE_JSON = true;

		//show confirm dialog before user leave the page.
		Zarafa.core.Util.enableLeaveRequester();

		// When the browser is unloading, all active requests will be aborted and
		// If more than one browser windows are open then close all browser windows.
		window.onunload = function () {
			if(Zarafa.core.BrowserWindowMgr.browserWindows.length > 1){
				Zarafa.core.BrowserWindowMgr.closeAllBrowserWindow();
			}
			container.getRequest().paralyze(Zarafa.core.data.ParalyzeReason.BROWSER_RELOADING);
		};

		// Create global container object
		/*jshint -W020 */ /* Ignore global read-only warning. */
		container = new Zarafa.core.Container();

		// Set the server object
		/*jshint -W051 */ /* Ignore variables should not be deleted warning. */
		container.setServerConfig(serverconfig);
		delete serverconfig;

		// Load all settings
		/*jshint -W051 */ /* Ignore variables should not be deleted warning. */
		container.getSettingsModel().initialize(settings);
		delete settings;

		// Load all persistent settings (i.e. settings that will not be deleted when the user resets his settings)
		// Persistent settings are not added to the welcome screen, so check if they exist first.
		if ( Ext.isDefined(window.persistentsettings) ){
			container.getPersistentSettingsModel().initialize(window.persistentsettings);
			/*jshint -W051 */ /* Ignore variables should not be deleted warning. */
			delete window.persistentsettings;
		}

		// Set the user object
		container.setUser(user);
		delete user;

		// Set the version object
		container.setVersion(version);
		delete version;

		// Set the language object
		container.setLanguages(languages);
		delete languages;
	},

	/**
	 * Initialize the ExtJs/WebApp environment, register generic event listeners,
	 * This will listen to the 'contextmenu' event on the {@link Ext#getBody body element}
	 * as well as the exception events on the {@link Zarafa.core.data.IPMStoreMgr} and
	 * {@link Zarafa.core.ResponseRouter}.
	 * @private
	 */
	initializeEnvironment : function()
	{
		// Register the State provider which uses the SettingsModel.
		Ext.state.Manager.setProvider(new Zarafa.core.data.SettingsStateProvider());

		// Disable contextmenu globaly
		Ext.getBody().on('contextmenu', this.onBodyContextMenu, this);

		// Disable default file drop behavior
		Ext.EventManager.on(window, 'dragover', this.onWindowDragDrop, this);
		Ext.EventManager.on(window, 'drop', this.onWindowDragDrop, this);

		// Add main event handlers to listen for errors
		container.getRequest().on('connectionparalyzed', this.onConnectionParalyze, this);
		container.getRequest().on('connectioninterrupted', this.onConnectionLoss, this);
		container.getRequest().on('connectionrestored', this.onConnectionRestore, this);
		container.getResponseRouter().on('receiveexception', this.onReceiveException, this);
		// We listen on the Ext.data.DataProxy object to listen in on all exception events
		Ext.data.DataProxy.on('exception', this.onException, this);

		// Enable tooltips
		Ext.QuickTips.init();
	},

	/**
	 * Event handler which is fired when the {@link Ext#getBody &lt;body&gt;} elements fires
	 * the 'contextmenu' event. If the element which fired the event doesn't have the
	 * 'zarafa-contextmenu-enabled' class and isn't a regular text input then the
	 * Browser contextmenu will be disabled.
	 * @param {Ext.EventObject} event The event object
	 * @param {Ext.Element} el The element on which the contextmenu was requested
	 * @private
	 */
	onBodyContextMenu : function(event, el)
	{
		el = Ext.get(el);

		// Don't disable the browser contextmenu when the
		// 'zarafa-contextmenu-enabled' CSS class is applied
		// on the element.
		if ( el.hasClass('zarafa-contextmenu-enabled') ){
			return;
		}

		// Don't disable the browser contextmenu for regular
		// text inputs.
		if ( el.dom.tagName.toUpperCase()==='INPUT' ){
			var type = el.getAttribute('type') || '';
			var readonly = !Ext.isEmpty(el.dom.attributes.readonly);
			if ( type.toUpperCase() === 'TEXT' && !readonly ) {
				return;
			}
		}

		// Disable contextmenu.
		event.preventDefault();
	},

	/**
	 * Event handler which is fired when the 'window' element fires the 'dragover' or
	 * the 'drop' event. This happens when the user drops a file over the webpage. On
	 * some UI fields this will provide a special action, but the browsers default is
	 * to open the file in the current page, which is not what we want.
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onWindowDragDrop : function(event)
	{
		event.stopPropagation();
		event.preventDefault();
		return false;
	},

	/**
	 * Event handler called when the {@link Ext.data.DataProxy} fired the
	 * {@link Ext.data.DataProxy#storeexception storeexception} event.
	 * This will check what type of exception it was ('response' or 'remote') and
	 * handle the exception accordingly.
	 *
	 * @param {Misc} misc See {@link Ext.data.DataProxy}#{@link Ext.data.DataProxy#exception exception}
	 * for description.
	 * @private
	 */
	onException : function(proxy, type, action, options, response, args)
	{
		var message;

		if (type === 'response') {
			// The error message can be in args when it is an Error object. This happens when the
			// processing of the response throws an Javascript Exception.
			if (Ext.isDefined(args) && args.error instanceof Error) {
				message = args.error.toString();
			} else {
				// When the exception has to do with the response itself we delegate this behavior to
				// onReceiveException
				this.onReceiveException(options, response);
				return;
			}
		} else if (response && response.error) {
			switch (response.error.type) {
				case Zarafa.core.ErrorType['MAPI']:
				case Zarafa.core.ErrorType['ZARAFA']:
				case Zarafa.core.ErrorType['GENERAL']:
					message = response.error.info.display_message;
					break;
				default:
					message = _('The server reported an unknown error on your request.');
					break;
			}
		} else {
			message = _('The server reported an unspecified error on your request.');
		}

		if (Ext.get('loading')) {
			this.setErrorLoadingMask(_('Error'), message);
		} else {
			container.getNotifier().notify('error.proxy', _('Error'), message);
		}
	},

	/**
	 * Called when the connection is being paralyzed and no further requests can be made
	 * to the server. Check if we should show a notification to the user about this, and
	 * ask if the user wishes to return to the logon page.
	 * @param {Zarafa.core.Request} request The request object
	 * @param {Zarafa.core.data.ParalyzeReason} reason The reason to paralyze the WebApp
	 * @private
	 */
	onConnectionParalyze : function(request, reason)
	{
		var message = '';
		var logoutFn = Ext.emptyFn;

		switch (reason) {
			case Zarafa.core.data.ParalyzeReason.BROWSER_RELOADING:
			/* falls through */
			default:
				// No message for the user needed.
				return;
			case Zarafa.core.data.ParalyzeReason.SESSION_EXPIRED:
				message = _('The session has expired, reauthentication is required.');
				// When logging out, we preserve the username for convenience.
				logoutFn = container.logout.createDelegate(container, [ true ], false);
				break;
			case Zarafa.core.data.ParalyzeReason.SESSION_INVALID:
				message = _('The session in the current browser window has been closed from another browser window or tab.');
				// When logging out, we preserve the username for convenience,
				// but we won't close the session which was created in the other tab.
				logoutFn = container.logout.createDelegate(container, [ true, true ], false);
				break;
		}

		if (Ext.get('loading')) {
			this.setErrorLoadingMask(_('Error'), message);
		} else {
			Ext.MessageBox.show({
				title: _('Kopano WebApp'),
				msg : message + '<br>' +  _('Do you wish to be redirected to the logon page?'),
				icon : Ext.MessageBox.ERROR,
				buttons : Ext.MessageBox.YESNO,
				fn : this.onConnectionParalyzeConfirmation,
				scope: this,
				logoutFn : logoutFn
			});
		}
	},

	/**
	 * Event handler for the {@link Ext.MessageBox MessageBox} which was opened by {@link #onConnectionParalyze}.
	 * This determines what the user has pressed, and if the user wishes to {@link Zarafa.core.Container#logout}
	 * immediately or not. If not, then a {@link Zarafa.core.ui.notifier.Notifier notification} will be shown
	 * to remind the user about the session.
	 * @param {String} button The button which was pressed by the user
	 * @param {String} id The id of the button which was clicked
	 * @param {Object} opt The options which was used to create the MessageBox.
	 * @private
	 */
	onConnectionParalyzeConfirmation : function(button, value, opt)
	{
		if (button === 'yes') {
			opt.logoutFn.call(this);
		} else {
			container.getNotifier().notify('error.connection', _('Session expired'), _('Reauthentication required, click here to go to back to logon page.'), {
				persistent : true,
				listeners : {
					click : opt.logoutFn
				}
			});
		}
	},

	/**
	 * Periodic function which is used to update the {@link Zarafa.core.ui.notification.Notifier notification}
	 * on the screen with the message that there is a connection problem, and the requests will be retried
	 * after the given timeout. This timeout counter will be updated every second.
	 * @param Zarafa.core.PingService} service The ping service handling the connection loss
	 * @param {Object} object Containing the information related to the connection loss
	 * @param {Number} timeout The number of seconds until the next retry to connect to the server
	 * @private
	 */
	onConnectionTimeupdate : function(service, object, timeout)
	{
		var request = container.getRequest();

		// Since we use defers, it is possible that the
		// connection was already restored. In that case,
		// we don't need to update the Notification message.
		if (!request.isInterrupted() || request.isParalyzed()) {
			return;
		}

		// Create the notification, we store the reference in connEl,
		// if it already exists, this will be an update action, otherwise
		// a new notification will be created.
		this.connEl = container.getNotifier().notify('error.connection', _('Connection problem'),
								 String.format(_('Could not connect to server, retrying in {0} second(s)'), timeout / 1000) + '<br />' + _('Click to retry now'), {
			persistent : true,
			update : !!this.connEl,
			reference : this.connEl,
			listeners : {
				// If the user clicks on the notification,
				// immediately retry to connecto to the server.
				click : service.retry,
				scope: service
			}
		});

		// Defer the function by 1 second, so we can update the retry counter.
		if (timeout > 1000) {
			this.connElTask.delay(1000, this.onConnectionTimeupdate, this, [ service, object, timeout - 1000 ]);
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.PingService#retry} event. This will
	 * cancel the currently scheduled call to {@link #onConnectionTimeupdate} and
	 * invoke it manually with the updated information.
	 * @param Zarafa.core.PingService} service The ping service handling the connection loss
	 * @param {Object} object Containing the information related to the connection loss
	 * @param {Number} timeout The number of seconds until the next retry to connect to the server
	 * @private
	 */
	onConnectionRetry : function(service, object, timeout)
	{
		// In case there was still a pending
		// update task, we interrupt that one.
		this.connElTask.cancel();

		// No we can update the notification
		this.onConnectionTimeupdate(service, object, timeout);
	},

	/**
	 * Event handler for the {@link Zarafa.core.Request#connectionloss} event. This will register the
	 * event handlers to the provided {@link Zarafa.core.PingService} which will give us the information
	 * about the next reconnect attempt.
	 * @param {Zarafa.core.Request} request The request object
	 * @param {Zarafa.core.PingService} service The ping service which is going to handle the connection loss
	 * @private
	 */
	onConnectionLoss : function(request, service)
	{
		this.connElTask = new Ext.util.DelayedTask(this.onConnectionTimeupdate, this);
		service.on('retry', this.onConnectionRetry, this);
	},

	/**
	 * Event handler for the {@link Zarafa.core.Request#connectionrestore} event. This will remove
	 * the error.connection {@link Zarafa.core.ui.notification.Notifier notification}
	 * and will show a info.connection.restore {@link Zarafa.core.ui.notification.Notifier notification}.
	 * @param {Zarafa.core.Request} request The request object
	 * @private
	 */
	onConnectionRestore : function(request)
	{
		if (this.connElTask) {
			this.connElTask.cancel();
			delete this.connElTask;
		}

		if (this.connEl) {
			container.getNotifier().notify('error.connection', null, null, {
				destroy : true,
				reference : this.connEl
			});
			container.getNotifier().notify('info.connection.restore', _('Connection restored'), _('Connection with server has been restored'));
			delete this.connEl;
		}
	},

	/**
	 * Event handler called when the PHP server returned an error
	 * in the root of the response. This indicates that the communication
	 * with the PHP server failed and the user should login again.
	 *
	 * @param {Object} requestdata The request data which was send to the server.
	 * @param {Object} xmlHttpRequest The raw browser response objec
	 * @private
	 */
	onReceiveException : function(requestdata, xmlHttpRequest)
	{
		var loading = Ext.get('loading');
		var errorTitle;
		var errorMsg;

		if (xmlHttpRequest.status !== 200) {
			// # TRANSLATORS: Example: HTTP 404
			errorTitle = String.format(_('HTTP {0}'), xmlHttpRequest.status);
			errorMsg = xmlHttpRequest.statusText;
		} else {
			errorTitle = _('Error');
			errorMsg = _('Invalid data received from the server');
		}

		if (loading) {
			this.setErrorLoadingMask(errorTitle, errorMsg);
		} else {
			container.getNotifier().notify('error.json', errorTitle, errorMsg);
		}
	},

	/**
	 * Hide the loading mask which is shown before the {@link Ext.Viewport} is being rendered.
	 * The loadmask is marked with the element classname 'loading' and the background 'loading-mask'.
	 * @param {Function} callback An optional callback function that will be called when the the
	 * loading mask is completely hidden.
	 * @private
	 */
	hideLoadingMask : function(callback)
	{
		var loadingMask = Ext.get('loading-mask');

		if ( loadingMask ) {
			// Hide loading mask
			loadingMask.fadeOut({
				duration: 1,
				remove: true,
				callback: Ext.isFunction(callback) ? callback : Ext.EmptyFn
			});
		}
	},

	/**
	 * Set an error text in the loading screen.
	 * @param {String} newError The title for the loading screen
	 * @param {String} newMessage The message for the loading screen
	 * @private
	 */
	setErrorLoadingMask : function(newTitle, newMessage)
	{
		var template = new Ext.Template('<div><b>{title}</b><br />{msg}</div>', { compiled : true, disableFormats : true });
		var message = Ext.get('loading-message');
		if (message) {
			message.dom.className = 'loading-error';
			template.overwrite(message, { title: newTitle, msg: newMessage });
		}
	},

	/**
	 * Validate the {@link Zarafa.hierarchy.data.HierarchyStore HierarchyStore} to determine
	 * if the {@link Zarafa.hierarchy.data.HierarchyStore#getDefaultStore Default Store} is present
	 * along with all the {@link Zarafa.hierarchy.data.HierarchyStore#getDefaultFolder Default Folders}.
	 * If there is a problem, this will show a {@link Zarafa.core.ui.notifier.Notifier Notification}
	 * indicating which store or folders are missing, and warning the user that not all functionality
	 * might be working as expected.
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The Hierarchy Store to validate
	 * @private
	 */
	validateHierarchy : function(store)
	{
		if (!store.getDefaultStore()) {
			container.getNotifier().notify('error.hierarchy.defaultfolder',
				_('Missing store'),
				_('The default store is missing from the hierarchy.') +
					'<br>' +
					_('Not all functionality of WebApp might be working properly because of this.'),
				{
					persistent : true,
					listeners : {
						'click' : this.onHierarchyNotifierClick,
						'scope': this
					}
				}
			);
			return;
		}

		// The following default folders are required to be present
		// to be able to properly work with the WebApp.
		var defaultFolders = [{
			type : 'inbox',
			name : pgettext('hierarchy.foldername', 'Inbox')
		},{
			type : 'outbox',
			name : pgettext('hierarchy.foldername', 'Outbox')
		},{
			type : 'sent',
			name : pgettext('hierarchy.foldername', 'Sent Items')
		},{
			type : 'wastebasket',
			name : pgettext('hierarchy.foldername', 'Deleted items')
		},{
			type : 'calendar',
			name : pgettext('hierarchy.foldername', 'Calendar')
		},{
			type : 'contact',
			name : pgettext('hierarchy.foldername', 'Contacts')
		},{
			type : 'drafts',
			name : pgettext('hierarchy.foldername', 'Drafts')
		},{
			type : 'journal',
			name : pgettext('hierarchy.foldername', 'Journal')
		},{
			type : 'note',
			name : pgettext('hierarchy.foldername', 'Notes')
		},{
			type : 'task',
			name : pgettext('hierarchy.foldername', 'Tasks')
		},{
			type : 'junk',
			name : pgettext('hierarchy.foldername', 'Junk E-mail')
		}];

		var missing = [];

		// Go over all default folders which we expect to be present,
		// if any of them is missing we update the 'missing' array
		// and will show them in a notification box.
		for (var i = 0, len = defaultFolders.length; i < len; i++) {
			var folder = defaultFolders[i];

			if (!store.getDefaultFolder(folder.type)) {
				missing.push(folder.name);
			}
		}

		// If any of the folders is missing, show a notification to the
		// user to inform him of the missing folders.
		if (!Ext.isEmpty(missing)) {
			// Construct an HTML list of the missing folders
			var list = '<ul><li>' + missing.join('</li><li>') + '</li></ul>';

			container.getNotifier().notify('error.hierarchy.defaultfolder',
				_('Missing folders'),
				String.format(
					ngettext('The following required folder is missing in the hierarchy: {0}',
						 'The following required folders are missing in the hierarchy: {0}', missing.length), list) +
					_('Not all functionality of WebApp might be working properly because of this.'),
				{
					persistent : true,
					listeners : {
						'click' : this.onHierarchyNotifierClick,
						'scope': this
					}
				}
			);
		}
	},

	/**
	 * Event handler which is fired when the user clicked on the {@link Zarafa.core.ui.notifier.Notifier notification}.
	 * This will remove the notification.
	 * @param {Ext.Element} element The notification element
	 * @param {Ext.EventObject} event The event object
	 * @private
	 */
	onHierarchyNotifierClick : function(element, event)
	{
		container.getNotifier().notify('error.hierarchy.defaultfolder', null, null, {
			reference : element,
			destroy : true
		});
	},

	/**
	 * Event handler called when load is received in hierarchy. This will {@link #hideLoadingMask hide the loadmask}.
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The store which was loaded
	 * @param {Ext.data.Record[]} records The records which were loaded by the store
	 * @param {Object} options The options which were originally passed to {@link Ext.data.Store#load}.
	 * @private
	 */
	onHierarchyLoad : function(store, records, options)
	{
		if (!Ext.isEmpty(records)) {
			// We have the hierarchy, load the entire UI
			container.getMainPanel();

			// validate the hierarchy, if the hierarchy is not valid a warning
			// notification box will be shown to the user informing him of incompatibilities.
			this.validateHierarchy(store);

			// Remove loading mask, we might still be busy loading the data for the
			// store of the user, but the context will have its own loadmask for that.
			// The user is at least allowed to see the Hierarchy and press buttons.
			this.hideLoadingMask(function(){
				container.fireEvent('webapploaded');

				// Notify that the WebApp UI is loaded.
				Zarafa.fireUIReady();
			});

			// Remove resize event listener of loading page
			window.removeEventListener('resize', resizeLoginBox);

			// Register webapp to handle mailto urls
			this.registerMailto();

			// Process data that was passed as URL data
			Zarafa.core.URLActionMgr.execute(urlActionData);
			/*jshint -W051 */
			delete urlActionData;

			// Start the keepalive to make sure we stay logged into the zarafa-server,
			// the keepalive is also used to get notifications back to the client.
			store.startKeepAlive();
		} else {
			this.setErrorLoadingMask(_('Error'), _('Loading model from server failed'));
		}
	},

	/**
	 * Function will register webapp as default client to handle mailto urls
	 * Ideally we should only register this handler if already not registered
	 * but browsers support is not proper for that, so we are always registering it
	 * in chrome this is not a problem, in firefox it will show bar that handler is already registered
	 * but that is a bug and already fixed in trunk version https://bugzilla.mozilla.org/show_bug.cgi?id=912347
	 * Support for isProtocolHandlerRegistered is also limited https://bugzilla.mozilla.org/show_bug.cgi?id=440620
	 */
	registerMailto : function()
	{
		var navigator = window.navigator;

		if (Ext.isFunction(navigator.registerProtocolHandler)) {
			// Register webapp handler for 'mailto' protocol in browsers.
			var url = container.getBaseURL() + '?action=mailto&to=%s';

			// Check if we have already registered for this protocol
			var register = true;
			if (Ext.isFunction(navigator.isProtocolHandlerRegistered)) {
				register = !navigator.isProtocolHandlerRegistered('mailto', url);
			}

			// Register if required
			if (register) {
				navigator.registerProtocolHandler('mailto', url, 'Kopano WebApp');
			}
		}
	},

	/**
	 * Load the default Webclient into the browser. This will initialize the environment,
	 * load the {@link Zarafa.hierarchy.data.HierarchyStore} and open the
	 * {@link Zarafa.core.ui.MainViewport MainViewport}.
	 */
	loadWebclient : function()
	{
		// Initialize globals & environment
		Zarafa.initializeGlobals();
		Zarafa.initializeEnvironment();

		// Start loading all plugins
		Zarafa.fireReady();

		Zarafa.whatsnew.Actions.openWhatsNewDialog();

		// Check if user is out of office and ask them if they want to switch it off
		this.checkOof();

		// Initialize context - check if there is one selected in settings
		this.initContext();

		// Start polling the server to get reminders data back to client
		container.getReminderStore().initializeReminderInterval();

		// Setup the hierarchy, this will complete the initialization
		// and allow the Main Viewport to be opened
		var hierarchyStore = container.getHierarchyStore();

		// For the hierarchy we only need to register the event handler once,
		// as only during the first time we have to remove the load mask.
		hierarchyStore.on('load', this.onHierarchyLoad, this, { single: true });

		// Load the folder hierarchy
		hierarchyStore.load();
		// When a client timeout has been defined, we will start keeping track
		// of idle time.
		var server = container.getServerConfig();
		var clientTimeout = server.getClientTimeout();
		if (clientTimeout){
			this.startIdleTimeChecker(clientTimeout);
		}

		// Check if the Wingdings font is installed
		this.wingdingsInstalled = window.checkfont.exists('Wingdings');
	},

	/**
	 * Starts the checking of idle time.
	 * This function uses original javascript events because we cannot set
	 * the useCapture property with ExtJS events.
	 * See https://developer.mozilla.org/en/docs/Web/API/EventTarget.addEventListener
	 *
	 * @param {Number} clientTimeout The timout time in seconds.
	 * @private
	 */
	startIdleTimeChecker : function(clientTimeout)
	{
		if ( !document.addEventListener ) {
			// User is using a browser that does not support addEventListener.
			// Probably IE<9 which we don't support.
			// However there is no reason to create errors for IE<9
			// Client timeout will still be handled by the backend though,
			// but the message will only be shown to the user when he tries to
			// connect to the backend after the session has timed out.
			return;
		}
		var me = this;

		document.addEventListener('click', function(){
			me.idleTime = 0;
		}, true);
		document.addEventListener('mousemove', function(){
			me.idleTime = 0;
		}, true);
		document.addEventListener('keydown', function(){
			me.idleTime = 0;
		}, true);

		var hierarchyStore = container.getHierarchyStore();

		// Start an interval for increasing the idle time
		Ext.TaskMgr.start.createDelegate(this, [{
			run : function(){
				// Add 5 seconds to the idle time counter
				this.idleTime += 5;
				if ( this.idleTime > clientTimeout ){
					hierarchyStore.sendDestroySession();
				}
			},
			scope : this,
			interval : 5000 //Run every 5 seconds
		}]).defer(5000); // Start after 5 seconds

		// Start an interval for sending keepalive requests
		// We need this keepalive to keep the connection alive if the user has made an
		// action in the WebApp without connecting to the server. (like mousemove, click, keydown)
		// Substracting 5 seconds to account for latency
		var interval = (clientTimeout-5)*1000;
		if ( interval < 5000 ){
			// This one is especially for Sean who was so smart to set a client timeout of 5 seconds
			// causing keepalive requests to be sent constantly and thereby ddos'ing his own server :-)
			// Let's never send keepalive requests with an interval lower than 5 seconds.
			// Anyone who sets a timeout this low deserves to be logged out! (and punished severly)
			interval = 5000;
		}

		Ext.TaskMgr.start.createDelegate(this, [{
			run : function(){
				hierarchyStore.sendKeepAlive();
			},
			scope : this,
			interval : interval
		}]).defer(interval); //Start sending keepalives after interval milliseconds.
	},

	/**
	 * init UI by lazily constructing the main panel (implicit in container.getMainPanel) and
	 * setting the default context to visible. Note that during onHierarchyLoad we will also
	 * open the default folder for that specific context to ensure the user can see all from
	 * the folder.
	 * @private
	 */
	initContext : function()
	{
		var defaultContext = container.getSettingsModel().get('zarafa/v1/main/default_context');
		var plugin = container.getContextByName(defaultContext);
		if (!plugin) {
			// If the defaultContext has an invalid name,
			// we will default to the 'today' context
			plugin = container.getContextByName('today');
		}
		container.switchContext(plugin);
	},

	/**
	 * Check if user is out of office
	 * If so, ask them if they want to switch OOF off
	 * @private
	 */
	checkOof : function()
	{
		var oof = false;

		if (container.getSettingsModel().get('zarafa/v1/contexts/mail/outofoffice/set') === true) {
			var oofFrom = container.getSettingsModel().get('zarafa/v1/contexts/mail/outofoffice/from');
			var oofUntil = container.getSettingsModel().get('zarafa/v1/contexts/mail/outofoffice/until');
			var date = new Date().getTime()/1000;

			// Check if current date fall within the time span of OOF start-date and end-date, if configured.
			if (oofFrom <= date) {
				// Check if end-date is configured, no need to check otherwise
				if(oofUntil === 0 || oofUntil > date) {
					oof = true;
				} else {
					// Current date falls out of the configured time span, so disable the OOF
					container.getSettingsModel().set('zarafa/v1/contexts/mail/outofoffice/set', false);
				}
			}
		}

		if ( oof ) {
			Ext.MessageBox.confirm(
				_('Kopano WebApp'),
				_('Out of Office currently on. Would you like to turn it off?'),
				this.onOofConfirm,
				this
			);
		}
	},

	/**
	 * Handler for the out of office confirmation box
	 * If the user pressed Yes/Ok, then disable out of office
	 * @param {String} id of the button that was clicked
	 * @private
	 */
	onOofConfirm : function(button)
	{
		if (button === 'yes') {
			container.getSettingsModel().set('zarafa/v1/contexts/mail/outofoffice/set', false);
			container.getNotifier().notify('info.saved', _('Out of office off'), _('Out of office has been turned off'));
		}
	},

	/**
	 * Load the Welcome message for new users into the browser. This will initialize
	 * the environment and open the {@link Zarafa.core.ui.WelcomeViewport WelcomeViewport}.
	 */
	loadWelcome : function()
	{
		// Setup globals & environment
		this.initializeGlobals();
		this.initializeEnvironment();

		// Start loading all plugins
		this.fireReady();

		// Load the welcome view
		container.getWelcomePanel();
		this.hideLoadingMask();
	},

	//
	// Generic Utility functions, should probably be moved elsewhere
	//

	/**
	 * This will resize a canvas {@link Ext.Element element}.
	 * Canvas resizing is more tricky then one would expect. For canvas 2 settings are
	 * important: the CSS and attribute dimensions.
	 *
	 * As one would expect, the CSS dimensions, given through a CSS file, or
	 * 'style' attribute (controlled by the functions {@link Ext.Element#setWidth setWidth}
	 * and {@link Ext.Element#setHeight setHeight}) controls how big the element itself
	 * is. It however does not mean that the drawing area is of the same size. The drawing
	 * area is controlled by the element attributes 'width' and 'height'.
	 *
	 * Now for the fun part, if you use CSS to size of the element to 1000x1000px,
	 * thus the element looks like:
	 *   <canvas style="width=1000px; height=1000px;">
	 * and set the attributes to 50x50px, making the complete element look like:
	 *   <canvas style="width=1000px; height=1000px;" width="50" height="50">
	 * you can then draw anything you like on the canvas, but only the first
	 * 50x50px of the canvas will be resized to the entire element size. Making
	 * your cute little drawing of a giraffe completely stretched.
	 *
	 * @param {Ext.Element} canvas The canvas element which must be resized.
	 * @param {Number} width The desired width of the canvas element
	 * @param {Number} height The desired height of the canvas element
	 */
	resizeCanvas : function(canvas, width, height)
	{
		canvas.setWidth(canvas.dom.width = width);
		canvas.setHeight(canvas.dom.height = height);
	},

	/**
	 * Generate a random string.
	 * @param {Number} len Length of the string
	 * @return {String} Random string
	 */
	generateId : function(len)
	{
		var text = "";
		var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

		for( var i = 0; i < len; i++ ) {
			text += possible.charAt(Math.floor(Math.random() * possible.length));
		}

		return text;
	},

	/**
	 * Determine if separate window popout is supported.
	 * Support is based on browser's ability to render any element into another browser window.
	 *
	 * @return {Boolean} True if popout is supported, false otherwise
	 */
	supportsPopOut : function()
	{
		// Currently, we do not support the popout in case of IE/Edge.
		return (!(Ext.isIE || Ext.isEdge));
	}
});