/*
 *  Copyright 2015 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * This tool displays the sitemap of a site
 * @private
 */
Ext.define('Ametys.plugins.web.sitemap.SitemapTool', {
	extend: 'Ametys.tool.Tool',
	
	/**
	 * @private
	 * @property {Ext.tree.Panel} _tree The sites tree panel
	 */
	
	constructor: function(config)
	{
		this.callParent(arguments);
		this._indicators = config.indicators ? JSON.parse(config.indicators) : [];
		Ametys.message.MessageBus.on(Ametys.message.Message.SELECTION_CHANGED, this._onSelectionChanged, this);
		Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onModified, this);
		Ametys.message.MessageBus.on(Ametys.message.Message.CREATED, this._onCreated, this);
		Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onDeleted, this);
		Ametys.message.MessageBus.on(Ametys.message.Message.WORKFLOW_CHANGED, this._onWorkflowChanged, this);
	},
	
	getMBSelectionInteraction: function() 
	{
		return Ametys.tool.Tool.MB_TYPE_ACTIVE;
	},
	
	createPanel: function ()
	{
		var langCombo = Ametys.cms.language.LanguageDAO.createComboBox({
			itemId: 'sitemap-tool-languages',
			fieldLabel: "{{i18n PLUGINS_WEB_TOOL_SITEMAP_SITEMAPTOOL_LANGUAGE}}",
			flex: 1,
			
			synchronize: true,
			listeners: {
	        	'change': Ext.bind(this._onChangeLang, this)
	        }
		});
		
		this._tree = this._createSitemapTreePanel(langCombo.getValue());
		
		return Ext.create ('Ext.Panel', {
			cls: 'sitemap-tool',
			border: false,
			
			layout: 'fit',
			
			// Languages combo box
			dockedItems: [{
				dock: 'top',
				xtype: 'toolbar',
				layout: {
			        type: 'hbox',
			        align: 'stretch'
			    },
			    border: false,
				
				defaults : {
					cls: 'ametys',
					labelWidth: 55,
					labelSeparator: ''
				},
				
				items: langCombo
			}],
			
			items: this._tree
		});
	},
	
	/**
	 * Listens when a language is changed.
	 * @param {Ext.form.field.Field} field The field
	 * @param {Object} newValue The new language value.
	 * @param {Object} oldValue The original value.
	 * @private
	 */
	_onChangeLang: function(field, newValue, oldValue)
	{
		this.showRefreshing();
		
		// Reload sitemap
		this._tree.setSitemapName (newValue, Ext.bind(this._refreshCb, this));
	},
	
	sendCurrentSelection: function()
	{
        var selection = this._tree.getSelectionModel().getSelection();
        
        var parameters = {};
        
        var targets = [];
        if (selection.length > 0)
        {
            var node = selection[0];
            targets.push(this.getMessageTargetConfiguration(node));
            parameters.creation = targets[0].id;
        }
        
        Ext.create('Ametys.message.Message', {
            type: Ametys.message.Message.SELECTION_CHANGED,
            parameters: parameters,
            targets: targets
        });
	},
	
	setParams: function (params)
	{
		this.callParent(arguments);
		this.refresh();
	},
	
	refresh: function ()
	{
		this.showRefreshing();
		this._tree.initialize (Ext.bind(this._refreshCb, this));
	},

	/**
	 * @private
	 * Callback function after (re)loading the tree
	 */
	_refreshCb: function ()
	{
		this.showRefreshed();
		
		// Select root node
		this._tree.getSelectionModel().select(0);
	},
	
    /**
     * Get the tree of the tool
     * @return {Ext.tree.Panel} The main tree component of the tool
     */
	getTree: function()
	{
		return this._tree;
	},
	
	/**
	 * Expand the tree and select a node by its path
	 * @param {String} path The path of the node
	 */
	selectNodeByPath: function (path)
	{
		this.getTree().selectByPath(path);
	},
	
	/**
	 * @private
	 * Draw the tree panel for the sites
	 * @param {String} defaultLang the default sitemap name
	 */
	_createSitemapTreePanel: function (defaultLang)
	{
		return Ext.create('Ametys.web.sitemap.SitemapTree', {
			// Language selection is handled by the tool itself
			sitemapContext: defaultLang,
			defaultSitemapName: defaultLang,
			indicators: this._indicators,
			
			stateful: true,
			stateId: this.getId() + '$sitemap-tree',			
			
			// handle drag & drop
			viewConfig: {
                plugins: {
                    ptype: 'ametystreeviewdragdrop',
                    containerScroll: true,
                    appendOnly: false,
                    sortOnDrop: false,
                    expandDelay: 500,
                    allowContainerDrops: false,
                    setAmetysDragInfos: Ext.bind(this.getDragInfo, this),
                    setAmetysDropZoneInfos: Ext.bind(this.getDropInfo, this)
                }
            },
            
			listeners: {
				'selectionchange': Ext.bind(this.sendCurrentSelection, this),
				'itemdblclick': Ext.bind(this._openPage, this)
			}
		});
	},
	
	/**
     * @private
     * This event is thrown by the getDragData to add the 'source' of the drag.
     * @param {Object} item The default drag data that will be transmitted. You have to add a 'source' item in it: 
     * @param {Ametys.relation.RelationPoint} item.source The source (in the relation way) of the drag operation. 
     */
    getDragInfo: function (item)
    {
        var targets = [];
        
        for (var i = 0; i < item.records.length; i++)
        {
        	var node = item.records[i];
        	if (!node.isRoot() && node.getData().moveable)
			{
				targets.push(this.getMessageTargetConfiguration(node));
			}
        }
        
        if (targets.length > 0)
        {
            item.source = {
                relationTypes: [Ametys.relation.Relation.MOVE, Ametys.relation.Relation.REFERENCE], 
                targets: targets
            };
        }
    },
    
    /**
     * @private
     * This event is thrown before the beforeDrop event and create the target of the drop operation relation.
     * @param {Ext.data.Model[]} targetRecords The target records of the drop operation.
     * @param {Object} item The default drag data that will be transmitted. You have to add a 'target' item in it: 
     * @param {Object} item.target The target (in the relation way) of the drop operation. A Ametys.relation.RelationPoint config.
     * @param {"append"/"before"/"after"} dropPosition The drop mode
     */ 
    getDropInfo: function(targetRecords, item, dropPosition)
    {
        var targets = [];
        
        var positionInTargets = -1
        for (var i = 0; i < targetRecords.length; i++)
        {
        	var node = targetRecords[i];
        	
        	if (node.isRoot() || dropPosition == 'append')
			{
				targets.push(this.getMessageTargetConfiguration(node));
			}
        	else if (node.parentNode != null) // dropPosition == 'before' or 'after'
        	{
        		// Get node position 
        		for (var i=0; i < node.parentNode.childNodes.length; i++)
        		{
        			if (node.parentNode.childNodes[i].getId() == node.getId())
        			{
        				positionInTargets = i + (dropPosition == 'after' ? + 1 : 0);
        				break;
        			}
        		}
        		
        		targets.push(this.getMessageTargetConfiguration(node.parentNode));
        	}
        }

        if (targets.length > 0)
        {
            item.target = {
                relationTypes: [Ametys.relation.Relation.MOVE, Ametys.relation.Relation.REFERENCE], 
                targets: targets,
                positionInTargets: positionInTargets
            };
        }
    },
	
    /**
     * @private
     * Get the target configuration object for given record
     * @param {Ext.data.Model} record The tree record to convert to its Ametys.message.MessageTarget configuration
     * @return {Object} The configuration to create a Ametys.message.MessageTarget. Can be null, if the record is null or not relevant to be a messagetarget.
     */
    getMessageTargetConfiguration: function (record)
    {
        if (record == null)
        {
            // Empty selection
            return null;
        }
        else
        {
        	if (record.isRoot())
			{
				return {
					id: Ametys.message.MessageTarget.SITEMAP,
					parameters: {id: record.getId()}
				};
			}
			else
			{
				return {
					id: Ametys.message.MessageTarget.PAGE,
					parameters: {ids: [record.getId()]}
				};
			}
        }
    },
    
	/**
	 * @private
	 * Open page on double-click event
	 * @param {Ext.view.View} view the tree view
	 * @param {Ext.data.Model} node The record that belongs to the double-clicked item
	 */
	_openPage: function (view, node)
	{
        Ametys.tool.ToolsManager.openTool (node.isRoot() ? "uitool-sitemappage" : "uitool-page", {id: node.getId()});
	},
	
	/**
	 * @private
	 * Listener on the current selection
	 * @param {Ametys.message.Message} message the selection message
	 */
	_onSelectionChanged: function(message)
	{
		var target = message.getTarget(Ametys.message.MessageTarget.PAGE) || message.getTarget(Ametys.message.MessageTarget.SITEMAP);
		if (target != null)
		{
			var selection = this._tree.getSelectionModel().getSelection();
			var node = this._tree.getStore().getNodeById(target.getParameters().id);
			if (node != null && selection.length > 0 && node == selection[0])
			{
				// same selection
				return;
			}
			
			// Pages from 2 different sitemaps can have the same path
			if (this._tree.getCurrentSitemapName() == target.getParameters().lang) 
			{
			    // Page
				this.selectNodeByPath(target.getParameters().path);
			}
			else if (this._tree.getCurrentSitemapName() == target.getParameters().name)
			{
			    // Sitemap
                this.selectNodeByPath(target.getParameters().name);
			}
		}
	},
	
	/**
     * @private
     * Listener on site edition
     * @param {Ametys.message.Message} message The edition message
     */
	_onModified: function(message)
	{
        var major = message.getParameters().major == true;
		var target = message.getTarget(Ametys.message.MessageTarget.SITEMAP);
		if (target != null)
		{
			this._tree.getStore().load({node: this._tree.getRootNode()});
		}
		
		var targets = message.getTargets(Ametys.message.MessageTarget.PAGE);
		if (targets.length > 0)
		{
			var store = this._tree.getStore();
			Ext.Array.forEach(targets, function(target) {
				var node = this._tree.getStore().getNodeById(target.getParameters().id);
				if (node != null)
				{
					Ametys.data.ServerComm.callMethod({
						role: "org.ametys.web.repository.page.SitemapDAO",
						methodName: "getPageProperties",
						parameters: [target.getParameters().id],
						callback: {
							scope: this,
							handler: function (data) {
                                if (major)
                                {
                                    // only refresh the edited node
                                    this._tree.refreshNode(target.getParameters().id, Ext.bind(this._tree.updateNodeData, this._tree, [target.getParameters().id, data]));
                                }
                                else
                                {
    								// Update page node only
    								this._tree.updateNodeData (target.getParameters().id, data);
                                }
							}
						},
						errorMessage: false
					});
				}
			}, this);
		}
	},
	
	/**
     * @private
     * Listener invoked on workflow changed message
     * @param {Ametys.message.Message} message The message
     */
	_onWorkflowChanged: function (message)
	{
		var targets = message.getTargets(Ametys.message.MessageTarget.CONTENT);
		if (targets.length > 0)
		{
			for (var i=0; i < targets.length; i++)
			{
                Ametys.data.ServerComm.callMethod({
                    role: "org.ametys.web.repository.page.SitemapDAO",
                    methodName: "getReferencingPages",
                    parameters: [targets[i].getParameters().id],
                    callback: {
                        scope: this,
                        handler: this._getReferencingPagesCb
                    },
                    errorMessage: "{{i18n PLUGINS_WEB_DAOS_CONTENT_GET_REFERENCING_PAGES_ERROR}}"
                });
			}
		}
	},

	/**
	 * @private
	 * Callback function invoked after getting referencing pages of a content.
	 * Update pages UI if visible in the tree.
	 * @param {Object[]} pages the referencing pages
	 */
	_getReferencingPagesCb: function (pages)
	{
		var me = this;
		Ext.Array.forEach (pages, function (page, index) {
			if (me._tree.getCurrentSiteName() == page.siteName && me._tree.getCurrentSitemapName() == page.lang)
			{
				me._tree.updateNodeData (page.id, page);
				me._tree.refreshNode(page.id);
			}
		})
	},
	
	/**
     * @private
     * Listener on page creation
     * @param {Ametys.message.Message} message The creation message
     */
	_onCreated: function (message)
	{
		var target = message.getTarget(Ametys.message.MessageTarget.PAGE);
		if (target != null)
		{
			var store = this._tree.getStore();
			var node = store.getNodeById(target.getParameters().parentId);
			if (node == null)
			{
				node = this._tree.getRootNode();
			}
			
			node.set('expanded', true);
			
			store.load({node: node});
		}
	},
	
	/**
     * @private
     * Listener on site deletion
     * @param {Ametys.message.Message} message The deletion message
     */
	_onDeleted: function (message)
	{
		var targets = message.getTargets(Ametys.message.MessageTarget.PAGE);
		if (targets.length > 0)
		{
			var store = this._tree.getStore();
			var me = this;
			
			Ext.Array.forEach(targets, function(target) {
				
				var node = store.getNodeById(target.getParameters().id);
				if (node != null)
				{
					var parentNode = node.parentNode;
					
					var parentNode = node.parentNode;
					me._tree.getSelectionModel().select([parentNode]);
					node.remove();
				}
			});
		}
	}
});