Ext.namespace('Zarafa.plugins.chat');

/**
 * @class Zarafa.plugins.chat.Chat
 * @extends Zarafa.core.Plugin
 *
 * Plugin that adds Chat to grommunio Web
 */
Zarafa.plugins.chat.Chat = Ext.extend(Zarafa.core.Plugin, {

	/**
	 * Initializes the plugin.
	 */
	initPlugin : function(){
		var pluginSettings = container.getSettingsModel().get('zarafa/v1/plugins/chat', true);
		var site = {
			url: pluginSettings.url,
			tabOrder: 9
		};

		// The tab in the top tabbar
		this.registerInsertionPoint('main.maintabbar.left', this.createMainTab.createDelegate(this, [site]), this);

		// The settings category
		this.registerInsertionPoint('context.settings.categories', this.createSettingCategory, this);

		// Register mail specific dialog types
		Zarafa.core.data.SharedComponentType.addProperty('plugins.chat.panel');

		// Autostart if needed
		if ( pluginSettings.autostart ){
			container.on('webapploaded', function(){
				// use the openTab function for convenience
				this.openTab(this.createMainTab(site));

				// OpenTab will also switch to the chat tab, but we don't want that, so
				// let's switch back to the main tab.
				var mainContentTabPanel = container.getMainPanel().contentPanel;
				mainContentTabPanel.activate(0);
			}, this);
		}
	},

	/**
	 * Adds a button to the top tab bar for this context.
	 * @return {Object} The button for the top tabbar
	 * @private
	 */
	createMainTab: function(site)
	{
		return {
			text: 'Chat',
			url: site.url,
			tabOrderIndex: site.tabOrder,
			cls: 'mainmenu-button-chat',
			handler: this.openTab
		};
	},

	/**
	 * Event handler for the click event of the tabbar buttons. It will
	 * open the tab if it already exists, or create it otherwise.
	 * @param {Zarafa.core.ui.MainTab} btn The button in the
	 * {@link Zarafa.core.ui.MainTabBar main tabbar}
	 */
	openTab: function(btn)
	{
		var tabIndex;
		Ext.each(container.getTabPanel().items.items, function(item, index){
			if ( item.tabId === 'chat' ){
				tabIndex = index;
			}
		});

		if ( Ext.isDefined(tabIndex) ){
			// open the existing tab
			var mainContentTabPanel = container.getMainPanel().contentPanel;
			mainContentTabPanel.activate(tabIndex);
			return;
		}

		// Create a new tab
		Zarafa.core.data.UIFactory.openLayerComponent(
			Zarafa.core.data.SharedComponentType['plugins.chat.panel'],
			null,
			{
				url: btn.url,
				title: btn.text,
				tabOrder: btn.tabOrderIndex,
				tabId: 'chat'
			}
		);
	},

	createSettingCategory : function()
	{
		return {
			xtype: 'zarafa.plugins.chat.settings.category'
		};
	},

	/**
	 * Bid for the type of shared component
	 * and the given record.
	 * This will bid on a common.dialog.create or common.dialog.view for a
	 * record with a message class set to IPM or IPM.Note.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context
	 * can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Number} The bid for the shared component
	 */
	bidSharedComponent : function(type, record)
	{
			var bid = -1;

			switch (type) {
				case Zarafa.core.data.SharedComponentType['plugins.chat.panel']:
					bid = 1;
			}

			return bid;
	},

	/**
	 * Will return the reference to the shared component.
	 * Based on the type of component requested a component is returned.
	 * @param {Zarafa.core.data.SharedComponentType} type Type of component a context
	 * can bid for.
	 * @param {Ext.data.Record} record Optionally passed record.
	 * @return {Ext.Component} Component
	 */
	getSharedComponent : function(type, record)
	{
		var component;
		switch (type)
		{
			case Zarafa.core.data.SharedComponentType['plugins.chat.panel']:
				return Zarafa.plugins.chat.ui.ContentPanel;
		}
	}

});

Zarafa.onReady(function() {
	container.registerPlugin(new Zarafa.core.PluginMetaData({
		name : 'chat',
		displayName : 'Chat',
		allowUserVisible : true,
		pluginConstructor : Zarafa.plugins.chat.Chat
	}));
});
Ext.namespace('Zarafa.plugins.chat.ui');

/**
 * @class Zarafa.plugins.chat.ui.ContentPanel
 * @extends Zarafa.core.ui.ContentPanel
 */
