/*
 *  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 is a relation handler between:
 * * source : content
 * * destination : content
 * 
 * It will establish a relation between those contents by set the attribute of type compatible.
 * E.g. when you drag a content on another content
 * @private
 */
Ext.define('Ametys.plugins.cms.relations.SetContentAttributeRelationHandler', {
    extend: 'Ametys.relation.RelationHandler',
    
    /**
     * @cfg {Number} [workflow-action-ids=2] The workflow action ids to use when modifying contents. A comma separated list of ids, that will be tested in that order. Should not be empty.
     */
    /**
     * @property {String[]} _workflowActionIds See #cfg-workflow-action-ids
     */
    
    /** 
     * @protected
     * @property {RegExp} sourceTargetType The message target type (of type content) of the source to handle
     */
    sourceTargetType: /^(simple-)?content$/,
    
    /**
     * @protected
     * @property {RegExp} destTargetType The message target type (of type content) of the target to handle
     */
    destTargetType: /^(simple-)?content$/,
    
    constructor: function(config)
    {
        this.callParent(arguments);
        this._workflowActionIds = (config['workflow-action-ids'] || "2").split(",");
    },
    
    /**
     * @private
     * Revert the #targetId test
     * @param {Object/Ametys.message.MessageTarget} target The target to test
     * @return {Boolean} True if the target type does not match #targetId
     */
    _nonMatchingSource: function(target)
    {
        return !this.sourceTargetType.test(target.isMessageTarget ? target.getId() : target.id)
    },
    
    /**
     * @private
     * Revert the #targetId test
     * @param {Object/Ametys.message.MessageTarget} target The target to test
     * @return {Boolean} True if the target type does not match #targetId
     */
    _nonMatchingDest: function(target)
    {
        return !this.destTargetType.test(target.isMessageTarget ? target.getId() : target.id)
    },
    
    /**
     * Get the workflow actions ids. See #cfg-workflow-action-ids
     * @return {String[]} A non-null and non empty array of workflow action ids.
     */
    getWorkflowActionIds: function()
    {
        return this._workflowActionIds;
    },
    
    supportedRelations: function(source, target)
    {
        // Are source contents and only contents ?
        var sourceContentTargets = Ametys.message.MessageTargetHelper.findTargets(source.targets, this.sourceTargetType, 1);
        if (sourceContentTargets.length == 0 || Ametys.message.MessageTargetHelper.findTargets(source.targets, Ext.bind(this._nonMatchingSource, this)).length > 0)
        {
            return [];
        }

        // Are target contents and only contents ?
        var destContentTargets = Ametys.message.MessageTargetHelper.findTargets(target.targets, this.destTargetType, 1);
        if (destContentTargets.length == 0 || Ametys.message.MessageTargetHelper.findTargets(target.targets, Ext.bind(this._nonMatchingDest, this)).length > 0)
        {
            return [];
        }
        
        // Is there at least one target editable ?
        var authorized = false;
        for (var i = 0; i < destContentTargets.length; i++)
        {
            if (destContentTargets[i].isMessageTarget && destContentTargets[i].getParameters().availableActions)
            {
                for (var w=0; w < this._workflowActionIds.length; w++)
                {
                    if (Ext.Array.contains(destContentTargets[i].getParameters().availableActions, parseInt(this._workflowActionIds[w])))
                    {
                        authorized = true;
                    }
                }
            }
            else
            {
                // Can not check available actions, in doubt authorize the relationship (it will be eventually blocked later)
                authorized = true;
            }
        }
        
        if (!authorized)
        {
            return [];
        }
        
        // Are all sources movable ?
        var movable = true;
        for (var i = 0; movable && i < sourceContentTargets.length; i++)
        {
            if (sourceContentTargets[i].isMessageTarget 
                && (sourceContentTargets[i].getParameters()['parentNode'] == null || sourceContentTargets[i].getParameters()['parentMetadataPath'] == null))
            {
                movable = false;
            }
        }

        var relations = [];
        relations.push(
                Ext.create('Ametys.relation.Relation', {
                    type: Ametys.relation.Relation.REFERENCE,
                    label: "{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_LABEL}}",
                    description: "{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_DESCRIPTION}}",
                    smallIcon: null,
                    mediumIcon: null,
                    largeIcon: null
                })
        );
        
        if (movable)
        {
            relations.push(
                    Ext.create('Ametys.relation.Relation', {
                        type: Ametys.relation.Relation.MOVE,
                        label: "{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_MOVE_LABEL}}",
                        description: "{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_MOVE_DESCRIPTION}}",
                        smallIcon: null,
                        mediumIcon: null,
                        largeIcon: null
                    })
            );
        }
        
        return relations;
    },
    
    link: function(source, target, callback, relationType)
    {
        var positionInTargets = target.positionInTargets,
            targetNodeId = target.getTarget(this.destTargetType, 1).getParameters().id;
        
        var contentIdsToEditToRemove = [];
        if (relationType == Ametys.relation.Relation.MOVE)
        {
            Ext.Array.each(source.getTargets(this.sourceTargetType, 1), function(sourceMessageTarget) {
                var sourceMessageTargetParams = sourceMessageTarget.getParameters(),
                    parentNode = sourceMessageTargetParams.parentNode;
                if (positionInTargets < 0 || targetNodeId != parentNode/* it is not a reorder (dropPosition=="append"), OR it is a reorder (dropPosition=="after"/"before") but with a change of parentNode*/)
                {
                    contentIdsToEditToRemove.push({
                        referencingAttributePath: sourceMessageTargetParams.parentMetadataPath,
                        contentId: parentNode,
                        valueToRemove: sourceMessageTargetParams.id
                    });
                }
            });
        }
        
        var contentIdsToReference = [];
        Ext.Array.each(source.getTargets(this.sourceTargetType, 1), function(sourceMessageTarget) {
            contentIdsToReference.push(sourceMessageTarget.getParameters().id);
        });
        
        var contentIdsToEditByType = {};

        var contentIdsToEdit = [];
        Ext.Array.each(target.getTargets(this.destTargetType, 1), function(targetMessageTarget) {
            contentIdsToEdit.push(targetMessageTarget.getParameters().id);
            
            contentIdsToEditByType[targetMessageTarget.getId()] = contentIdsToEditByType[targetMessageTarget.getId()] || [];
            contentIdsToEditByType[targetMessageTarget.getId()].push(targetMessageTarget.getParameters().id)
        });
        
        this.serverCall("getCompatibleAttributes", 
                        [contentIdsToReference, contentIdsToEdit], 
                        this._linkCb, 
                        { 
                            waitMessage: true, 
                            ignoreCallbackOnError: false, 
                            arguments: [callback, contentIdsToReference, contentIdsToEdit, positionInTargets, contentIdsToEditByType, contentIdsToEditToRemove, source, target]
                        }
        );
    },
    
    /**
     * @private
     * The callback when #link is called
     * @param {Object} compatibleAttributes The compatible attributes as JSON (response from server)
     * @param {Object[]} args The passed arguments
     */
    _linkCb: function(compatibleAttributes, args)
    {
        var callback = args[0];
        var contentIdsToReference = args[1];
        var contentIdsToEdit = args[2];
        var positionInTargets = args[3];
        var contentIdsToEditByType = args[4];
        var contentIdsToEditToRemove = args[5];
        var source = args[6];
        var target = args[7];
        
        var attributeForced = false;
        
        // Should attributes be limited by target?
        if (compatibleAttributes.length > 0)
        {
            // Compute the authorized attributes
            var childrenAttributePaths = [];
            Ext.Array.each(target.targets, function(oneTarget) {
                childrenAttributePaths = Ext.Array.merge(childrenAttributePaths, oneTarget.getParameters()['childrenMetadataPaths'] || []);
            });
            childrenAttributePaths = Ext.Array.unique(childrenAttributePaths);
            
            // Filter the authorized metadata
            if (childrenAttributePaths.length > 0)
            {
                compatibleAttributes = Ext.Array.filter(compatibleAttributes, function(item) {
                    return Ext.Array.contains(childrenAttributePaths, item.name);
                });
                attributeForced = true;
            }
        }
        
        if (compatibleAttributes.length == 1)
        {
            if (attributeForced)
            {
                this._linkChoiceCb(compatibleAttributes[0].path, callback, contentIdsToReference, contentIdsToEdit, positionInTargets, contentIdsToEditByType, contentIdsToEditToRemove);
            }
            else
            {
                Ametys.Msg.confirm("{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_CONFIRM_TITLE}}",
                        "{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_CONFIRM_TEXT1}} '" + compatibleAttributes[0].label + "' {{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_CONFIRM_TEXT2}}",
                        Ext.bind(this._linkChoiceCb, this, [compatibleAttributes[0].path, callback, contentIdsToReference, contentIdsToEdit, positionInTargets, contentIdsToEditByType, contentIdsToEditToRemove], 0));
            }
        }
        else if (compatibleAttributes.length > 1)
        {
            Ametys.plugins.cms.relations.setcontentattributerelationhandler.ChooseAttribute.display(compatibleAttributes, Ext.bind(this._linkChoiceCb, this, [callback, contentIdsToReference, contentIdsToEdit, positionInTargets, contentIdsToEditByType, contentIdsToEditToRemove], true));
        }
        else
        {
            // No attribute can be used
            Ametys.log.ErrorDialog.display({
                title: "{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_NOATTRIBUTE_TITLE}}",
                text: "{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_NOATTRIBUTE_TEXT}}",
                category: this.self.getName()
            });
            callback(false);
        }
    },
    
    /**
     * @protected
     * When the contributor needs to choose an attribute, this is the callback function
     * @param {String} attributePath The path of the chosen attribute or null if no choice was made
     * @param {Function} callback The initial callback
     * @param {String[]} contentIdsToReference The content ids to reference
     * @param {String[]} contentIdsToEdit The content ids to edit
     * @param {Number} positionInTargets The position in target if it is a reorder
     * @param {Object} contentIdsToEditByType The contentIdsToEdit to edit classified by message target type ('content', 'reference-table-content'...)
     * @param {Object} contentIdsToEditToRemove The contents that were referencing and that should not anymore
     * @param {String} contentIdsToEditToRemove.contentId The content id to remove
     * @param {String} contentIdsToEditToRemove.valueToRemove The value referenced to remove
     * @param {String} contentIdsToEditToRemove.referencingMetadataPath The attribute path referencing the value
     * @param {String} button If called from a confirm dialog, check this value
     */
    _linkChoiceCb: function(attributePath, callback, contentIdsToReference, contentIdsToEdit, positionInTargets, contentIdsToEditByType, contentIdsToEditToRemove, button)
    {
        if (attributePath == null || (button && (button == 'no' || button == 'cancel')))
        {
            callback(false);
            return;
        }

        var contentIdsToEditAsObj = {};
        Ext.Array.forEach(contentIdsToEdit, function(contentId) {
            contentIdsToEditAsObj[contentId] = positionInTargets;
        });
        
        this.serverCall("setContentAttribute", 
                [contentIdsToReference, contentIdsToEditAsObj, contentIdsToEditToRemove, attributePath, this._workflowActionIds], 
                this._linkCb2, 
                { 
                    waitMessage: true, 
                    ignoreCallbackOnError: false, 
                    arguments: [callback, contentIdsToReference, contentIdsToEdit, contentIdsToEditByType, contentIdsToEditToRemove]
                }
        );
    },
    
    /**
     * @private
     * The callback when #link is called
     * @param {Object} response The JSON java response
     * @param {Object[]} args The passed arguments
     */
    _linkCb2: function(response, args)
    {
        var callback = args[0];
        var contentIdsToReference = args[1];
        var contentIdsToEdit = Ext.Object.getKeys(args[2]);
        var contentIdsToEditByType = args[3];
        var contentIdsToEditToRemove = args[4];
        
        if (response == null)
        {
            callback(false);
        }
        else
        {
            var errors = response['errorMessages'];
            if (errors && errors.length > 0)
            {
                var detailedMsg = '<ul>';
                for (var i=0; i < errors.length; i++)
                {
                    detailedMsg += '<li>' + errors[i] + '</li>';
                }
                detailedMsg += '</ul>';
                
                Ametys.form.SaveHelper.SaveErrorDialog.showErrorDialog ("{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_ERROR_TITLE}}", "{{i18n PLUGINS_CMS_RELATIONS_SETCONTENTATTRIBUTE_REFERENCE_ERROR_TEXT}}", detailedMsg);
            }

            callback(response['success'] == true);

            var errorLessContents = [];
            
            for (var contentId in contentIdsToEditToRemove)
            {
                if (!response['errorIds'] || !Ext.Array.contains(response['errorIds'], contentIdsToEditToRemove[contentId]))
                {
                    errorLessContents.push(contentIdsToEditToRemove[contentId].contentId);
                }                   
            }
            
            for (var type in contentIdsToEditByType)
            {
                for (var contentId in contentIdsToEditByType[type])
                {
                    if (!response['errorIds'] || !Ext.Array.contains(response['errorIds'], contentIdsToEditByType[type][contentId]))
                    {
                        errorLessContents.push(contentIdsToEditByType[type][contentId]);
                    }                   
                }
            }
            
            if (errorLessContents.length > 0)
            {
                Ext.create("Ametys.message.Message", {
                    type: Ametys.message.Message.MODIFIED,
                    
                    targets: {
                        id: type,
                        parameters: { ids: errorLessContents}
                    },
                    
                    parameters: {
                        major: false
                    }
                });
            }
        }
    }
});