Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.BrowserWindowMgr
 * @extends Ext.util.Observable
 *
 * The global Browser-window manager that will keep track of all the available browser windows with
 * a unique key assigned to each of the browser window as its name.
 * @singleton
 */
Zarafa.core.BrowserWindowMgr = Ext.extend(Ext.util.Observable, {
	/**
	 * The list of registered browser window. It contains the list with the window DOM object bound to a
	 * unique key.
	 * @property
	 * @type Ext.util.MixedCollection
	 * @private
	 */
	browserWindows : undefined,

	/**
	 * The key of registered browser window which is active currently.
	 * @property
	 * @type String
	 * @private
	 */
	activeBrowserWindow : undefined,

	/**
	 * The list of registered browser window with the object which contain,
	 * component The constructor of the component which has to be created in the container layer and
	 * config which must be passed to the constructor when creating the component.
	 * @property
	 * @type Ext.util.MixedCollection
	 * @private
	 */
	browserWindowComponents : undefined,

	/**
	 * Denotes that the multiple popups are blocked by the browser or not.
	 * @property
	 * @type Boolean
	 * @private
	 */
	isPopupsBlocked : false,

	/**
	 * An array which contain, component The constructor of the component which has to be created in the container layer and
	 * config which must be passed to the constructor when creating the component,
	 * for the popup window which was blocked by the browser.
	 * @property
	 * @type Array
	 * @private
	 */
	blockedPopupsContent : [],

	/**
	 * @constructor
	 */
	constructor : function()
	{
		this.browserWindows = new Ext.util.MixedCollection();

		// It is required to manage multiple browser windows when first separate window is created by user.
		// So, we must have to register the main webapp-browser-window to the BrowserWindowMgr.
		window.name = 'mainBrowserWindow';
		this.register(window);
		window.addEventListener("focus", function(event) {
			Zarafa.core.BrowserWindowMgr.setActive('mainBrowserWindow');
		}, false);
		this.browserWindowComponents = new Ext.util.MixedCollection();

		this.addEvents('separatewindowresize');
	},

	/**
	 * Register component and config which will use to re create separate window ui on reload or refresh
	 * @param {String} uniqueWindowName unique name of browser window
	 * @param {Function} component The constructor of the component which has to be created in the container layer.
	 * @param {Object} config The configuration object which must be
	 */
	initComponent: function (uniqueWindowName, component, config)
	{
		this.browserWindowComponents.add(uniqueWindowName, {
			component: component,
			config: config
		});
	},


	/**
	 * Helper function which registers necessary events. This happens mostly when anew browser window
	 * gets loaded for the first time.
	 *
	 * @param {Object} browserWindowObject The newly created window object which must be registered.
	 * @param {Ext.Component} componentInstance component which is required to be rendered into the separate browser window.
	 * @param {Ext.Container} mainContainer The container which is the parent most container of separate window.
	 * @private
	 */
	initEvents : function(browserWindowObject, componentInstance, mainContainer)
	{
		// Initialize ext doc classes on separate window.
		this.initExtCss(browserWindowObject);

		componentInstance.on('close', this.onSeparateWindowClose.createDelegate(this, [ browserWindowObject ]));
		componentInstance.on('userupdaterecord', this.onComponentUserupdateRecord.createDelegate(this, [ componentInstance ], true));

		Ext.EventManager.on(
			browserWindowObject,
			"resize",
			this.onSeparateWindowResize.createDelegate(this, [ browserWindowObject, mainContainer ]),
			browserWindowObject
		);


		// Initialize events which are use to handle drag and drop in separate window.
		Ext.dd.DragDropMgr.initEvents(browserWindowObject);

		browserWindowObject.addEventListener("focus", this.onSeparateWindowFocus.createDelegate(this, [ browserWindowObject.name ]));
		browserWindowObject.addEventListener("unload", this.onSeparateWindowUnload.createDelegate(this, [ browserWindowObject, componentInstance, mainContainer ]));

		// Disable contextmenu globaly in the separate browser window.
		Ext.getBody().on('contextmenu', this.onBodyContextMenu, this);

		// Check component instance has before unload event handler if yes then
		// Register event handler for separate window onbeforeunload event
		// which is use to show a confirmation dialog warning if window record has any unsaved changes
		if (Ext.isFunction(componentInstance.onBeforeUnload)) {
			browserWindowObject.onbeforeunload = componentInstance.onBeforeUnload.createDelegate(componentInstance);
		}
	},

	/**
	 * Event handler for the {@link Zarafa.core.plugins.RecordComponentPlugin#userupdaterecord userupdaterecord} event
	 * on the {@link #field}. This will relay the value of {@link Zarafa.core.plugins.RecordComponentPlugin#isChangedByUser} config option.
	 * to the newly created content panel into popout window.
	 * It is required to persist the user change state into RecordComponentPlugin for this particular field.
	 * @param {Ext.Component} field The component which fired the event
	 * @param {Zarafa.core.data.MAPIRecord} record The record which was updated
	 * @param {Boolean} isChangedByUser Indicates if the record has been changed by the user since it has been loaded.
	 * @param {Ext.Component} componentInstance component which gets rendered into the separate browser window.
	 * @private
	 */
	onComponentUserupdateRecord : function(field, record, isChangedByUser, componentInstance)
	{
		// Check the record has unsaved user changes before popout
		// If true then update the record component plugin value
		if (componentInstance.isRecordChangeByUser) {
			field.recordComponentPlugin.isChangedByUser = componentInstance.isRecordChangeByUser;
		}

		// If record get saved then add record id
		if (!record.phantom) {
			var windowName = this.getOwnerWindow(componentInstance).name;
			var windowObject = this.browserWindows.get(windowName);
			if (Ext.isEmpty(windowObject.entryid)) {
				windowObject.entryid = record.get("entryid");
			}
		}
	},

	/**
	 * Register a newly created browser-window object with the {@link Zarafa.core.BrowserWindowMgr BrowserWindowMgr}.
	 * @param {Object} browserWindowObject The newly created window object which must be registered.
	 * @param {Function} component The constructor of the component which has to be created in the container layer.
	 * @param {Object} config The configuration object which must be

	 */
	register : function(browserWindowObject,component,config)
	{
		var uniqueWindowName = browserWindowObject.name;
		if (uniqueWindowName != 'mainBrowserWindow') {
			this.initComponent(uniqueWindowName, component, config);

			// add entryid along with the window object to identify browser window of particular record.
			var entryid = config.record.get('entryid');
			if (!Ext.isEmpty(entryid)) {
				browserWindowObject.entryid = entryid;
			}
		}
		this.browserWindows.add(uniqueWindowName, browserWindowObject);
		this.setActive(uniqueWindowName);
	},

	/**
	 * De-register an already registered browser-window object from the {@link Zarafa.core.BrowserWindowMgr BrowserWindowMgr}.
	 * @param {String} uniqueWindowName The unique name of the browser window.
	 */
	unRegister : function(uniqueWindowName)
	{
		this.browserWindows.removeKey(uniqueWindowName);
		Ext.MessageBox.removeBrowserWindowMessageBox(uniqueWindowName);
		// As the window which was closed is also the active one, we are not able to decide which window should be considered
		// as an active one. So assign 'undefined' to the activeBrowserWindow which will later on replaced while other
		// available window receive focus.
		if(this.activeBrowserWindow === uniqueWindowName){
			this.activeBrowserWindow = undefined;
		}
	},

	/**
	 * A helper function which creates main container into the separate window and load the required
	 * component within. Some necessary events needs to be registered as well.
	 * @param {Object} separateWindowInstance Browser window object.
	 * @protected
	 */
	createUI : function(separateWindowInstance)
	{
		// Enable tooltips
		Ext.QuickTips.init();

		var separateWindowId = separateWindowInstance.name;

		//The constructor of the component which has to be created in the container layer.
		var browserWindowComponent;

		if (this.browserWindowComponents.containsKey(separateWindowId)) {
			browserWindowComponent = this.browserWindowComponents.get(separateWindowId);
		} else if (!Ext.isEmpty(this.blockedPopupsContent)) {
			// No inner components found for this particular popup window.
			// This is the situation where some popups were blocked by browser and
			// user manually allows to load the same.
			var blockedPopup = this.blockedPopupsContent.pop();
			this.register(separateWindowInstance, blockedPopup.component, blockedPopup.config);
			browserWindowComponent = this.browserWindowComponents.get(separateWindowId);
			this.isPopupsBlocked = false;
		}

		var component = browserWindowComponent.component;

		//The configuration object
		var config = {
			plugins : [ 'zarafa.contentlayerplugin' ],
			confirmClose : false
		};
		config = Ext.applyIf(config,browserWindowComponent.config);

		// Create instance of the component which is required to be rendered into the separate browser window.
		var componentInstance = new component(config);

		var mainContainer = Ext.create({
			xtype : 'panel',
			height : separateWindowInstance.innerHeight,
			width : separateWindowInstance.innerWidth,
			renderTo : Ext.get(separateWindowInstance.document.body),
			layout : 'fit',
			items : [componentInstance]
		});

		// Check the record has unsaved user changes before popout

		// If true then update the record component plugin value
		if(config.isRecordChangeByUser){
			componentInstance.recordComponentPlugin.isChangedByUser = config.isRecordChangeByUser;
		}
		this.initEvents(separateWindowInstance, componentInstance, mainContainer);

		// We need to use some delay due to the behavioral difference between various browsers.
		// Let say we are creating 5 popups, Chrome blocks the last popups, and FF blocks the first one.
		// When first popup gets rendered and following popups will be blocked than there is no way to
		// listen to any event of already-rendered popup and display warning message in already rendered popup.
		var task = new Ext.util.DelayedTask(Zarafa.core.BrowserWindowMgr.displayBlockedPopupWarning.createDelegate(this, [separateWindowInstance], this));
		task.delay(200);
	},

	/**

	/**
	 * Provides browser window object mapped with the unique name currently assigned in {@link #activeBrowserWindow}.
	 * @return {Object} Browser window object
	 */
	getActive: function()
	{
		// Some time while child window not available any more and main window focus not set
		// at that time activeBrowser window will not available.
		// So handle this type of situation by returning main browser window while active browser window not available

		return this.browserWindows.get(this.activeBrowserWindow) || this.browserWindows.get('mainBrowserWindow');
	},

	/**
	 * Assign the provided window name to {@link #activeBrowserWindow}.
	 * @param {String} uniqueWindowName The unique name of the browser window.
	 */
	setActive : function(uniqueWindowName)
	{
		this.activeBrowserWindow = uniqueWindowName;

		// Activate respective QuickTip which is associated with the active browser window.
		if(Ext.QuickTips && Ext.QuickTips.tip) {
			var activeQuickTip = Ext.QuickTips.browserQuickTips.get(uniqueWindowName);
			if(Ext.isDefined(activeQuickTip)) {
				Ext.QuickTips.tip = activeQuickTip;
			}
		}

		// Activate respective MessageBox which is associated with the active browser window.
		Ext.MessageBox.setActiveWindowMessageBox(uniqueWindowName);
	},

	/**
	 * Determines if the webapp-main-window is currently active or not by checking that the value currently
	 * assigned into {@link #activeBrowserWindow} is 'mainBrowserWindow'.
	 * @return {Boolean} True if the active window is webapp-main-window, false otherwise.
	 */
	isMainWindowActive : function()
	{
		return this.activeBrowserWindow === 'mainBrowserWindow';
	},

	/**
	 * Helper function which allows to know which browser window object is the owner of any particular component
	 * passed as argument.
	 * @param {Ext.Component} component of which we want to get the owning browser window.
	 *
	 * @return {Boolean} true if the owner is main webapp window, false otherwise
	 */
	isOwnedByMainWindow : function(component)
	{
		var ownerWindow = this.getOwnerWindow(component);
		return ownerWindow ? ownerWindow.name === 'mainBrowserWindow' : false;
	},

	/**
	 * Helper function which returned the owning window of the element passed as argument.
	 * @param {Ext.Component/Ext.Element} component/element of which we want to get the owning browser window.
	 *
	 * @return {Object} Browser window object
	 * @private
	 */
	getOwnerWindow : function(component)
	{
		if(!Ext.isDefined(component)){
			return undefined;
		}

		// Incase we receive Ext.Component as parameter then we have to get underlying Ext.Element
		if(Ext.isFunction(component.getEl)){
			component = component.getEl();
		}

		var componentDom = component.dom ? component.dom : component;
		var ownerDocument = componentDom ? componentDom.ownerDocument : undefined;
		var defaultView = ownerDocument ? ownerDocument.defaultView : undefined;
		return defaultView ? defaultView :  this.browserWindows.get('mainBrowserWindow');
	},

	/**
	 * Event handler which is raised when browser window receives user focus.
	 * This will make respective browser window active into {@link Zarafa.core.BrowserWindowMgr BrowserWindowMgr}.
	 *
	 * @param {Object} browserWindowId The newly created window object id which must be registered.
	 * @private
	 */
	onSeparateWindowFocus : function(browserWindowId)
	{
		this.setActive(browserWindowId);
	},

	/**
	 * Event handler which is raised when browser window gets closed.
	 * This will de-register the closed component instance from {@link Zarafa.core.data.ContentPanelMgr ContentPanelMgr}.
	 * This will remove all the child elements of the parent most container of separate window.
	 * This will de-register the closed window instance from {@link Zarafa.core.BrowserWindowMgr BrowserWindowMgr}.
	 *
	 * @param {Object} browserWindowObject The newly created window object which must be registered.
	 * @param {Ext.Component} componentInstance component which is required to be rendered into the separate browser window.
	 * @param {Ext.Container} mainContainer The container which is the parent most container of separate window.
	 * @private
	 */
	onSeparateWindowUnload : function(browserWindowObject, componentInstance, mainContainer)
	{
		Ext.defer(this.onWindowRefreshOrClose, 50, this, [browserWindowObject,componentInstance,browserWindowObject.name]);
		mainContainer.destroy();
	},

	/**
	 * Function which handles the situation where window gets refreshed or closed
	 * if it was refresh then create record instance based on original record and update the browser window component config.
	 * And if window was closed then de-register the closed window component from {@link #browserWindowComponents}.
	 * @param {Object} win The browser window which gets refresh or closed.
	 * @param {Ext.Component} componentInstance component of the separate browser window.
	 * @param {Object} browserWindowId The newly created window object id which must be registered.
	 */
	onWindowRefreshOrClose: function (win, componentInstance, browserWindowId)
	{
		var isClose = false,
			oldRecord = componentInstance.record;
		try {
			if (!win || !win.innerWidth) {
				isClose = true;
			} else {
				var config = this.browserWindowComponents.get(win.name).config,
					model = container.getCurrentContext().getModel(),
					newRecord;

				if (Ext.isDefined(config.recordComponentPluginConfig) && Ext.isDefined(config.recordComponentPluginConfig.useShadowStore)) {
					config.recordComponentPluginConfig.useShadowStore = true;
				}

				// on window refresh we have to discard all unsaved user changes
				// If record is not phantom then get new record from respective store using oldRecord id properties.
				if (!oldRecord.phantom) {
					var recordConfig = {
						store_entryid: oldRecord.get('store_entryid'),
						parent_entryid: oldRecord.get('parent_entryid'),
						entryid: oldRecord.get('entryid')
					};

					newRecord = Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.Note', recordConfig, oldRecord.get('entryid'));
				} else {
					// if record is phantom then we have to discard this record and
					// create new record using respective context model
					newRecord = model.createRecord();
				}
				if (Ext.isDefined(config.isRecordChangeByUser)) {
					config.isRecordChangeByUser = undefined;
				}
				win.onbeforeunload = undefined;

				// Update the new record in respective browser window config
				config.record = newRecord;
			}
		} catch (e) {
			// Catch an Exception. Firefox will throw one when we
			// try to access property of the window that does not
			// exist anymore.
			isClose = true;
		}
		container.getShadowStore().remove(oldRecord, true);
		if (isClose) {
			this.browserWindowComponents.removeKey(browserWindowId);
			this.unRegister(browserWindowId);
		}
	},

	/**

	/**
	 * Event handler which is raised after the {@link Zarafa.core.ui.ContentPanel contentPanel} has been
	 * closed. This will de-register the closed window instance from {@link Zarafa.core.BrowserWindowMgr BrowserWindowMgr}.
	 *
	 * @private
	 */
	onSeparateWindowClose : function(browserWindowObject)
	{
		browserWindowObject.close();
	},

	/**
	 * Event handler which is raised when the browser window gets resized.
	 * This will register a {@link Ext.util.DelayedTask DelayedTask} and set the delay of 100ms.
	 * Delay is required to avoid executing the handler frequently, if event fires multiple time within the specified time-frame
	 * then the handler will gets executed only once for the last event.
	 *
	 * @param {Object} browserWindowObject The browser window which gets resized.
	 * @param {Ext.Container} mainContainer The container which needs to be resized according to the size of browser window.
	 * @private
	 */
	onSeparateWindowResize : function(browserWindowObject, mainContainer)
	{
		var resizeTask = new Ext.util.DelayedTask(
			this.doSeparateWindowResize,
			this,
			[browserWindowObject, mainContainer]
		);

		resizeTask.delay(100);
	},

	/**
	 * A {@link Ext.util.DelayedTask DelayedTask} which will set size of main container according to the browser window
	 * when it gets resized.
	 *
	 * @param {Object} browserWindowObject The browser window which gets resized.
	 * @param {Ext.Container} mainContainer The container which needs to be resized according to the size of browser window.
	 * @private
	 */
	doSeparateWindowResize : function(browserWindowObject, mainContainer)
	{
		var width = browserWindowObject.innerWidth || Ext.lib.Dom.getViewWidth();
		var height = browserWindowObject.innerHeight || Ext.lib.Dom.getViewHeight();
		mainContainer.setSize(width, height);

		this.fireEvent('separatewindowresize', browserWindowObject, width. height);
	},

	/**
	 * Event handler which is fired when the {@link Ext#getBody <body>} elements fires
	 * the 'contextmenu' event. If the element which fired the event doesn't have the
	 * 'zarafa-contextmenu-enabled' class 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)
	{
		// Disable contextmenu globally in the separate browser window,
		// only when the 'zarafa-contextmenu-enabled'
		// CSS class is applied on the element will we allow the contextmenu to be shown.
		if (!Ext.get(el).hasClass('zarafa-contextmenu-enabled')) {
			event.preventDefault();
		}
	},

	/**
	 * Function which is used to close all browser windows except main window
	 */
	closeAllBrowserWindow : function()
	{
		var browserWindows = Zarafa.core.BrowserWindowMgr.browserWindows.items;
		Ext.each(browserWindows,function(windowObject){
			if(windowObject.name !== 'mainBrowserWindow'){
				windowObject.close();
			}
		});
	},

	/**
	 * Function which is used to initialize ext doc classes on separate window
	 * Ext has some separate doc classes for all browsers,and
	 * must be initialize those classes for newly created window.
	 * @param browserWindowObject The newly created window object which must be registered.
	 * @returns {boolean} true if all doc classes successfully apply on new window, false otherwise
	 */
	initExtCss: function (browserWindowObject) {
		// find the body element
		var body = browserWindowObject.document.body || browserWindowObject.document.getElementsByTagName('body')[0];
		if (!body) {
			return false;
		}

		Ext.fly(body.parentElement).addClass('x-viewport');

		var cls = [];

		if (Ext.isIE) {
			// Only treat IE9 and less like IE in the css
			if (!Ext.isIE10p) {
				cls.push('ext-ie');
			}
			if (Ext.isIE6) {
				cls.push('ext-ie6');
			} else if (Ext.isIE7) {
				cls.push('ext-ie7', 'ext-ie7m');
			} else if (Ext.isIE8) {
				cls.push('ext-ie8', 'ext-ie8m');
			} else if (Ext.isIE9) {
				cls.push('ext-ie9', 'ext-ie9m');
			} else if (Ext.isIE10) {
				cls.push('ext-ie10');
			}
		}

		if (Ext.isGecko) {
			if (Ext.isGecko2) {
				cls.push('ext-gecko2');
			} else {
				cls.push('ext-gecko3');
			}
		}

		if (Ext.isOpera) {
			cls.push('ext-opera');
		}

		if (Ext.isWebKit) {
			cls.push('ext-webkit');
		}

		if (Ext.isSafari) {
			cls.push("ext-safari " + (Ext.isSafari2 ? 'ext-safari2' : (Ext.isSafari3 ? 'ext-safari3' : 'ext-safari4')));
		} else if (Ext.isChrome) {
			cls.push("ext-chrome");
		}

		if (Ext.isMac) {
			cls.push("ext-mac");
		}
		if (Ext.isLinux) {
			cls.push("ext-linux");
		}

		// add to the parent to allow for selectors like ".ext-strict .ext-ie"
		if (Ext.isStrict || Ext.isBorderBox) {
			var p = body.parentNode;
			if (p) {
				if (!Ext.isStrict) {
					Ext.fly(p, '_internal').addClass('x-quirks');
					if (Ext.isIE9m && !Ext.isStrict) {
						Ext.isIEQuirks = true;
					}
				}
				Ext.fly(p, '_internal').addClass(((Ext.isStrict && Ext.isIE ) || (!Ext.enableForcedBoxModel && !Ext.isIE)) ? ' ext-strict' : ' ext-border-box');
			}
		}
		// Forced border box model class applied to all elements. Bypassing javascript based box model adjustments
		// in favor of css.  This is for non-IE browsers.
		if (Ext.enableForcedBoxModel && !Ext.isIE) {
			Ext.isForcedBorderBox = true;
			cls.push("ext-forced-border-box");
		}

		Ext.fly(body, '_internal').addClass(cls);
		return true;
	},

	/**
	 * Function which is used to display warning message while browser blocks popups
	 */
	displayBlockedPopupWarning : function() {
		if(Zarafa.core.BrowserWindowMgr.isPopupsBlocked) {
			// We completed with the internal UI of single-allowed popup, Inform user that popup is blocked
			Ext.MessageBox.show({
				title : _("Open in new browser window"),
				msg : _("Your browser seems to have blocked one or more pop-ups. Please change your browser's settings to always allow pop-ups from WebApp."),
				buttons: Ext.Msg.OK,
				icon: Ext.MessageBox.WARNING
			});

			Zarafa.core.BrowserWindowMgr.isPopupsBlocked = false;
		}
	},

	/**
	 * Helper function which will bring the main webapp window to front
	 * @param {Ext.Component/Ext.Element} component/element of which will use to get the owning browser window.
	 */
	switchFocusToMainWindow: function (component)
	{
		var activeWindow = this.getActive();

		// if component belongs to one of the currently opened popout windows then get owner window of component
		if (Ext.isDefined(component) && !this.isOwnedByMainWindow(component)) {
			activeWindow = this.getOwnerWindow(component);
		}

		activeWindow.setFocusOnMainWindow();
	},

	/**
	 * Function which is use to find browser window of given record.
	 * @param {Zarafa.core.data.MAPIRecord} record The mapi record
	 * @return {Object} Browser window object
	 */
	getOpenedWindow: function (record)
	{
		return this.browserWindows.find(function (browserWindow) {
			return Zarafa.core.EntryId.compareEntryIds(browserWindow.entryid, record.get('entryid'));
		});
	}
});

Zarafa.core.BrowserWindowMgr = new Zarafa.core.BrowserWindowMgr();