Zarafa.plugins.chat.ui.ContentPanel = Ext.extend(Zarafa.core.ui.ContentPanel, {
	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor : function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			// Overridden from Ext.Component
			xtype: 'zarafa.plugins.chat.ui.contentpanel',
			layout : 'fit',
			header: false,
			iconCls: 'icon_chat',
			border: false,
			items : [{
				xtype: 'zarafa.plugins.chat.ui.panel',
				url: config.url,
				tabOrder: config.tabOrder
			}]
		});

		Zarafa.plugins.chat.ui.ContentPanel.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.plugins.chat.ui.contentpanel', Zarafa.plugins.chat.ui.ContentPanel);
Ext.namespace('Zarafa.plugins.chat.ui');

/**
 * @class Zarafa.plugins.chat.ui.Panel
 * @extends Ext.Panel
 */
Zarafa.plugins.chat.ui.Panel = Ext.extend(Ext.Panel, {
	/**
	 * The id of the iframe element inside this panel
	 * @property
	 * @type String
	 */
	iframeId : undefined,

	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor : function(config)
	{
		config = config || {};

		this.iframeId = 'chat-iframe-'+config.tabOrder;

		Ext.applyIf(config, {
			// Overridden from Ext.Component
			xtype: 'zarafa.plugins.chat.ui.panel',
			layout : 'fit',
			header: false,
			border: false,
			iconCls: 'icon_chat',
			html : {
				tag: 'iframe',
				id: this.iframeId,
				cls: 'chat-iframe',
				src: config.url,
				style: 'display:block',
				partition: 'persist:kw-plugin-chat-'+container.getUser().getUserName(),
				allow: 'geolocation; microphone; camera; midi; encrypted-media'
			},
			listeners: {
				afterrender: this.onAfterRender,
				scope: this
			}
		});

		Zarafa.plugins.chat.ui.Panel.superclass.constructor.call(this, config);
	},

	/**
	 * Handler for the afterrender event of this panel. Will set a load mask when opening
	 * a url.
	 */
	onAfterRender: function()
	{
		this.showLoadMask();

		var iframe = document.getElementById(this.iframeId);
		var event = (this.tag === 'webview') ? 'contentload' : 'load';

		iframe.addEventListener(event, function(){
			this.hideLoadMask();
			Zarafa.plugins.chat.Notifications.initialize(iframe);
		}.createDelegate(this));
	},

	/**
	 * Handler for the dialog event of WEBVIEW elements. Will handle alert, prompt,
	 * and confirm dialogs
	 * @param  {Event} e The dialog event
	 */
	handleDialogRequests : function(e)
	{
		// Handle alerts
		if ( e.messageType === 'alert' ) {
			window.alert(e.messageText);
		}

		// Handle confirm dialogs
		else if ( e.messageType === 'confirm' ) {
			var confirmation =  window.confirm(e.messageText);

			if ( confirmation ) {
				e.dialog.ok();
			} else {
				e.dialog.cancel();
			}
		}

		// Handle prompts
		else if ( e.messageType === 'prompt' ){
			var wprompt = window.prompt( e.messageText);

			if ( wprompt === null ){
				e.dialog.cancel();
			} else {
				e.dialog.ok(wprompt);
			}
		}

	},

	/**
	 * Handler for the permissionrequest event of WEBVIEW elements. Will handle the request
	 * by its type.
	 * Possible types are media, geolocation, pointerLock, download, loadplugin and fullscreen.
	 * For now we deny geolocation, fullscreen and pointerLock requests.
	 * @param {Event} e The permissionrequest event
	 */
	handlePermissionRequests : function(e)
	{
		e.preventDefault();
		switch (e.permission) {
			// Allow
			case 'download':
			case 'media':
			case 'loadplugin':
				e.request.allow();
			break;
			// Deny
			case 'pointerLock':
			case 'fullscreen':
			case 'geolocation':
				e.request.deny();
			break;
			// also deny all other, not yet known, requests
			default:
				e.request.deny();
			break;

		}
	},

	/**
	 * Handler for the newwindow event of WEBVIEW elements. Will handle new windows, by
	 * opening them externally in the browser.
	 * @param  {Event} e The newwindow event
	 */
	handleNewWindowRequests : function(e)
	{
		e.window.discard();
	},

	/**
	 * If {@link #showLoadMask} is enabled, this function will display
	 * the {@link #loadMask}.
	 * @param {Boolean} errorMask True to show an error mask instead of the loading mask.
	 * @protected
	 */
	showLoadMask : function(errorMask)
	{
		if (this.isLoadMaskShown === true) {
			return;
		}
		if (!this.loadMask) {
			this.loadMask = new Zarafa.common.ui.LoadMask(this.ownerCt.el);
		}

		if (errorMask) {
			this.loadMask.showError();
		} else {
			this.loadMask.show();
			this.isLoadMaskShown = true;
		}
	},

	/**
	 * If {@link #showLoadMask} is enabled, and {@link #showLoadMask} has been
	 * called to display the {@link #loadMask} this function will disable the
	 * loadMask.
	 * @protected
	 */
	hideLoadMask : function()
	{
		if (this.isLoadMaskShown === false) {
			return;
		}

		if (this.loadMask) {
			this.loadMask.hide();
			this.isLoadMaskShown = false;
		}
	}
});

