/*
 *  Copyright 2013 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 class controls a ribbon button that will be responsible for a workflow action.
 * The button will be enabled only if the workflow action is available for current selection.
 * @private
 */
Ext.define('Ametys.plugins.cms.content.controller.SmartContentController', {
	extend: 'Ametys.ribbon.element.ui.ButtonController',
	
	/**
	 * @property {Boolean} _enabledOnUnlockOnly Not supported
	 * @protected
	 */
	
	/**
	 * @property {Number[]} _workflowActionId The workflow action id to execute
	 * @private
	 */
	
	/**
	 * @property {String[]} [_contentIds=[]] List of identifiers of contents concerned by the action of the controller
	 * @private
	 */
	
	constructor: function(config)
	{
        // Provide a default configuration for the no read description
        config['noread-start-description'] = config['noread-start-description'] || "{{i18n PLUGINS_CMS_SMART_CONTENT_CONTROLLER_NO_READ_DEFAULT_DESCRIPTION_START}}";
        config['noread-content-description'] = config['noread-content-description'] || "{{i18n PLUGINS_CMS_SMART_CONTENT_CONTROLLER_NO_READ_DEFAULT_DESCRIPTION_CONTENT}}";
        config['noread-end-description'] = config['noread-end-description'] || "{{i18n PLUGINS_CMS_SMART_CONTENT_CONTROLLER_NO_READ_DEFAULT_DESCRIPTION_END}}";
        
		this.callParent(arguments);
		
		// Disable property to prevent conflict with common controller
		this._enabledOnUnlockOnly = false;
		
        Ametys.message.MessageBus.on(Ametys.message.Message.WORKFLOW_CHANGING, this._onWorkflowChanging, this);
		Ametys.message.MessageBus.on(Ametys.message.Message.WORKFLOW_CHANGED, this._onWorkflowChanged, this);
		Ametys.message.MessageBus.on(Ametys.message.Message.LOCK_CHANGED, this.refreshIfMatchingMessage, this);
	},
	
    /**
     * @private
     * Listener on workflow changing. Set buttons in refreshing state
     * @param {Ametys.message.Message} message The bus message
     */
    _onWorkflowChanging: function (message)
    {
        if (this.updateTargetsInCurrentSelectionTargets (message))
        {
            this.refreshing();
        }
    },
    
    /**
     * @private
     * Listener on workflow changed. Stop the refreshing state and refresh
     * @param {Ametys.message.Message} message The bus message
     */
    _onWorkflowChanged: function (message)
    {
        if (this.updateTargetsInCurrentSelectionTargets (message))
        {
            this.stopRefreshing();
            this.refresh(true);
        }
    },
        
	/**
	 * Get the workflow action id to be executed by the action of the controller
	 * @return {Number} the workflow action id
	 */
	getWorkflowActionId: function ()
	{
		return this._workflowActionId;
	},
	
	/**
	 * Get the ids of contents concerned by the action of the controller
	 * @return {String[]} the ids of contents 
	 */
	getContentIds: function ()
	{
		return this._contentIds;
	},
	
	updateState: function ()
	{
        this.stopRefreshing(true);
            
		this._getStatus(this.getMatchingTargets());
	},
	
	/**
	 * Refresh the controller from the content informations state of given targets
	 * @param targets The content targets
	 * @protected
	 */
	_getStatus: function (targets)
	{
        var result = this._calculateStatus(targets);
        this._getStatusCb(result, targets);
	},
    /**
     * @protected
     * Simulation of getStatus server side, done only client-side.
     * In cases where the server side still needs to be called, see Ametys.plugins.cms.content.controller.SmartContentController#_getStatus
     * @param {Object[]} targets see Ametys.ribbon.element.ui.CommonController#getMatchingTargets
     * @return {Object} a map of arrays representing contents in different states. default is unmodifiable-contents, locked-contents, noright-contents, invalidworkflowaction-contents,invalidworkflowstep-contents, allright-contents  
     */
    _calculateStatus: function(targets)
    {
        var result = {};
        result["unmodifiable-contents"] = [];
        result["locked-contents"] = [];
        result["noright-contents"] = [];
        result["invalidworkflowaction-contents"] = [];
        result["invalidworkflowstep-contents"] = [];
        result["allright-contents"] = [];

        var enabledOnModifiableOnly = this.getConfig("enabled-on-modifiable-only") == "true";
        var enabledOnUnlockOnly = this.getConfig("enabled-on-unlock-only") == "true";
        var enabledOnRightOnly = this.getConfig("enabled-on-right-only") == "true";
        var enabledOnWorkflowActionOnly = this.getConfig("enabled-on-workflow-action-only");
        var enabledOnWorkflowStepOnly = this.getConfig("enabled-on-workflow-step-only");
        
        // Perform the same check than the server except for the read access
        // There is no use in trying to hide information that is already there
        Ext.Array.each(targets, function(matchingTarget)
            {
                var parameters = matchingTarget.getParameters();
                if (parameters && parameters.content)
                {
                    var content = parameters.content;
                    
                    var error = false;
                    // "enabled-on-modifiable-only" == true && ! content instanceof ModifiableContent (isModifiable)
                    if (enabledOnModifiableOnly && !content.getIsModifiable())
                    {
                        var i18nStr = this.getConfig("nomodifiable-content-description");
                        var description = Ext.String.format(i18nStr, content.getTitle());
                        var contentParam = this._getContentDefaultParameters(content);
                        contentParam["description"] = description;
                        result["unmodifiable-contents"].push(contentParam);
                        error = true;
                    }

                    // "enabled-on-unlock-only" == true && isLocked
                    if (enabledOnUnlockOnly && content.getLocked() && !this._isLockOwner(content))
                    {
                        var i18nStr = this.getConfig("locked-content-description");
                        var lockOwner = content.getLockOwner();
                        var fullName = lockOwner != null ? lockOwner.fullname : "";
                        var login = lockOwner != null ? lockOwner.login : "Anonymous";
                        var description = Ext.String.format(i18nStr, content.getTitle(), fullName, login);
                        var contentParam = this._getContentDefaultParameters(content);
                        contentParam["description"] = description;
                        result["locked-contents"].push(contentParam);
                        error = true;
                    }

                    // "enabled-on-right-only" == true && !hasRight (rights empty or RIGHT_ALLOW on the requested rights)
                    if (enabledOnRightOnly && !this._hasRight(content.getRights()))
                    {
                        var i18nStr = this.getConfig("noright-content-description");
                        var description = Ext.String.format(i18nStr, content.getTitle());
                        var contentParam = this._getContentDefaultParameters(content);
                        contentParam["description"] = description;
                        result["noright-contents"].push(contentParam);
                        error = true;
                    }
                    
                    // "enabled-on-workflow-action-only" not empty && this value is not in availableActions
                    if (enabledOnWorkflowActionOnly != null)
                    {
                        var actionId = this._workflowAction(content.getAvailableActions(), enabledOnWorkflowActionOnly);
                        if (actionId == -1)
                        {
                            var i18nStr = this.getConfig("workflowaction-content-description");
                            var description = Ext.String.format(i18nStr, content.getTitle());
                            var contentParam = this._getContentDefaultParameters(content);
                            contentParam["description"] = description;
                            result["invalidworkflowaction-contents"].push(contentParam);
                            error = true;
                        }
                        else
                        {
                            result["workflowaction-content-actionId"] = actionId;
                        }
                    }

                    // "enabled-on-workflow-step-only" not empty && this value is not in the current steps
                    if (enabledOnWorkflowStepOnly != null && !this._workflowStep(content.getWorkflowSteps(), enabledOnWorkflowStepOnly))
                    {
                        var i18nStr = this.getConfig("workflowstep-content-description");
                        var description = Ext.String.format(i18nStr, content.getTitle());
                        var contentParam = this._getContentDefaultParameters(content);
                        contentParam["description"] = description;
                        result["invalidworkflowstep-contents"].push(contentParam);
                        error = true;
                    }
                    
                    if (!error)
                    {
                        var i18nStr = this.getConfig("allright-content-description");
                        var description = Ext.String.format(i18nStr, content.getTitle());
                        var contentParam = this._getContentDefaultParameters(content);
                        contentParam["description"] = description;
                        result["allright-contents"].push(contentParam);
                    }
                    
                }
                
            }, this);
        return result;
    },
    
    /**
     * @protected
     * Override to bypass rights's checking on selected targets (will be handled on #updateState)
     * Indeed if #hasRightOnAny return false, the button state and tooltip is not updated as it considers that no selection matched. 
     */
    hasRightOnAny: function(targets)
    {
        return true;
    },
    /**
     * @private
     * Check if a least one required rights in is the available rights
     * @param {String[]} rights A array of available rights
     * @return {Boolean} true if a least one required rights in is the available rights
     */
    _hasRight: function(rights)
    {
        var rightToCheck = this.getInitialConfig("rights");
            
        if (!rightToCheck)
        {
            // No right is needed: ok.
            return true;
        }
            
        var neededRights = rightToCheck.split('|');
        for (var i=0; i < neededRights.length; i++)
        {
            if (Ext.Array.contains(rights, neededRights[i]))
            {
                return true;
            }
        }
        
        // No right found
        return false;
    },
    /**
     * @private
     * Check if a least one wanted step in is the available steps
     * @param {Number[]} availableSteps A array of workflow steps available
     * @param {String} wantedStepsString A comma separated list of required steps
     * @return {Boolean} true if a least one wanted step in is the available steps
     */
    _workflowStep: function(availableSteps, wantedStepsString)
    {
        var result = this._isInArray(availableSteps, wantedStepsString);
        return result != null;
    },
    /**
     * @private
     * Get the first wanted action that is available
     * @param {Number[]} availableActions A array of workflow actions available
     * @param {String} wantedActionsString A comma separated list of required actions
     * @return {Number} The first action wanted that is also available or -1 if non is matching.
     */
    _workflowAction: function(availableActions, wantedActionsString)
    {
        var result = this._isInArray(availableActions, wantedActionsString);
        return result != null ? result : -1;
    },
    /**
     * @private
     * Get the first wanted value that is available
     * @param {Number[]} available A array of available values
     * @param {String} wantedAsString A comma separated list of required values
     * @return {Number} The first value wanted that is also available or null if non matches.
     */
    _isInArray: function(available, wantedAsString)
    {
        var wanted = wantedAsString.split(",");

        for (var i = 0; i < wanted.length; i++) 
        {
            var iWanted = Ext.String.trim(wanted[i]);
            if (!Ext.isEmpty(iWanted))
            { 
                var id = parseInt(wanted[i]);
                if (Ext.Array.contains(available, id))
                {
                    return  id;
                }
            }
        }
        
        return null;  
    },
    /**
     * @private
     * Get the minimal informations: id and title.
     * @param {Ametys.cms.content.Content} content The non null content to analyse
     * @return {Object} A map with 'id' and 'title'
     */
    _getContentDefaultParameters : function(content)
    {
        var contentParams = {};
        contentParams["id"] = content.getId();
        contentParams["title"] = content.getTitle();
        return contentParams;
    },
	
	/**
	 * @protected
	 * Callback function called after retrieving the lock state of content targets
	 * @param params The JSON result 
	 */
	_getStatusCb: function (params, targets)
	{
		this._contentIds = [];
		
		var description = '';
		
		var allRightContents = params['allright-contents'];
		if (allRightContents.length > 0)
		{
			for (var i=0; i < allRightContents.length; i++)
			{
				this._contentIds.push(allRightContents[i].id)
			}
			this.enable();
		}
		else
		{
			description = this.getInitialConfig("error-description") || '';
			this.disable();
		}
        
        // server check only returns id in case of no read
        // we need to build the params ourself based on targets
        if (params['noread-contents'])
        {
            params['noread-contents'] = this._convertIdsToParams(params['noread-contents'], this.getInitialConfig("noread-content-description"), targets || this.getMatchingTargets());
        }
		
		this._updateTooltipDescription (description, params);
		
		this.toggle(allRightContents.length > 0 && this.getInitialConfig()["enable-toggle"] == "true");
		
		this._workflowActionId = params['workflowaction-content-actionId'];
	},
    
    /**
     * Use the current matching targets to transform a list of content ids
     * into a list of parameters to generate a tooltip
     */
    _convertIdsToParams: function(contentIds, contentDescription, targets)
    {
        let params = [];
        if (contentIds && contentIds.length > 0)
        {
            // let's try to use the target to retrieve the title of faulty contents
            // and build a fake param list to generate the description
            for (var i=0; i < contentIds.length; i++)
            {
                let matchingContent = null;
                for (var j=0; j < targets.length; j++)
                {
                    if (targets[j].getParameters().id == contentIds[i])
                    {
                        matchingContent = targets[j].getParameters().content;
                        break;
                    }
                }
                
                if (matchingContent != null)
                {
                    var contentParam = this._getContentDefaultParameters(matchingContent);
                    contentParam.description = Ext.String.format(contentDescription, matchingContent.getTitle());
                    params.push(contentParam);
                }
                // Couldn't use the target as fallback, provide basic params with the id
                else
                {
                    params.push({
                        id: contentIds[i],
                        description: Ext.String.format(contentDescription, contentIds[i])
                    })
                }
            }
        }
        
        return params;
    },
	
	/**
	 * @protected
	 * Update the tooltip description according state of the current selection
	 * @param description The initial description. Can be empty.
	 * @param params The JSON result received
	 */
	_updateTooltipDescription: function (description, params)
	{
		description = this._handlingMultiple (description, 'allright', params['allright-contents']);
        description = this._handlingMultiple (description, 'noread', params['noread-contents']);
		description = this._handlingMultiple(description, "locked", params['locked-contents']);
		description = this._handlingMultiple(description, "noright", params['noright-contents']);
		description = this._handlingMultiple(description, "nomodifiable", params['unmodifiable-contents']);
		description = this._handlingMultiple(description, "workflowaction", params['invalidworkflowaction-contents']);
		description = this._handlingMultiple(description, "workflowstep", params['invalidworkflowstep-contents']);
		
		this.setDescription (description);
	},
	
	/**
	 * @private
	 * Add text to description
	 * @param description The initial description to concatenate. Can be empty.
	 * @param {String} prefix The parameters prefix to used to retrieve the start and end description. The start and end description are retrieved from initial configuration with [prefix]-start-description and [prefix]-end-description
	 * @param {Object[]} contents The concerned contents. If empty, no text will be concatenated
	 */
	_handlingMultiple: function(description, prefix, contents)
	{
		if (contents && contents.length > 0)
		{
			if (description != "")
			{
				description += "<br/><br/>";
			}
			
			description += this.getInitialConfig(prefix + "-start-description");
			for (var i=0; i < contents.length; i++)
			{
				if (i != 0) 
				{
					description += ", ";
				}
				description += contents[i].description;
			}
			description += this.getInitialConfig(prefix + "-end-description");
		}
		
		return description;
	},
    
    /**
     * Test if the current user is the lock owner of the content
     * @param {Ametys.cms.content.Content} content
     * @return {boolean} containing id and title
     */
    _isLockOwner: function (content)
    {
        var currentUser = Ametys.getAppParameter('user');
        var lockOwner = content.getLockOwner();
        return currentUser.login === lockOwner.login && currentUser.population === lockOwner.populationId;
    },
});