Ext.namespace('Zarafa.hierarchy.ui');

/**
 * @class Zarafa.hierarchy.ui.HierarchyTreePanel
 * @extends Zarafa.hierarchy.ui.Tree
 * @xtype zarafa.hierarchytreepanel
 *
 * HierarchyTreePanel for hierachy list in the main window.
 */
Zarafa.hierarchy.ui.HierarchyTreePanel = Ext.extend(Zarafa.hierarchy.ui.Tree, {
	/**
	 * @cfg {Boolean} enableItemDrop true to enable just drag for {@link Zarafa.core.data.MAPIRecord items}
	 * from a {@Link Ext.grid.GridPanel grid}.
	 */
	enableItemDrop : false,

	/**
	 * The dropZone used by this tree if drop is enabled (see {@link #enableItemDrop})
	 * @property
	 * @type Ext.tree.TreeDropZone
	 */
	itemDropZone : undefined,

	/**
	 * @cfg {Object} itemDropConfig Custom config to pass to the {@link Ext.tree.TreeDropZone} instance
	 */
	itemDropConfig : undefined,

	/**
	 * @cfg {Object} bbarConfig Custom config to pass to the {@link Zarafa.hierarchy.ui.HierarchyTreeBottomBar}.
	 * By default the xtype in this object is set to zarafa.hierarchytreebottombar.
	 */
	bbarConfig: undefined,

	/**
	 * @cfg {Boolean} showAllFoldersDefaultValue True to render the 'Show all folders'
	 * {@link #showAllFoldersCheckbox checkbox} as {@link Ext.form.Checkbox#checked checked}.
	 */
	showAllFoldersDefaultValue : false,

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

		var checked = Ext.isDefined(config.showAllFoldersDefaultValue) ?
				config.showAllFoldersDefaultValue : this.showAllFoldersDefaultValue;

		Ext.applyIf(config, {
			xtype : 'zarafa.hierarchytreepanel',
			baseCls: 'zarafa-hierarchy-treepanel',
			flex : 1,
			minHeight : 100,
			stateful : true,
			statefulName : 'hierarchytree',
			tbar: {
				items: [{
					xtype: 'checkbox',
					cls: 'zarafa-hierarchy-treepanel-showallfolders',
					ref: '../showAllFoldersCheckbox',
					boxLabel : _('Show all folders'),
					checked : checked,
					listeners : {
						beforerender: this.reviseCheckboxDisablity,
						check: this.onCheckShowAllFoldersCheckbox,
						scope: this
					}
				}]
			},
			loadMask : true,
			treeSorter : true,
			trackMouseOver : true,
			containerScroll: true,
			// Default values for the Drag&Drop objects.
			// By default is Drag&Drop disabled...
			dragConfig : {
				ddGroup: 'dd.mapifolder'
			},
			dropConfig : {
				ddGroup: 'dd.mapifolder',
				expandDelay : 250,
				allowParentInsert : true
			},
			enableItemDrop : true,
			itemDropConfig : {
				ddGroup: 'dd.mapiitem'
			}
		});

		if(!Ext.isDefined(config.bbar)){
			config.bbarConfig = config.bbarConfig || {};
			config.bbar = Ext.applyIf(config.bbarConfig, {
				xtype : 'zarafa.hierarchytreebottombar'
			});
		}

		this.addEvents(
			/**
			 * @event beforeitemdrop
			 * Fires when a DD object is dropped on a node in this tree for preprocessing. Return false to cancel the drop. The dropEvent
			 * passed to handlers has the following properties:<br />
			 * <ul style="padding:5px;padding-left:16px;">
			 * <li>tree - The TreePanel</li>
			 * <li>target - The node being targeted for the drop</li>
			 * <li>data - The drag data from the drag source</li>
			 * <li>point - The point of the drop - append, above or below</li>
			 * <li>source - The drag source</li>
			 * <li>rawEvent - Raw mouse event</li>
			 * <li>dropNode - Drop node(s) provided by the source <b>OR</b> you can supply node(s)
			 * to be inserted by setting them on this object.</li>
			 * <li>cancel - Set this to true to cancel the drop.</li>
			 * <li>dropStatus - If the default drop action is cancelled but the drop is valid, setting this to true
			 * will prevent the animated 'repair' from appearing.</li>
			 * </ul>
			 * @param {Object} dropEvent
			 */
			'beforeitemdrop',
			/**
			 * @event itemdrop
			 * Fires after a DD object is dropped on a node in this tree. The dropEvent
			 * passed to handlers has the following properties:<br />
			 * <ul style="padding:5px;padding-left:16px;">
			 * <li>tree - The TreePanel</li>
			 * <li>target - The node being targeted for the drop</li>
			 * <li>data - The drag data from the drag source</li>
			 * <li>point - The point of the drop - append, above or below</li>
			 * <li>source - The drag source</li>
			 * <li>rawEvent - Raw mouse event</li>
			 * <li>dropNode - Dropped node(s).</li>
			 * </ul>
			 * @param {Object} dropEvent
			 */
			'itemdrop'
		);

		Zarafa.hierarchy.ui.HierarchyTreePanel.superclass.constructor.call(this, config);

		// We cannot wait for render time to set these listeners (as in initEvents()) because
		// then the listeners would not be set when 'Show all folders' is checked.
		this.mon(this.store, 'remove', this.onStoreRemove, this);
		this.mon(this.store, 'removeFolder', this.onFolderRemove, this);
	},


	/**
	 * Called after the tree has been {@link #render rendered} This will initialize
	 * all event handlers and when {@link #enableDD Drag & Drop} has been enabled,
	 * it will initialize the {@link #dropZone} with a special
	 * {@link Zarafa.hierarchy.ui.HierarchyFolderDropZone Folder DropZone} object.
	 * When {@link #enableItemDrop} has been enabled, it will also initialize the
	 * {@link #itemDropZone} using the {@link Zarafa.hierarchy.ui.HierarchyItemDropZone Item DropZone}.
	 * @private
	 */
	initEvents : function()
	{
		// Capture events that can change the height of the hierarchy tree, or of the containing panel,
		// so we can add a class and the css can handle the position of the bottombar
		this.on('afterrender', function(){
			this.on('expandnode', this.checkTreeHeight, this);
			this.on('collapsenode', this.checkTreeHeight, this);
			this.on('afterlayout', this.checkTreeHeight, this);
			this.on('append', this.checkTreeHeight, this);
			this.on('remove', this.checkTreeHeight, this);
			this.on('resize', this.checkTreeHeight, this);
		}, this, {single: true});

		// Add listeners to Zarafa.hierarchy.ui.Tree events
		this.on('contextmenu', this.onTreeNodeContextMenu, this);
		this.on('click', this.onFolderClicked, this);
		this.mon(container, 'folderselect', this.onFolderSelect, this);

		this.mon(container, 'contextswitch', this.reviseCheckboxDisablity, this);

		// TODO This needs to be fixed by lazy loading the stuff in the mainPanel, then we can do container.getNavigationBar()
		// But at the moment it is instantiated as getMainPanel is run and so we cannot yet get the navigationBar that way
		var navigationPanel = this.findParentByType('zarafa.navigationpanel');
		if(navigationPanel){
			this.mon(navigationPanel, 'toggleshowallfolders', this.onToggleShowAllFolders, this);
		}

		// Add listener for the 'load' event so we can select
		// the currently active folder (when a folder was activated
		// before the hierarchy is shown).
		this.mon(this.loader, 'load', this.onHierarchyLoaderLoad, this);

		if (this.stateful === true) {
			this.on('expandnode', this.saveFolderState, this, { buffer : 5 });
			this.on('collapsenode', this.saveFolderState, this, { buffer : 5 });
		}

		if (this.enableDD || this.enableDrop) {
			// Initialize a special DropZone which has better support for detecting where
			// mapifolders can be dropped inside the hierarchy.
			if (!this.dropZone) {
				this.dropZone = new Zarafa.hierarchy.ui.HierarchyFolderDropZone(this, this.dropConfig || {
					ddGroup: this.ddGroup || 'TreeDD', appendOnly: this.ddAppendOnly === true
				});
			}

			this.on('nodedrop', this.onNodeDrop, this);
		}

		if (this.enableItemDrop) {
			// Initialize a special DropZone which has support for dragging MAPIRecord objects
			// from a grid into the hierarchy.
			if (!this.itemDropZone) {
				this.itemDropZone = new Zarafa.hierarchy.ui.HierarchyItemDropZone(this, this.itemDropConfig || {
					ddGroup: this.ddGroup || 'TreeDD'
				});
			}

			this.on('itemdrop', this.onItemDrop, this);
		}

		Zarafa.hierarchy.ui.HierarchyTreePanel.superclass.initEvents.call(this);
	},

	/**
	 * Register the {@link #stateEvents state events} to the {@link #saveState} callback function.
	 * @protected
	 */
	initStateEvents : function(){
		Zarafa.hierarchy.ui.HierarchyTreePanel.superclass.initStateEvents.call(this);
		this.mon(this.showAllFoldersCheckbox, 'check', this.saveState, this, {delay: 100});
	},

	/**
	 * Checks the height of the hierarchy tree. When it is smaller then the height of the containing box
	 * it will add a class so the the css can handle the position of the bottombar.
	 */
	checkTreeHeight: function(){
		if(!this.ownerCt) {
			return;
		}

		var treeHeight = this.body.down('ul').getHeight();
		var panelHeight = this.ownerCt.getHeight();
		var topBarHeight = this.getTopToolbar().getHeight();
		var bottomBarHeight = this.getBottomToolbar().getHeight();
		if ( panelHeight < treeHeight + bottomBarHeight + topBarHeight ){
			this.ownerCt.getEl().addClass('fixed-bottombar');
		} else {
			this.ownerCt.getEl().removeClass('fixed-bottombar');
		}
	},

	/**
	 * Fires on {@link Zarafa.core.Container#contextswitch}, or {@link Ext.form.Checkbox#beforerender}.
	 * Make (@link #showAllFoldersCheckbox) disable if current context is Settings or Zarafa, enable otherwise.
	 * @param {Object | Ext.form.Checkbox} parameters contains folder details or checkbox instance
	 * @param {Context} oldContext (optional) previously selected context
	 * @param {Context} newContext (optional) selected context
	 *
	 * @private
	 */
	reviseCheckboxDisablity : function(parameters, oldContext, newContext)
	{
		newContext = newContext || container.getCurrentContext();
		var settingsOrToday = (newContext == container.getContextByName('settings') || newContext == container.getContextByName('today'));
		this.showAllFoldersCheckbox.setDisabled(settingsOrToday);
	},

	/**
	 * Event handler which is trigggered after drop is completed on {@link Zarafa.hierarchy.ui.Tree Tree}.
	 * @param {Object} dropEvent The object describing the drop information
	 * @private
	 */
	onNodeDrop : function(dropEvent)
	{
		if (Ext.isDefined(dropEvent.dropNode)) {
			var targetNode = dropEvent.target;

			switch (dropEvent.point) {
				case 'above':
				case 'below':
					targetNode = dropEvent.target.parentNode;
					break;
				case 'append':
				/* falls through */
				default:
					break;
			}

			var sourceFolder = dropEvent.dropNode.getFolder();
			var targetFolder = targetNode.getFolder();

			if (dropEvent.rawEvent.ctrlKey) {
				sourceFolder.copyTo(targetFolder);
			} else {
				sourceFolder.moveTo(targetFolder);
			}

			sourceFolder.save();
		}
	},

	/**
	 * Event handler which is trigggered after drop of item is completed on {@link Zarafa.hierarchy.ui.Tree Tree}.
	 * @param {Object} dropEvent The object describing the drop information
	 * @private
	 */
	onItemDrop : function(dropEvent)
	{
		if (!Ext.isEmpty(dropEvent.dropItem)) {
			var targetNode = dropEvent.target;

			var sourceNodes = Array.isArray(dropEvent.dropItem) ? dropEvent.dropItem : [ dropEvent.dropItem ];
			var targetFolder = targetNode.getFolder();
			var store = sourceNodes[0].getStore();

			// When dropping items in the wastebasket, call deleteRecords so recurring meetings are handled.
			if(targetFolder.isSpecialFolder('wastebasket')) {
				Zarafa.common.Actions.deleteRecords(sourceNodes);
			} else if (dropEvent.rawEvent.ctrlKey) {
				for (var i = 0, len = sourceNodes.length; i < len; i++) {
					sourceNodes[i].copyTo(targetFolder);
				}
			} else {
				for (var i = 0, len = sourceNodes.length; i < len; i++) {
					sourceNodes[i].moveTo(targetFolder);
				}
			}

			// store.save is already called in deleteRecords
			if(!targetFolder.isSpecialFolder('wastebasket')) {
				store.save(sourceNodes);
			}
		}
	},

	/**
	 * Event handler which is fired when the {@link #store} fires the
	 * {@link Zarafa.hierarchy.data.HierarchyStore#remove} event handler. This will check
	 * if any of the folders inside the store is currently opened, and
	 * will deselect those folders.
	 *
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The store which fired the event
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} storeRecord The store which was deleted
	 * @private
	 */
	onStoreRemove : function(store, storeRecord)
	{
		var subFolders = storeRecord.getSubStore('folders');

		if (this.model) {
			subFolders.each(function(folder) {
				this.model.removeFolder(folder);
			}, this);
		}
	},

	/**
	 * Event handler which is fired when the {@link #store} fires the
	 * {@link Zarafa.hierarchy.data.HierarchyStore#removeFolder} event handlerr. This will check
	 * if the folder is currently opened, and will deselect that folder.
	 *
	 * @param {Zarafa.hierarchy.data.HierarchyStore} store The store which fired the event
	 * @param {Zarafa.hierarchy.data.MAPIStoreRecord} storeRecord The store from where the folder is
	 * removed
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder which was removed from the store
	 * @private
	 */
	onFolderRemove : function(store, storeRecord, folder)
	{
		if (this.model) {
			this.model.removeFolder(folder);
		}
	},

	/**
	 * Fires when the {@link Zarafa.core.Container} fires the
	 * {@link Zarafa.core.Container#folderselect} event. This
	 * will search for the corresponding node in the tree,
	 * and will mark the given folder as {@link #selectFolderInTree selected}.
	 *
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord|Array} folder The folder which
	 * is currently selected.
	 * @private
	 */
	onFolderSelect : function(folder)
	{
		if (Array.isArray(folder)) {

			// If we have multi selected folder then select previously selected node in tree.
			if (folder.length > 1 && this.model) {
				folder = this.model.getDefaultFolder();
			} else {
				folder = folder[0];
			}
		}

		// Select the node of selected folder.
		if (folder) {
			this.selectFolderInTree(folder);
		}
	},

	/**
	 * Fires when the {@link #loader} fires the {@link Zarafa.hierarchy.data.HierarchyTreeLoader#load}
	 * event to indicate that all nodes have been rendered into the tree.
	 * This will {@link #selectFolderInTree select} {@link Zarafa.core.ContextModel#getFolders all folders}
	 * @param {Object} loader TreeLoader object.
	 * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
	 * @param {Object} response The response object containing the data from the server.
	 * @private
	 */
	onHierarchyLoaderLoad : function(loader, node, response)
	{
		// Use respective model considering the case the current model doesn't belongs to current context
		var currentContextModel = container.getCurrentContext().getModel();
		if (currentContextModel) {
			var folders = currentContextModel.getFolders();
			for (var i = 0, len = folders.length; i < len; i++) {
				this.selectFolderInTree(folders[i], folders[i].id === node.id);
			}

			// If we have multi selected folder then select previously selected node in tree.
			if (folders.length > 1 && currentContextModel) {
				this.selectFolderInTree(currentContextModel.getDefaultFolder());
			}
		}
	},

	/**
	 * Fired on contextmenu event on {@link Zarafa.hierarchy.ui.FolderNode}
	 * @param {Zarafa.hierarchy.ui.FolderNode} treeNode The node on which the contextmenu
	 * was requested.
	 * @param {Ext.EventObject} eventObj The event object with event information
	 * @private
	 */
	onTreeNodeContextMenu : function(treeNode, eventObj)
	{
		// If folder is favorites root folder then disable the right click
		// as it doesn't support any context menu items.
		if(treeNode.getFolder().isFavoritesRootFolder()) {
			return;
		}

		var positionEventObj = eventObj.getXY();

		// Handle a specific situation for Edge where somehow eventObj replaced with 'blur' event which doesn't have the position.
		// Check if the position is available or not, get the position of treeNode
		// and use that position to render context menu if not available.
		if (positionEventObj[0] === 0 && positionEventObj[1] === 0) {
			var treeNodeAnchor = treeNode.ui.anchor;
			var nodePosition = treeNodeAnchor.getBoundingClientRect();
			positionEventObj = [nodePosition.left, nodePosition.top];
		}
		var folder = treeNode.getFolder();
		if(folder.isFavoritesFolder() && !folder.isSearchFolder()) {
			folder = folder.getOriginalRecordFromFavoritesRecord();
		}
		Zarafa.core.data.UIFactory.openDefaultContextMenu(folder, { position : positionEventObj, contextNode : treeNode });
	},

	/**
	 * Fired when a node is clicked in {@link Zarafa.hierarchy.ui.Tree}.
	 * It calls container to change folder.
	 * @param {Ext.tree.TreeNode} node which is clicked
	 */
	onFolderClicked : function(treeNode)
	{
		var folder = treeNode.getFolder();
		Zarafa.hierarchy.Actions.openFolder(folder);
	},

	/**
	 * @return {Zarafa.hierarchy.ui.TreeEditor} The tree editor which can be used
	 * @private
	 */
	getTreeEditor : function()
	{
		if (!this.treeEditor) {
			this.treeEditor = new Zarafa.hierarchy.ui.TreeEditor(this);
		}
		return this.treeEditor;
	},

	/**
	 * Triggers node editing
	 * @param {Zarafa.hierarchy.ui.FolderNode} treeNode node to be edited
	 */
	startEditingNode : function(treeNode)
	{
		this.getTreeEditor().startEditingNode(treeNode);
	},

	/**
	 * When {@link #stateful} is enabled, this will test if the given
	 * folder has the 'is_open' state enabled.
	 * If the folder is not found in the settings, the rule is that
	 * {@link Zarafa.hierarchy.data.MAPIFolderRecord#isIPMSubTree subtrees}
	 * will be expaned by default, all other folders are collapsed.
	 * @param {Zarafa.hierarchy.data.MAPIFolderRecord} folder The folder to check
	 * @return {Boolean} True if the folder should be expanded by default
	 * @private
	 */
	isFolderOpened : function(folder)
	{
		var opened;
		if (this.stateful === true) {
			var state = container.getHierarchyStore().getState(folder, 'tree');
			if (state) {
				opened = state.is_open;
			}
		}
		if (!Ext.isDefined(opened)) {
			opened = Zarafa.hierarchy.ui.HierarchyTreePanel.superclass.isFolderOpened.call(this, folder);
		}
		return opened;
	},

	/**
	 * Event handler for the {@link #expandnode} and {@link #collapsenode} events. When
	 * {@link #stateful} is enabled, then this function will save the current
	 * state of the given node.
	 *
	 * @param {Zarafa.hierarchy.ui.FolderNode} node The node which will be saved into the settings
	 * @private
	 */
	saveFolderState : function(node)
	{
		if (this.stateful === true && !node.isRoot) {
			var folder = node.getFolder();
			var state = container.getHierarchyStore().getState(folder, 'tree');

			if (state.is_open !== node.expanded) {
				container.getHierarchyStore().applyState(folder, 'tree', { is_open : node.expanded });
			}
		}
	},

	/**
	 * Called when the {@link #showAllFoldersCheckbox} checkbox in the top toolbar is checked or
	 * unchecked. It will set the toggle showAllFolders option in the
	 * {@link Zarafa.core.ui.NavigationPanel}.
	 * @param {Ext.Form.Checkbox} button The pressed button
	 * @param {Boolean} checkState True when checkbox is checked, false when not
	 */
	onCheckShowAllFoldersCheckbox: function(checkbox, checkState)
	{
		container.getNavigationBar().setShowFolderList(checkState);
	},

	/**
	 * Called when the {@link Zarafa.core.ui.NavigationPanel} fires the
	 * {@link Zarafa.core.ui.NavigationPanel#toggleshowallfolders toggleshowallfolders} event. Then
	 * we change the {@link #showAllFoldersCheckbox} button in the top toolbar accordingly.
	 * @param {Boolean} show Value of the showAllFolders state
	 */
	onToggleShowAllFolders: function(show)
	{
		// Suspend events to stop the check-event that will fire from triggering all kinds of
		// other updates externally.
		this.suspendEvents(false);

		this.showAllFoldersCheckbox.setValue(show);

		this.resumeEvents();
	},

	/**
	 * Called before the panel is being destroyed.
	 */
	beforeDestroy : function()
	{
		if (this.rendered) {
			Ext.destroy(this.itemDropZone);
		}

		Zarafa.hierarchy.ui.HierarchyTreePanel.superclass.beforeDestroy.apply(this, arguments);
	},

	/**
	 * 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.hierarchy.ui.HierarchyTreePanel.superclass.getState.call(this) || {};
		var checkboxValue = this.showAllFoldersCheckbox.getValue();
		return Ext.apply(state, {
			showallcheckbox : checkboxValue
		});
	},

	/**
	 * Obtain the path in which the {@link #getState state} must be saved.
	 * This option is only used when the {@link Zarafa.core.data.SettingsStateProvider SettingsStateProvider} is
	 * used in the {@link Ext.state.Manager}. This returns {@link #statefulName} if provided, or else generates
	 * a custom name.
	 * @return {String} The unique name for this component by which the {@link #getState state} must be saved.
	 */
	getStateName : function()
	{
		return 'sidebars/' + Zarafa.core.ui.MainViewSidebar.superclass.getStateName.call(this);
	}
});
Ext.reg('zarafa.hierarchytreepanel', Zarafa.hierarchy.ui.HierarchyTreePanel);