Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.MultiFolderContextModel
 * @extends Zarafa.core.ContextModel
 *
 * Extension to the context model to support maintaining
 * multiple folders which can be grouped together in a sort
 * of tabbed view.
 */
Zarafa.core.MultiFolderContextModel = Ext.extend(Zarafa.core.ContextModel, {
	/**
	 * @cfg {Object[]} colorScheme The color defintion objects which is applied within
	 * the context. Each folder will be assigned with an entry from this array to
	 * define the colors applied to it.
	 */
	colorScheme : undefined,

	/**
	 * A flag that denotes if colors are being assigned, thereby letting
	 * the setColor function know that it shouldn't fire the 'colormapchanged'
	 * event.
	 * @property
	 * @type Boolean
	 * @private
	 */
	assigningColors : false,

	/**
	 * @cfg {Boolean} default_merge_state True if the contents of each folder is currently merged into a single view.
	 * This is updated through {@link #setMergeState} and when this field changes,
	 * the {@link #foldermergestatechanged} event will be fired.
	 */
	default_merge_state : false,

	/**
	 * The currently active grouping of all active {@link #folders}.
	 * Each key inside this object refers to another object containting the
	 * 'folders' properties which is the list of all entryids which are
	 * grouped together into a single view. The 'active' property denotes which
	 * folder is currently the active folder.
	 * When this context is {@link #stateful stateful}, this option will be
	 * saved in the settings.
	 * @property
	 * @type Object
	 * @private
	 */
	groupings : undefined,

	/**
	 * Mapping of folder entryids to color schemes
	 * @property
	 * @type Object
	 */
	colorMap : undefined,

	/**
	 * The currently active group out of all groupings in this context model
	 * Outlook has a single selected folder at any time, regardless of how folders are grouped
	 * This property is used to index into the active group
	 * When this context is {@link #stateful stateful}, this option will be
	 * saved in the settings.
	 * @property
	 * @type String
	 * @private
	 */
	active_group : undefined,

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

		this.addEvents(
			/**
			 * @event foldermergestatechanged
			 * Fires when the contents of the selected folders must be merged into a single view
			 * @param {Zarafa.core.ContextModel} model this context model.
			 * @param {Boolean} mergeState The merge state, true to merge all folders into
			 * a single view.
			 */
			'foldermergestatechanged',
			/**
			 * @event foldergroupingchanged
			 * Fires when the groupings for the selected folders has been changed.
			 * @param {Zarafa.core.ContextModel} model this context model.
			 * @param {Object} groupings The groupings object
			 * @param {String} active The active group
			 */
			'foldergroupingchanged',
			/**
			 * @event colormapchanged
			 * Fires when a new color schema for a folder has been set.
			 * @param {Zarafa.core.ContextModel} model this context model.
			 * @param {Object} colorMap The new colorMap
			 */
			'colormapchanged'
		);

		// Initialize groupings
		this.groupings = {};
		this.colorMap = {};

		Zarafa.core.MultiFolderContextModel.superclass.constructor.call(this, config);

		var hierarchyStore = container.getHierarchyStore();
		hierarchyStore.on('addFolder', this.onHierarchyAddFolder, this);
	},

	/**
	 * Called during the {@link Zarafa.core.Context#enable enabling} of the {@link Zarafa.core.Context context}.
	 * Secondly it will {@link #setFolders set the} {@link #folders folder} to this object to {@link #load} the {@link #store}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder MAPI folder to show.
	 * @param {Boolean} suspended True to enable the ContextModel {@link #suspendLoading suspended}
	 */
	enable : function(folder, suspended)
	{
		Zarafa.core.MultiFolderContextModel.superclass.enable.apply(this, arguments);

		this.setMergeState(this.getCurrentMergeState(), true);
	},

	/**
	 * Set the mergeState to force eiher the UI components to merge or separate
	 * all folders currently in the view. If the lock argument is not given, or
	 * is currently  This will only act upon the currently open folders in the view.
	 * @param {Boolean} mergeState True to merge all folders into a single view
	 * @param {Boolean} init (optional) True when this function is called during initialization
	 * and it should force the change of the data mode.
	 */
	setMergeState : function(mergeState, init)
	{
		if (init === true || this.default_merge_state !== mergeState) {
			this.default_merge_state = mergeState;

			this.fireEvent('foldermergestatechanged', this, this.default_merge_state);
		}
	},

	/**
	 * Get the current merge state of all opened folders
	 * @return {Boolean} The current merge state
	 */
	getCurrentMergeState : function()
	{
		return this.default_merge_state;
	},

	/**
	 * Event handler which is executed right before the {@link #folderchange}
	 * event is fired. This allows subclasses to update the folders.
	 *
	 * @param {Zarafa.core.ContextModel} model The model which fired the event.
	 * @param {Array} folders selected folders as an array of {@link Zarafa.hierarchy.data.MAPIFolderRecord Folder} objects.
	 * @private
	 */
	onFolderChange : function(model, folders)
	{
		Zarafa.core.MultiFolderContextModel.superclass.onFolderChange.apply(this, arguments);

		this.sortFolders();
		this.assignColors();
		this.applyGrouping();
	},

	/**
	 * Overriden in order to assign colors to the loaded folders
	 *
 	 * Sets {@link #defaultFolder default folder} for the particular {@link Zarafa.core.Context context}.
	 * This will help while opening new item dialog from other contexts
	 * e.g. Create new Contact from Inbox, at this moment we need {@link #defaultFolder} to create the item.
	 *
	 * When the {@link Zarafa.core.Context context} was opened without any folders,
	 * this also means we can now {@link #addFolder load} the {@link #defaultFolder}.
	 *
	 * @param {Zarafa.core.hierarchyStore} store that holds hierarchy data.
	 * @private
	 * @override
	 */
	onHierarchyLoad : function(hierarchyStore)
	{
		// only continue when hierarchyStore has data
		if (hierarchyStore.getCount() === 0) {
			return;
		}

		Zarafa.core.MultiFolderContextModel.superclass.onHierarchyLoad.apply(this, arguments);

		this.sortFolders();
		this.assignColors();
	},

	/**
	 * Handler for the 'addfolder' event of the {@link Zarafa.hierarchy.data.HierarchyStore HierarchyStore}
	 * Will add color schemes to the calendars.
	 */
	onHierarchyAddFolder : function()
	{
		this.assignColors();
	},

	/**
	 * Returns the default {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} which is
	 * used within the current selection of folders. If multiple folders are grouped together then
	 * this function will return the active group folder. If this ContextModel is not enabled then
	 * this will always return default calendar folder from default store.
	 * @return {Zarafa.hierarchy.data.MAPIFolderRecord} The default folder
	 */
	getDefaultFolder : function()
	{
		if(this.enabled) {
			var activeGroup = this.getActiveGroup();

			if(activeGroup && this.groupings[activeGroup]) {
				var folder = this.getFolder(this.groupings[activeGroup].active);
				if(!Ext.isEmpty(folder)) {
					return folder;
				}
			}
		}

		// fallback to parent
		return Zarafa.core.MultiFolderContextModel.superclass.getDefaultFolder.apply(this, arguments);
	},

	/**
	 * Updates a group in the {@link #groupings} to mark it as active.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder which is activated
	 * @param {String} groupId The group to which the folder belongs
	 */
	activateFolderInGroup : function(folder, groupId)
	{
		this.active_group = groupId;
		this.groupings[groupId].active = folder.get('entryid');

		this.fireEvent('foldergroupingchanged', this, this.groupings, this.active_group);
	},

	/**
	 * Moves a {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} from an existing
	 * group to a new Group in the {@link #groupings}.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to move to the other group
	 * @param {String} newGroup The group to which the folder should be moved
	 * @param {String} oldGroup The group from where the folder is moved
	 * @return {String} The groupID to which the folder was added.
	 */
	mergeFolderToGroup : function(folder, newGroup, oldGroup)
	{
		var entryid = folder.get('entryid');
		if (this.groupings[oldGroup]) {
			this.groupings[oldGroup].folders.remove(entryid);
			if (Ext.isEmpty(this.groupings[oldGroup].folders)) {
				delete this.groupings[oldGroup];
			} else if (this.groupings[oldGroup].active == entryid) {
				this.groupings[oldGroup].active = this.groupings[oldGroup].folders[0];
			}
		}
		this.groupings[newGroup].folders.push(entryid);
		this.groupings[newGroup].active = entryid;
		this.active_group = newGroup;

		this.fireEvent('foldergroupingchanged', this, this.groupings, this.active_group);
		return newGroup;
	},

	/**
	 * Move a {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} out of the current group and
	 * create a new group into which the folder should be moved.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to move to a new group
	 * @param {String} oldGroup The group from wehre the folder is moved
	 * @return {String} The groupID to which the folder was added.
	 */
	separateFolderFromGroup : function(folder, oldGroup)
	{
		var groupId = Ext.id(null, 'group-');
		var entryid = folder.get('entryid');
		if (this.groupings[oldGroup]) {
			this.groupings[oldGroup].folders.remove(entryid);
			if (Ext.isEmpty(this.groupings[oldGroup].folders)) {
				delete this.groupings[oldGroup];
			} else if (this.groupings[oldGroup].active == entryid) {
				this.groupings[oldGroup].active = this.groupings[oldGroup].folders[0];
			}
		}
		this.groupings[groupId] = { folders : [ entryid ], active : entryid };
		this.active_group = groupId;

		this.fireEvent('foldergroupingchanged', this, this.groupings, this.active_group);
		return groupId;
	},

	/**
	 * Remove a {@link Zarafa.hierarchy.data.MAPIFolderRecord folder} out of the current group.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to move to a new group
	 * @param {String} oldGroup The group from wehre the folder is moved
	 */
	removeFolderFromGroup : function(folder, oldGroup)
	{
		var entryid = folder.get('entryid');
		if (this.groupings[oldGroup]) {
			this.groupings[oldGroup].folders.remove(entryid);
			if (Ext.isEmpty(this.groupings[oldGroup].folders)) {
				delete this.groupings[oldGroup];
			} else if (this.groupings[oldGroup].active == entryid) {
				this.groupings[oldGroup].active = this.groupings[oldGroup].folders[0];
			}
		}
		this.fireEvent('foldergroupingchanged', this, this.groupings, this.active_group);
	},

	/**
	 * Add an item to a new Group to the {@link #groupings}
	 * @param {String} entryid The entryid to add to a new group
	 * @private
	 */
	addItemToGroup : function(entryid)
	{
		if (this.default_merge_state) {
			// attempt to merge to an existing group
			var keys = Object.keys(this.groupings);
			if (Ext.isEmpty(keys)) {
				// if no other groups - create a new group and set it in groupings
				this.active_group = Ext.id(null, 'group-');
				this.groupings[this.active_group] = { folders : [ entryid ], active : entryid };
			} else {
				this.active_group = keys[0];
				this.groupings[keys[0]].folders.push(entryid);
			}
		} else {
			// create a new group and add it to groupings
			this.active_group = Ext.id(null, 'group-');
			this.groupings[this.active_group] = { folders : [ entryid ], active : entryid };
		}
	},

	/**
	 * Remove an item from the group (and delete the group if empty) in {@link #groupings}.
	 * @param {String} entryid THe entryid to remove from the group
	 * @private
	 */
	removeItemFromGroup : function(entryid)
	{
		Ext.iterate(this.groupings, function(key, group) {
			if(Ext.isEmpty(group.folders)) {
				// continue loop for other groups
				return true;
			}

			var index = group.folders.indexOf(entryid);

			if (index > -1) {
				group.folders.splice(index, 1);

				if (Ext.isEmpty(group.folders)) {
					delete this.groupings[key];

					// If we are removing the currently active group,
					if (this.active_group == key) {
						var keys = Object.keys(this.groupings);
						// then set the last group available as active,
						// or undefined if there are no groups left
						this.active_group = keys.pop();
					}
				}

				// break looping
				return false;
			}
		}, this);
	},

	/**
	 * Update the {@link #groupings} based by removing the folders in the
	 * {@link #groupings} which are no longer listed in {@link #folders}
	 * and add folders to the {@link #groupings} if they were not yet assigned
	 * to a group.
	 * @private
	 */
	applyGrouping : function()
	{
		var entryids = [];
		for (var i = 0, len = this.folders.length; i < len; i++) {
			entryids.push(this.folders[i].get('entryid'));
		}

		var grouped = [];
		Ext.iterate(this.groupings, function(key, group) {
			grouped = grouped.concat(group.folders);
		}, this);

		for (i = 0, len = grouped.length; i < len; i++) {
			var entryid = grouped[i];
			var index = entryids.indexOf(entryid);

			if (index < 0) {
				// The grouped entryid was not found in the folderslist,
				// it has been deleted so clean the group.
				this.removeItemFromGroup(entryid);
			} else {
				// The grouped entryid was found in the folderslist,
				// remove it from the entryids list.
				entryids.remove(entryid);
			}
		}

		// All items remaining in the entryids list, are the new
		// folders which haven't been assigned to a group yet.
		for (i = 0, len = entryids.length; i < len; i++) {
			this.addItemToGroup(entryids[i]);
		}
		this.fireEvent('foldergroupingchanged', this, this.groupings, this.active_group);
	},

	/**
	 * Obtain the currently active {@link #groupings}
	 * @return {Object} The folder groupings.
	 */
	getGroupings : function()
	{
		return this.groupings;
	},

	/**
	 * Sorts the folders in the selected folder list so that they are
	 * in the same order as they appear in the folder hierarchy.
	 * @private
	 */
	sortFolders : function()
	{
		// If one folder is selected, sorting doesn't make sense,
		// so only sort when multiple folders are selected.
		if (this.folders.length > 1) {
			this.folders = container.getHierarchyStore().getSortedFolders(this.hasFolder, this);
		}
	},

	/**
	 * Loop through all folders, assigning a color for each folder,
	 * if we don't have sufficient colors, we have to start again from
	 * the start (This obviously means that some colors will appear twice).
	 * A color scheme is assigned for each folder entry id
	 * Note: This functionality has changed after colors became persistent
	 * Now assigning colors will only be done once for existing users that had
	 * calendars open. And it will assign a color for the default calendar
	 * folder for every new user.
	 * @private
	 */
	assignColors : function()
	{
		this.assigningColors = true;
		for (var i = 0, len = this.folders.length; i < len; i++) {
			// check if folder is already in mapping and add it if it isn't
			this.getColorScheme(this.folders[i].get('entryid'));
		}
		this.assigningColors = false;
	},

	/**
	 * Obtain color scheme for given folder entryid. Fall back to first scheme if folder not in mapping
	 * @param {String} folderId Entry id of a folder
	 * @return {Object} The color scheme as defined in {@link Zarafa.calendar.ui.ColorSchemes color scheme}
	 * for this folder
	 */
	getColorScheme : function(folderId)
	{
		// Remove 'favorites-' prefix before getting respective color scheme.
		folderId = folderId.replace('favorites-','');

		if (!this.colorMap) {
			return undefined;
		}

		var colorSchemeName = this.colorMap[folderId];
		var colorScheme = Zarafa.core.ColorSchemes.getColorScheme(colorSchemeName);
		if ( !Ext.isDefined(colorScheme) ){
			// No scheme defined yet. Let's just use a random color scheme.
			colorScheme = this.colorScheme[Math.floor(Math.random()*this.colorScheme.length)];
			this.setColorScheme(folderId, colorScheme);
		}

		return colorScheme;
	},

	/**
	 * Map a folder to a color scheme
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} Folder object to map
	 * @param {Object} Color scheme to map to
	 */
	setColorScheme : function(folderId, scheme)
	{
		this.colorMap[folderId] = scheme.name;

		if ( !this.assigningColors ){
			this.fireEvent('colormapchanged', this, this.colorMap);
		}
	},

	/**
	 * Obtain the currently active group out of this.groupings
	 */
	getActiveGroup : function()
	{
		return this.active_group;
	},

	/**
	 * Handler for 'foldergroupinchanged' event, which calls {@link #saveState} only
	 * when the groupings or active group has changed.
	 *
	 * @param {Zarafa.core.ContextModel} contextModel the contextModel which states needs to be saved.
	 * @param {Object} groupings The groupings object
	 * @param {String} active The active group
	 */
	saveFolderGroupinChanged : function(model, groupings, active)
	{
		if (this.groupings != groupings || active != this.active_group) {
			this.saveState();
		}
	},

	/**
	 * Register the {@link #stateEvents state events} to the {@link #saveState} callback function.
	 * @protected
	 */
	initStateEvents : function()
	{
		Zarafa.core.MultiFolderContextModel.superclass.initStateEvents.call(this);
		this.on('foldergroupingchanged', this.saveFolderGroupinChanged, this, { delay : 100 });
		// The colorMap is actually does not change when we switch to the calendar context,
		// therefore checking if it's changed is not required.
		this.on('colormapchanged', this.saveState, this, { delay : 100 });
	},

	/**
	 * When {@link #stateful} the State object which should be saved into the
	 * {@link Ext.state.Manager}.
	 * @return {Object} The state object
	 * @protected
	 */
	getState : function()
	{
		var state = Zarafa.core.MultiFolderContextModel.superclass.getState.call(this) || {};

		return Ext.apply(state, {
			groupings : this.groupings,
			active_group : this.active_group,
			colorMap : this.colorMap
		});
	},

	/**
	 * Apply the given state to this object activating the properties which were previously
	 * saved in {@link Ext.state.Manager}.
	 * @param {Object} state The state object
	 * @protected
	 */
	applyState : function(state)
	{
		if ( Ext.isDefined(state.groupings) ){
			if ( Array.isArray(state.groupings) ){
				// PHP has probably converted an empty object to an empty array since it doesn't know
				// the difference. But we need an object or things will go wrong!
				state.groupings = {};
			}
			// Check the active folders in the groups
			for ( var groupId in state.groupings ) {
				if ( state.groupings.hasOwnProperty(groupId) ){
					if ( state.groupings[groupId].folders.indexOf(state.groupings[groupId].active) < 0 ){
						// If the active folder was not found in the folders of the group, then we will
						// change the active folder to the first folder in the group
						state.groupings[groupId].active = state.groupings[groupId].folders[0];
					}
				}
			}
		}

		Zarafa.core.MultiFolderContextModel.superclass.applyState.call(this, state);
	},

	/**
	 * Reset all the grouping information
	 */
	resetGroupings : function()
	{
		this.groupings = {};
	}
});