Ext.reg('zarafa.plugins.chat.ui.panel', Zarafa.plugins.chat.ui.Panel);
Ext.namespace('Zarafa.plugins.chat.settings');

/**
 * @class Zarafa.plugins.chat.settings.Category
 * @extends Zarafa.settings.ui.SettingsCategory
 * @xtype zarafa.plugins.chat.settings.category
 */
Zarafa.plugins.chat.settings.Category = Ext.extend(Zarafa.settings.ui.SettingsCategory, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor : function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			title : _('Chat'),
			categoryIndex : 12,
			iconCls : 'k-chat-settings-category',
			items : [{
				xtype : 'zarafa.plugins.chat.settings.generalsettingswidget'
			}]
		});

		Zarafa.plugins.chat.settings.Category.superclass.constructor.call(this, config);
	}
});

Ext.reg('zarafa.plugins.chat.settings.category', Zarafa.plugins.chat.settings.Category);
Ext.namespace('Zarafa.plugins.chat.settings');

/**
 * @class Zarafa.plugins.chat.settings.GeneralSettingsWidget
 * @extends Zarafa.settings.ui.SettingsWidget
 * @xtype zarafa.plugins.chat.settings.generalsettingswidget
 */
Zarafa.plugins.chat.settings.GeneralSettingsWidget = Ext.extend(Zarafa.settings.ui.SettingsWidget, {
	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor : function(config)
	{
		config = config || {};

		Ext.applyIf(config, {
			cls: 'chat-settings-panel zarafa-settings-widget',
			title: _('Chat'),
			items: [{
				xtype: 'checkbox',
				ref: 'autostart',
				hideLabel: true,
				boxLabel: _('Open Chat at start'),
				listeners: {
					check: this.onCheckAutoStart,
					scope: this
				}
			}]
		});

		Zarafa.plugins.chat.settings.GeneralSettingsWidget.superclass.constructor.call(this, config);
	},

	/**
	 * Event handler for the check event of the autostart checkbox.
	 *
	 * @param {Ext.form.Checkbox} checkbox The autostart checkbox
	 * @param {Boolean} checked True if the checkbox is checked, false otherwise
	 */
	onCheckAutoStart : function(checkbox, checked)
	{
		if ( !this.updating ){
			this.updateSettings(this.settingsModel);
		}
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel widget panel}
	 * to load the latest version of the settings from the
	 * {@link Zarafa.settings.SettingsModel} into the UI of this category.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
	 */
	update : function(settingsModel)
	{
		this.updating = true;
		this.settingsModel = settingsModel;

		var autostart = settingsModel.get('zarafa/v1/plugins/chat/autostart')===true;
		this.autostart.setValue(autostart);

		this.updating = false;
	},

	/**
	 * Called by the {@link Zarafa.settings.ui.SettingsCategoryWidgetPanel widget panel}
	 * to update the settings from the UI into the {@link Zarafa.settings.SettingsModel settings model}.
	 * @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
	 */
	updateSettings : function(settingsModel)
	{
		settingsModel.set('zarafa/v1/plugins/chat/autostart', this.autostart.getValue());
	}
});

Ext.reg('zarafa.plugins.chat.settings.generalsettingswidget', Zarafa.plugins.chat.settings.GeneralSettingsWidget);
Ext.namespace('Zarafa.plugins.chat');

/**
 * This object is used to relay browser notifications from the Chat iframe
 * to the grommunio Web window. Chat need to have the Notifymatters plugin installed
 * and activated.
 * @type {Object}
 */
