/*
 *  Copyright 2016 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.
 */

/**
 * Dialog box used to select one or more tags
 * @private
 */
Ext.define('Ametys.cms.uihelper.ChooseTagHelper', {
    singleton: true,

    /**
     * @property {Object} initialConfig The initial configuration object passed to the #open method:
     * @property {Boolean} [initialConfig.multiple=false] True to allow multiple selections
     * @property {Boolean} [initialConfig.onlyCustomTags=false] If true, only custom tags (JCR) are displayed
     * @property {Boolean} [initialConfig.onlyTagsWithChildren=false] If 'true', only tags with children can be selected.
     * @property {Boolean} [initialConfig.allowProviders=false] If 'true', tag providers are also be selected.
     * @property {Boolean} [initialConfig.allowCreation=false] If 'true', a link at the dialog bottom proposes to add a new tag (defaults to false).
     * @property {String} [initialConfig.targetType] The type of tags to display. If set to a value, display the tags of this target type. If set to null, display all tags.
     * @property {String} [initialConfig.filterTarget] Filter on target type. If set to a value, disable the tags of this target type. If set to null, enable all tags.
     * @property {String} [initialConfig.url="tags.json"] The url to get the tags informations
     * @property {String} [initialConfig.icon] The full path to icon (16x16 pixels) for the dialog box.
     * @property {String} [initialConfig.iconCls="ametysicon-label49"] The separated CSS class to apply for dialog icon. Use only if the 'icon' configuration is undefined.
     * @property {String} initialConfig.title The title of dialog box
     * @property {String} [initialConfig.helpMessage] The help message to display on top of dialog box.
     * @property {String[]} [initialConfig.values] the selected tags as an array of id.
     * @property {String[]} initialConfig.taggableObjects the ids of current objects to be tagged.
     * @property {Function} initialConfig.cbContextParametersFct the function to get context parameters to add to callback parameters
     * @property {String[]} initialConfig.callback The callback function invoked when tags are selected. The callback function will received the following parameters:
     * @property {Function} initialConfig.callback.tags The names of selected tags
     * @property {Object} initialConfig.callback.params The callback additional parameters
     */

    /**
     * @property {Ametys.window.DialogBox} _box The dialog box
     * @private
     */

    /**
     * @property {Ametys.plugins.cms.tag.TagsTreePanel} _tree The tags tree panel
     * @private
     */

    /**
     * Configure and open the dialog box
     * @param {Object} config The configuration options :
     * @param {String} [config.url=tags.json] The url to get the tags informations.
     * @param {String} [config.iconCls] The CSS class for the icon of the dialog box.
     * @param {String} [config.title] The title of the dialog box.
     * @param {String} [config.helpMessage] The help message to display on top of dialog box.
     * @param {String[]} [config.values] the selected tags as an array of id.
     * @param {String[]} [config.taggableObjects] the ids of current objects to be tagged.
     * @param {Boolean} [config.multiple=false] `true` to allow selecting multiple tags.
     * @param {Boolean} [config.onlyCustomTags=false] `true` to show only custom tags.
     * @param {Boolean} [config.allowProviders=false] `true` to allow selecting providers.
     * @param {Boolean} [config.allowCreation=false] `true` to allow the creation of new tags from the dialog box.
     * @param {Boolean} [config.onlyTagsWithChildren=false] `true` to prevent selection of tags without child.
     * @param {String} [config.targetType] The type of tags to display. Set to the type of target to display only tags of this type. Set to null to display all tags.
     * @param {String[]} [config.filterTarget] Filter on target type. Set to the type(s) of target to disable tags of this(those) type(s). Set to null to enable all tags.
     * @param {Object} [config.contextualParameters] Optional contextual parameters to be passed to the server requests
     * @param {Function} [config.cbContextParametersFct] the function to get context parameters to add to callback parameters
     * @param {Function} config.callback The callback function invoked when tags are selected. The callback function will received the following parameters:
     * @param {Function} config.callback.tags The names of selected tags
     * @param {Object} tree the tag panel tree
     * @param {Function} createAction the function to create tag
     * @param {String} createAction.parentId the parent tag id
     * @param {Function} createAction.callback the callback after creation
     */
    open: function (config, tree, createAction)
    {
        this.initialConfig = Ext.applyIf(config || {}, this.getDefaultConfig());
        
        this._clickEventRegistered = false;
        
        this._tree = tree || this.createTree();
        this._createAction = createAction || Ametys.plugins.cms.tag.TagActions.create;
        
        this._box = this._createDialogBox();
                              
	    this.initialize();    
        
    	this._box.show();
    },

    /**
     * @protected
     * Get the default configuration
     * @param {Object} config the default values for configuration object
     */
    getDefaultConfig: function(config)
    {
        return {
            title: "{{i18n PLUGINS_CMS_HELPER_CHOOSETAG_TITLE}}",
            url: "tags.json",
            plugin: "cms",
            multiple: false,
            onlyCustomTags: false,
            allowProviders: false,
            allowCreation: false,
            onlyTagsWithChildren: false,
            values: []
        }
    },
    
    /**
     * Function called after creating dialog box and before showing the dialog to initialize the tags tree.
     * The default implementation does nothing.
     * @protected
     */
    initialize: function ()
    {
        // Nothing to do
    },
    
    /**
     * @protected
     * Creates the tags' tree
     */
    createTree: function ()
    {
        var me = this;
        return Ext.create('Ametys.plugins.cms.tag.TagsTreePanel', {
            width: 320,
            height: 450,

            onlyCustomTags: this.initialConfig.onlyCustomTags,
            allowProviders: this.initialConfig.allowProviders,
            allowCreation: this.initialConfig.allowCreation,
            onlyTagsWithChildren: this.initialConfig.onlyTagsWithChildren,
            targetType: this.initialConfig.targetType,
            filterTarget: this.initialConfig.filterTarget,
            contextualParameters: this.initialConfig.contextualParameters,
            values: this.initialConfig.values,
            taggableObjects: this.initialConfig.taggableObjects,
            url: this.initialConfig.url,
            plugin: this.initialConfig.plugin,
            checkMode: this.initialConfig.multiple
        });
    },
    
    /**
     * Creates the dialog box
     * @return {Ametys.window.DialogBox} the dialog box
     */
    _createDialogBox: function()
    {
    	me = this;
        this._tree.addListener('load', this._onLoad, this, { single: true });
    	this._tree.addListener('selectionchange', Ext.bind(this._onSelectionChange, this));
    	this._tree.addListener('checkchange', function(node, checked) {
            if (me.multiple === true)
            {
                me._box.getDockedItems('toolbar[dock="bottom"]')[0].items.get(0).setDisabled(false);
            }
        });
    	
    	return Ext.create('Ametys.window.DialogBox', {
	    		title: this.initialConfig.title,
                icon: this.initialConfig.icon,
	    		iconCls: this.initialConfig.icon ? null : (this.initialConfig.iconCls || (this.initialConfig.multiple ? 'ametysicon-tag25' : 'ametysicon-label49')),
	    		width: 420,
	    		cls: 'ametys-dialogbox',
	    		bodyPadding: '0',
	    		
	    		items: {
	    			xtype: 'panel',
	    			layout: {
	    				type: 'vbox',
	    				align : 'stretch',
	    				pack  : 'start'
	    			},
	    			border: false,
	    			items : [{
	    				xtype: 'component',
	    				cls: 'a-text',
                        padding: '5',
	    				html: this.initialConfig.helpMessage || this._getDefaultHelpMessage()
	    			},
	    			this._tree,
	    			{
	    				xtype: 'component',
	    				cls: 'a-text link',
	    				html: "{{i18n plugin.cms:PLUGINS_CMS_HELPER_CHOOSETAG_NO_JCR_TAG_SELECTED}}",
	    				hidden: !this.initialConfig.allowCreation
	    			}]
	    		},
	    		
	    		
	    		closeAction: 'destroy',
	    		buttons : [{
	    			text :"{{i18n plugin.cms:PLUGINS_CMS_HELPER_CHOOSETAG_OKBUTTON}}",
	    			handler : Ext.bind(this._validate, this)
	    		}, {
	    			text :"{{i18n plugin.cms:PLUGINS_CMS_HELPER_CHOOSETAG_CANCELBUTTON}}",
	    			handler: Ext.bind(function() {this._box.close();}, this)
	    		}]
	    	});
    },
    
    /**
     * @private
     * Get the default help message to be displayed on top of dialog box
     * The default help message depends on 'multiple' and 'onlyTagsWithChildren' configuration's parameters
     * @return {String} The default help message
     */
    _getDefaultHelpMessage: function ()
    {
    	if (this.initialConfig.multiple)
    	{
    		return this.initialConfig.onlyTagsWithChildren ? "{{i18n plugin.cms:PLUGINS_CMS_HELPER_CHOOSETAG_SELECT_TAGS_WITH_CHILDREN_HELP_MESSAGE}}" : "{{i18n plugin.cms:PLUGINS_CMS_HELPER_CHOOSETAG_SELECT_TAGS_HELP_MESSAGE}}";
    	}
    	else
    	{
    		return this.initialConfig.onlyTagsWithChildren ? "{{i18n plugin.cms:PLUGINS_CMS_HELPER_CHOOSETAG_SELECT_TAG_WITH_CHILDREN_HELP_MESSAGE}}" : "{{i18n plugin.cms:PLUGINS_CMS_HELPER_CHOOSETAG_SELECT_TAG_HELP_MESSAGE}}";
    	}
    },

    /**
     * This method updates the dialog box according to the current selection
     * @param {Ext.selection.Model} sm The selection model
     * @param {Ext.data.Model[]} nodes The selected nodes
     * @private
     */
    _onSelectionChange: function(sm, nodes)
    {
        if (this.initialConfig.multiple === false)
        {
        	var isValidSelection = this._isSelectionValid();
            this._box.getDockedItems('toolbar[dock="bottom"]')[0].items.get(0).setDisabled(!isValidSelection);
        }
        this._isAllowedToCreateTag();
    },

    /**
     * Initialize the "ok" button of the dialog box on the first load.
     * Select and focus the first node of the tree as well
     * @param {Ext.data.Store} store The store
     * @param {Ext.data.Model[]} records the loaded records
     * @private
     */
    _onLoad: function(store, records)
    {
        for (var i = 0; i < records.length; i++)
        {
            if (records[i].isRoot())
            {
                this._box.getDockedItems('toolbar[dock="bottom"]')[0].items.get(0).setDisabled(true);
            }
        }
        
        var firstChild = this._tree.getRootNode().firstChild;
        if (firstChild)
    	{
        	var view = this._tree.getView();
        	view.select(firstChild);
        	view.focusNode(firstChild);
    	}
    },

    /**
     * Check if selection is valid according to the configuration parameters
     * @return {boolean} true if the selection is valid
     * @private
     */
    _isSelectionValid : function()
    {
        var selection = this._tree.getSelectionModel().getSelection();
        for (var s in selection)
        {
            if (this.initialConfig.allowProviders === false && selection[s].get('type') == 'provider')
            {
                return false;
            }
            if (this.initialConfig.onlyTagsWithChildren === true && selection[s].get('leaf') === true)
            {
                return false;
            }
            if (selection[s].get('disabled') === true)
            {
                return false;
            }
        }
        return true;
    },

    /**
     * This method retrieves the current selection, and set the tag creation link accordingly.
     * @private
     */
    _isAllowedToCreateTag: function()
    {
        var selection = this._tree.getSelectionModel().getSelection();
        var node = selection[0];
        if (node !== null)
        {
            var createCmp = this._box.items.get(0).child("component[cls~=link]");

            var providerNode = this._getParentProviderNode(node);
            if (providerNode == null || providerNode.get('creationAllowed') !== true)
            {
            	// Selected node is not part of JCR provider branch
            	createCmp.update("{{i18n PLUGINS_CMS_HELPER_CHOOSETAG_NO_JCR_TAG_SELECTED}}");
                this._clickEventRegistered = false;
            }
            else if (node.get('creationAllowed') === true)
            {
            	createCmp.update("<a class='action'>{{i18n PLUGINS_CMS_HELPER_CHOOSETAG_ADD_MESSAGE}}</a>");
                // add a click event listener on the <a class='action'> dom node to call the #_createTag method.
                if (!this._clickEventRegistered)
                {
                	createCmp.mon(createCmp.getEl(), 'click', this._createTag, this, {delegate: 'a.action'});
                    this._clickEventRegistered = true;
                }
            }
            else
            {
                this._clickEventRegistered = false;
                createCmp.update("{{i18n PLUGINS_CMS_HELPER_CHOOSETAG_ADD_NO_RIGHT}}");
            }
        }
    },

    /**
     * Get the provider parent node of given node
     * @param {Ext.data.NodeInterface} node the node
     * @return {Ext.data.NodeInterface} the provider parent node or null if not found
     */
    _getParentProviderNode: function (node)
    {
    	var parentNode = node;
    	while (parentNode != null)
    	{
    		if (parentNode.get('type') == 'provider')
    		{
    			return parentNode;
    		}
    		parentNode = parentNode.parentNode;
    	}
    	return null;
    },

    /**
     * Retrieve the current tree values, and call the callback function from the initial configuration sent to #open
     * @private
     */
    _validate: function ()
    {
        if (this.initialConfig.multiple === false)
        {
            var selected = [];
            var selection = this._tree.getSelectionModel().getSelection();
            for (var s in selection)
            {
                selected.push(selection[s].data.name);
            }
            this._box.close();
            if (Ext.isFunction(this.initialConfig.callback))
            {
                this.initialConfig.callback(selected, this._getCbContextParameters());
            }
        }
        else
        {
            this._box.close();
            if (Ext.isFunction(this.initialConfig.callback))
            {
                this.initialConfig.callback(this._tree.values, this._getCbContextParameters());
            }
        }
    },

    /**
     * Get callback context parameters
     * @private
     */
    _getCbContextParameters: function()
    {
    	if (Ext.isFunction(this.initialConfig.cbContextParametersFct))
    	{
    		return this.initialConfig.cbContextParametersFct();
    	}
    	
    	return {};
    },
    
    /**
     * Create a tag from the dialog box
     * @private
     */
    _createTag: function()
    {
        var selection = this._tree.getSelectionModel().getSelection();
        if (selection && selection.length > 0)
        {
            var id = selection[0].get('id');
            this._createAction(id, Ext.bind(this._onTagCreated, this));
        }
    },

    /**
     * Callback function after tag creation
     * @param {String} tagId The tag's id
     * @param {String} tagName The tag's name
     * @param {String} tagParentId The tag's parent id
     * @private
     */
    _onTagCreated: function (tagId, tagName, tagParentId)
    {
        var node = this._tree.getStore().getNodeById(tagParentId);
        if (node != null)
        {
            node.set('leaf', false);
            node.commit();

            this._tree.getStore().load({
                node : node,
                callback : function()
                {
                    Ext.defer(this._tree.expandNode, 200, this._tree, [ node, false, null ]);
                },
                scope : this
            });
        }
    }

});