/*
 *  Copyright 2014 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 JCR tags in a tree 
 * @private
 */
Ext.define('Ametys.plugins.cms.tag.tool.TagsTool', {
    extend : 'Ametys.tool.Tool',

    /**
     * @property {Ext.tree.Panel} _tree The tree panel
     * @private
     */
    
    /**
	 * @property {Ext.Template} _tagTooltipTpl The template used for tags' tooltip
	 */
    _tagTooltipTpl : Ext.create('Ext.XTemplate', [
                         '<tpl if="description && description != \'\'">',
                            '{description}<br/>',
                         '</tpl>',
                         '<u>{{i18n PLUGINS_CMS_UITOOL_TAG_TOOLTIP_VISIBILITY}}</u> : <span style="white-space: nowrap">{visibility}</span><br/>',
                         '<tpl if="target && target != \'\'">',
                            '<u>{{i18n PLUGINS_CMS_UITOOL_TAG_TOOLTIP_TARGET}}</u> : <span style="white-space: nowrap">{target}</span><br/>',
                         '</tpl>',
                         '<tpl if="isColorable">',
                           '<tpl if="color && color != \'\'">',
                              '<u>{{i18n PLUGINS_CMS_UITOOL_TAG_TOOLTIP_COLOR}}</u> : <span class="{class}-tooltip-{color}" /><br/>',
                           '<tpl else>',
                              '<u>{{i18n PLUGINS_CMS_UITOOL_TAG_TOOLTIP_COLOR}}</u> : <span class="{class}-tooltip-default" /><br/>',
                           '</tpl>',
                         '</tpl>'
    ]),
                                                     
    
    /**
	 * @property {String} _rootNodeLabel The label of the root node
	 */
    _rootNodeLabel : '{{i18n PLUGINS_CMS_UITOOL_TAGS_ROOT_NODE_LABEL}}',
    
    constructor : function(config)
    {
        this.callParent(arguments);

        Ametys.message.MessageBus.on(Ametys.message.Message.CREATED, this._onTagCreated, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onTagModified, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onTagDeleted, this);
    },

    createPanel : function()
    {
        var store = this._createStore();
        
        this._tree = Ext.create('Ext.tree.Panel', {
            border : false,
            scrollable : true,
            animate : true,
            dockedItems: this._getDockedItems(),

            store : store,
            root : {
            	text : this._rootNodeLabel,
            	expanded: false,
            	iconCls : "tag-icon-root"
            },
            rootVisible : true,
            selModel: {
            	mode: "SINGLE"
            },

            viewConfig: {
                plugins: {
                    ptype: 'ametystreeviewdragdrop',
                    containerScroll: true,
                    appendOnly: true,
                    sortOnDrop: true,
                    expandDelay: 500,
                    allowContainerDrops: false,
                    setAmetysDragInfos: Ext.bind(this.getDragInfo, this),
                    setAmetysDropZoneInfos: Ext.bind(this.getDropInfo, this)
                }
            }
        });
        
        this._tree.on('itemmouseenter', this._createQtip, this);
        this._tree.on('selectionchange', this._onSelectNode, this);

        return this._tree;
    },
    
    
    /**
     * @template
     * @protected 
     * Get the docked items for the tree panel
     * @return {}
     */
    _getDockedItems: function()
    {
        var items = this._getTopToolbarItems();
        if (items && items.length > 0)
        {
            return [{
                dock: 'top',
                xtype: 'toolbar',
                layout: {
                    type: 'hbox',
                    align: 'stretch'
                },
                
                border: false,
                defaults : {
                    cls: 'ametys',
                    labelWidth: 55,
                    labelSeparator: ''
                },
                
                items: items
            }]
        }
    },
    
    /**
     * @template
     * @protected
     * Get the toolbars to but on top of the tool.<br/>
     * Override this method to add your own toolbars. 
     */
    _getTopToolbarItems: function ()
    {
        return [];
    },
    
    /**
     * @protected
     * Get the store that will return the tags
     * @return {Ext.data.TreeStore}
     */
    _createStore: function()
    {
       var store = Ext.create('Ext.data.TreeStore', {
            model : this.getTagModel(),
            autoLoad : false,
            
            sorters: [{
                property: 'priority',
                direction: 'DESC'
            }, {
                property: 'text',
                direction: 'ASC'
            }],

            proxy : {
                type : 'ametys',
                plugin : 'cms',
                url : 'tags/repository.json',
                reader : {
                    type : 'json'
                },
                extraParams: {
                    contextualParameters: this.getContextualParameters(),
                    tagProviderId: this.getTagProviderId(),
                    tagProviderEPId: this.getTagProviderEPId()
                }
            }
        });
        
       return store;
    },
    
    /**
     * @template
     * @protected
     * Get the contextual parameters.<br/>
     * Override this method to set your own parameters. 
     */
    getContextualParameters: function ()
    {
    	return {};
    },
    
    /**
     * @template
     * @protected
     * Get the id of tag provider.<br/>
     * Returns 'org.ametys.cms.tag.jcr.CMSJCRTagProvider' by default<br/>
     * Override this method to get tags of provider of your choice.
     * @return the id of tag provider
     */
    getTagProviderId: function()
    {
    	return 'org.ametys.cms.tag.jcr.CMSJCRTagProvider';
    },
    
    /**
     * @template
     * @protected
     * Get the id of tag provider extension point.<br/>
     * Returns 'org.ametys.cms.tag.jcr.CMSJCRTagProvider' by default<br/>
     * Override this method to get tags of provider extension point of your choice.
     * @return the id of tag provider extension point
     */
    getTagProviderEPId: function()
    {
    	return 'org.ametys.cms.tag.TagProviderExtensionPoint';
    },
    
    /**
     * @template
     * @protected
     * Get the id of the tag DAO.<br/>
     * Returns 'org.ametys.cms.tag.jcr.JCRTagsDAO' by default<br/>
     * Override this method to get DAO link to you provider
     * @return the id of the tag DAO
     */
    getTagDAO: function()
    {
    	return 'org.ametys.cms.tag.jcr.JCRTagsDAO';
    },
    
    /**
     * @template
     * @protected
     * Get the tag model.<br/>
     * Returns 'Ametys.plugins.cms.tag.TagsTreePanel.TagNode' by default<br/>
     * Override this method to get different tag model
     * @return the tag model
     */
    getTagModel: function()
    {
    	return 'Ametys.plugins.cms.tag.TagsTreePanel.TagNode';
    },
    
    /**
     * @template
     * @protected
     * Get the tag target message id.<br/>
     * Returns 'Ametys.plugins.cms.tag.TagsTreePanel.TagNode' by default<br/>
     * Override this method to get different tag target message id
     * @return the tag target message id
     */
    getTagTargetMessage: function()
    {
    	return Ametys.message.MessageTarget.TAG;
    },
    
    /**
     * @template
     * @protected
     * Get the root tag target message id.<br/>
     * Returns 'Ametys.plugins.cms.tag.TagsTreePanel.TagNode' by default<br/>
     * Override this method to get different root tag target message id
     * @return the root tag target message id
     */
    getRootTagTargetMessage: function()
    {
    	return Ametys.message.MessageTarget.TAG_ROOT;
    },
    
    getMBSelectionInteraction : function()
    {
        return Ametys.tool.Tool.MB_TYPE_ACTIVE;
    },
    
    sendCurrentSelection : function()
    {
    	var me = this;
    	var targets = [];
    	Ext.each (this._tree.getSelectionModel().getSelection(), function (node) {
    	    targets.push(me.getMessageTargetConfiguration(node));
        });
    	
    	Ext.create("Ametys.message.Message", {
			type: Ametys.message.Message.SELECTION_CHANGED,
			targets: targets
		});
    },
    
    setParams: function (params)
    {
    	this.callParent(arguments);
    	this.refresh();
    },
    
    refresh : function()
    {
        this.showRefreshing();
		
        //Ask the server the root ID
        Ametys.data.ServerComm.callMethod({
			role: this.getTagDAO(),
			methodName: "getTagRootNode",
			parameters: [this.getTagProviderId(), this.getContextualParameters()],
			callback: {
				scope: this,
				handler: this._getRootIdCb
			},
			errorMessage: {
				msg: "{{i18n PLUGINS_CMS_HANDLE_TAGS_TAG_ERROR}}",
				category: this.self.getName()
			}
		});
    },
    
    /**
     * Callback function called after retrieving the root node's ID
     * @param {Object} result The server response
     * @param {Object} args The callback arguments
     * @private
     */
    _getRootIdCb: function (result, args)
    {
    	this._tree.setRootNode({
		    id : result.id,
            editable : false,
            allowDrag : false,
            allowDrop : true,
            text : this._rootNodeLabel,
            name : 'tags',
            path : 'tags',
            expanded : true,
            iconCls : "tag-icon-root"
    	});
    	
    	this._tree.getSelectionModel().select(0);
    	this.showRefreshed();
    },
    
    /**
     * This function is called when a node is selected.
     * @param {Ext.selection.Model} sm The selection model
     * @param {Ext.data.NodeInterface[]} selected The selected nodes
     * @private
     */
    _onSelectNode : function(sm, selected)
    {
    	this.sendCurrentSelection();
    },
    
    /**
     * Destroy and create the node tooltip when the mouse enters the node
     * @param {Ext.view.View} view The tree view
     * @param {Ext.data.Model} node The tree node
     * @param {HTMLElement} el The node's element
     */
    _createQtip: function (view, node, el)
    {
    	// If it's the root node, no Qtip
    	if (!node.parentNode)
    	{
    		return;
    	}
    	
		Ext.QuickTips.unregister(el);
		Ext.QuickTips.register(Ext.apply({target: el, id: el.id + '-tooltip'}, this._getTooltip(node)));
	},
	
	/**
	 * Get the tooltip configuration
	 * @param {Ext.data.Model} node The tree node
	 * @returns {Object} The tooltip configuration. See Ametys.ui.fluent.Tooltip.
	 */
	_getTooltip: function(node)
	{
		var title = node.get('name');
		if (node.get("title") != null)
		{
			title = Ext.String.escapeHtml(node.get('title')) + " (" + title + ")";
		}
		
		var text = this._tagTooltipTpl.applyTemplate ({
			description: Ext.String.escapeHtml(node.get('description')).replace(/\n/g, "<br/>"), 
			visibility : (node.get('visibility') == 'PRIVATE' ? "{{i18n PLUGINS_CMS_UITOOL_TAG_TOOLTIP_VISIBILITY_PRIVATE}}" : "{{i18n PLUGINS_CMS_UITOOL_TAG_TOOLTIP_VISIBILITY_PUBLIC}}"),
            target: node.get('target-info') && Ext.isObject(node.get('target-info')) ? node.get('target-info').label || node.get('target-info').name : null,
            color: node.get('color'),
            class: node.get('class'),
            isColorable: node.get('isColorable')
		});
		
		return {
			title: title,
			glyphIcon: node.get('tooltipIconCls'),
			imageWidth: 48,
			imageHeight: 48,
			text: text,
			inribbon: false
		};
	},

    /**
     * This function reload the given node
     * @param {String} id The ID of the node to reload
     * @param {Function} callback The callback function to call after reload. Can be null. Has the following parameters:
     * @param {Ext.data.Model} callback.node The refreshed node
     * @private
     */
    _refreshNode : function(id, callback)
    {
        var node;
        if (id == null)
        {
            var selection = this._tree.getSelectionModel().getSelection();
            node = selection.length > 0 ? selection[0] : null;
        }
        else
        {
            node = id != '' ? this._tree.getStore().getNodeById(id) : this._tree.getRootNode();
        }

        if (node != null)
        {
            this._tree.getStore().load({
                node : node,
                callback : function()
                {
                    Ext.defer(this._expandNode, 200, this, [ node, callback ]);
                },
                scope : this
            });
        }
    },

    /**
     * Expand the given node
     * @param {String} node The node to expand.
     * @param {Function} callback The callback function to call after reload. Can be null. Has the following parameters:
     * @param {Ext.data.Model} callback.node The expanded node
     * @private
     */
    _expandNode : function(node, callback)
    {
        if (node == null)
        {
            var selection = this.getSelectionModel().getSelection();
            node = selection.length > 0 ? selection[0] : null;
        }

        if (node != null)
        {
            callback = callback ? Ext.bind(callback, null, [ node ]) : null;
            this._tree.expandNode(node, false, callback);
        }
    },
    
    /**
	 * Deletes a node from the tree and selects the parent node.
	 * @param {String} id The ID of the node to delete.
	 * @private
	 */
	_deleteNode: function (id)
	{
		var node = this._tree.getStore().getNodeById(id);
		if (node != null)
		{
			var parentNode = node.parentNode;
			this._tree.getSelectionModel().select([parentNode]);
			node.remove();
		}
	},

    /**
     * Listener on {@link Ametys.message.Message#CREATED} message. 
     * If the tool is concerned by the created object, the parent node will be refreshed.
     * @param {Ametys.message.Message} message The created message.
     * @private
     */
    _onTagCreated: function (message)
    {
        var tagTarget = message.getTarget(this.getTagTargetMessage());
        if (tagTarget != null)
        {
            var tagParentId = tagTarget.getParameters().parentId;

            var node = this._tree.getStore().getNodeById(tagParentId);
            if (node != null)
            {
            	node.set('leaf', false);
            	node.commit();
            }
            
            this._refreshNode(tagParentId);
        }
    },

    /**
     * Listener on {@link Ametys.message.Message#MODIFIED} message. 
     * If the tool is concerned by the created object, the node will be refreshed.
     * @param {Ametys.message.Message} message The created message.
     * @private
     */
    _onTagModified: function (message)
    {
        var tagTarget = message.getTarget(this.getTagTargetMessage());
        if (tagTarget != null)
        {
            var tagId = tagTarget.getParameters().id;
            
            Ametys.data.ServerComm.callMethod({
    			role: this.getTagDAO(),
    			methodName: "getTag",
    			parameters: [tagId],
    			callback: {
    				scope: this,
    				handler: this._updateTag
    			},
    			errorMessage: {
                    msg: "{{i18n PLUGINS_CMS_HANDLE_TAGS_TAG_ERROR}}",
                    category: this.self.getName()
                }
    		});
            
            if (message.getParameters().major)
            {
            	// Reload node
            	var node = this._tree.getStore().getNodeById(tagId);
                if (node !== null)
                {
                	node.set('expanded', true)
                	node.commit();
                	this._tree.getStore().load({node: node});
                }
            }
        }
        
        var tagRootTarget = message.getTarget(this.getRootTagTargetMessage());
        if (tagRootTarget != null)
        {
            var tagRootNode = this._tree.getStore().getNodeById(tagRootTarget.getParameters().id);
            if (tagRootNode != null)
            {
            	// Reload whole tree
                this._tree.getStore().load({ node: tagRootNode });
            }
        }
    },
    
    /**
     * Update a tag which has been modified
     * @param {Object} response The JSON server response
     * @private
     */
    _updateTag : function (response)
    {
        var id = response['id'];
        var node = this._tree.getStore().getNodeById(id);
        if (node !== null)
        {
            node.beginEdit();
            node.set('name', response['name']);
            node.set('title', response['title']);
            node.set('description', response['description']);
            node.set('priority', response['priority']);
            
            this._updateAdditionalInfo(node, response);
            
            node.endEdit();
            node.commit();
            
            this._tree.getStore().sort();
        }
    },
    
    /**
     * @template
     * @protected
     * Update a additional info of the tag which has been modified
     * @param {Object} node The node to update
     * @param {Object} response The JSON server response
     */
    _updateAdditionalInfo: function (node, response)
    {
        node.set('visibility', response['visibility']);
        node.set('target', response['target']);
        node.set('target-info', response['target-info']);
        node.set('color', response['color']);
        node.set('class', response['class']);
        node.set('isColorable', response['isColorable']);
    },

    /**
     * Listener on {@link Ametys.message.Message#DELETED} message. 
     * If the tool is concerned by the created object, the node will be refreshed.
     * @param {Ametys.message.Message} message The deleted message.
     * @private
     */
    _onTagDeleted : function(message)
    {
        var tagTarget = message.getTarget(this.getTagTargetMessage());
        if (tagTarget != null)
        {
            this._deleteNode(tagTarget.getParameters().id)
        }
    },
    
    /**
     * @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 cfg = this.getMessageTargetConfiguration(item.records[i]);
            if (cfg != null)
            {
                targets.push(cfg);
            }
        }
    
        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.   
     */ 
    getDropInfo: function(targetRecords, item)
    {
        var targets = [];
        
        for (var i = 0; i < targetRecords.length; i++)
        {
            var cfg = this.getMessageTargetConfiguration(targetRecords[i]);
            if (cfg != null)
            {
                targets.push(cfg);
            }
        }

        if (targets.length > 0)
        {
            item.target = {
                relationTypes: [Ametys.relation.Relation.MOVE], 
                targets: targets
            };
        }
    },
    
    /**
     * @private
     * Called by #getDragInfo, #getDragInfo or by tools to send the current selection
     * @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
        {
        	return {
                id: record.isRoot() ? this.getRootTagTargetMessage() : this.getTagTargetMessage(),
                parameters: {
                    name: record.isRoot() ? '' : record.get('name'),
                    id: record.getId()
                }
            }
        }
    }
});

Ext.define("Ametys.message.TagMessageTarget",
    {
		override: "Ametys.message.MessageTarget",

	    statics: 
	    {
	        /**
	         * @member Ametys.message.MessageTarget
	         * @readonly
	         * @property {String} TAG The target type is a tag. The expected parameters are:
	         * @property {String} TAG.id The id of tag
	         * @property {String} TAG.parentId The id of the parent of the tag
	         * @property {String} TAG.name The name of tag
	         */
	        TAG: "tag",
	
	        /**
	         * @member Ametys.message.MessageTarget
	         * @readonly
	         * @property {String} TAG_ROOT The target type is a tag root. The expected parameters are:
	         * @property {String} TAG_ROOT.id The id of root tag
	         * @property {String} TAG_ROOT.name The name of root tag 
	         */
	        TAG_ROOT: "tag-root"
	    }
    }
);