Zarafa.plugins.chat.Notifications = {
	/**
	 * Reference to the iframe element where Chat is loaded
	 * @type {HTMLElement}
	 */
    iframe: null,

	/**
	 * Timer id of the timeout that is used to enable the notification relaying.
	 * We need to try a couple of times because we don't know when the Chat plugin
	 * is initialized and ready to process our messages.
	 * @type {Number}
	 */
	timer: undefined,

	/**
	 * Set to true when the notification relaying has been enabled.
	 * @type {Boolean}
	 */
	enabled: false,

	/**
	 * Set to true when the post message listener has been added
	 * @type {Boolean}
	 */
	messageHandlerAdded: false,

	/**
	 * Object with references to the notifications that are shown to the user
	 * @type {Object}
	 */
	notifications: {},

	/**
	 * The maximum number of times we will try to enable the relaying.
	 * It could be that the Notifymatters plugin is not installed or activated
	 * in chat.
	 * @type {Number}
	 */
	maxRetry: 100,

	/**
	 * Number of times we have tried to enable the relaying of notifications
	 * @type {Number}
	 */
	retryCount: 0,

	/**
	 * TIme in milliseconds to wait before retrying to enable
	 * @type {Number}
	 */
	retryDelay: 200,

	/**
	 * Initializes the relaying of notifications.
	 * @param  {[type]} iframe [description]
	 * @return {[type]}        [description]
	 */
    initialize : function(iframe) {
		if ( !window.Notification ){
			return;
		}

        this.iframe = iframe;

		if ( !this.messageHandlerAdded ){
        	window.addEventListener('message', this.onMessage.bind(this), false);
			this.messageHandlerAdded = true;
		}

		// When the chat tab has been closed and reopened we must enable the
		// relaying of notifications again.
		this.enabled = false;
		this.retryCount = 0;
        this.enable();
    },

	/**
	 * Enables the relaying of notifications by sending the 'enable' message to the
	 * chat iframe.
	 */
	enable : function() {
		if ( this.enabled ){
			return;
		}

		if ( ++this.retryCount > this.maxRetry ) {
			clearTimeout(this.timer);
			return;
		}

        this.sendMessage('enable', Notification.permission);

		// Set a timeout to retry because the chat plugin might not be ready yet
		// and we have no other way of checking that.
		this.timer = setTimeout(function(){
			this.enable();
		}.bind(this), this.retryDelay);
	},

	/**
	 * Event handler for the 'message' event of the postMessage API.
	 * @param  {Object} event Event object
	 */
    onMessage : function(event) {
        if ( event.source !== this.iframe.contentWindow ) {
			// Only handle messages from the chat iframe
            return;
        }
        if (!event.data || !event.data.type || event.data.type.indexOf('notifymatters.') !== 0 ) {
            // Ignore unknown stuff.
            return;
        }

		switch (event.data.type) {
			case 'notifymatters.ok':
				this.enabled = true;
				clearTimeout(this.timer);
				break;

            case 'notifymatters.notification.requestPermission':
                window.Notification.requestPermission(function(result) {
                    this.sendMessage('ref.resolve', result, event.data.ref);
                }.bind(this));
                break;

            case 'notifymatters.notification.new': {
				event.data.data.options.icon = event.origin + event.data.data.options.icon;
                const notification = new Notification(event.data.data.title, event.data.data.options);
                this.notifications[event.data.data.id] = notification;

                notification.onclick = function() {
					// Activate chat tab
					const plugin = container.getPluginByName('chat');
					if ( plugin ) {
						plugin.openTab();
					}

					window.blur();
					window.focus();
                    this.sendMessage('notification.onclick', null, event.data.data.id);
                }.bind(this);

                notification.onclose = function() {
                    delete this.notifications[event.data.data.id];
                    this.sendMessage('notification.onclose', null, event.data.data.id);
                }.bind(this);
                break;
            }

            case 'notifymatters.notification.close': {
                const notification = this.notifications[event.data.data.id];
                if (!notification) {
                    return;
                }
                notification.close();
                break;
            }

			default:
                console.warn('received unknown notifymatters message type', event.data.type);
                break;
		}
    },

	/**
	 * Wrapper function for posting messages to the chat iframe
	 * @param  {String} msg The message to send
	 * @param  {Any} data The data to send with the message
	 * @param  {Number} ref Notification id for the Notifymatters plugin
	 */
    sendMessage : function(msg, data, ref) {
        const payload = {
            type: 'notifymatters.' + msg,
            data: data,
            ref: Ext.isNumber(ref) ? ref : null,
            notifymatters: '20171130'
        }

        this.iframe.contentWindow.postMessage(payload, this.iframe.src);
    }
};
