/*
 *  Copyright 2024 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.
 */
 
 /**
 * Rule actions
 * @private
 */

Ext.define('Ametys.plugins.odf.pilotage.actions.RuleActions', {
    singleton: true,
    
    /**
     * Opens the thematics tool for a given program.
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function.
     */
    preview: function(controller)
    {
        var target = controller.getMatchingTargets()[0];
        var params = {
            id: target.getParameters().id,
            title: target.getParameters().title
        };
        
        Ametys.tool.ToolsManager.openTool('uitool-rules', params);
    },
    
    /**
     * Action function to be called by the controller.
     * Will open the dialog box to edit additional thematics
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     */
    editAdditionalThematics: function(controller)
    {
        var contentTarget = controller.getMatchingTargets()[0];
        var containerId = contentTarget.getParameters().id;
        
        Ametys.cms.content.ContentDAO.getContent(containerId, Ext.bind(this._editAdditionalThematicsCB, this));
    },
    
    /**
     * Callback to edit additional thematics
     * @param {Content} content The content to edit
     */
    _editAdditionalThematicsCB: function(content)
    {
        var openParams = {
            content: content,
            editWorkflowActionId: 2,
            workflowName: "container",
            viewName: "thematics-edition",
            width: Math.min(1000, window.innerWidth * 0.9),
            dialogConfig: {
                height: window.innerHeight * 0.90,
            }
        }
        
        Ametys.cms.uihelper.EditContent.open(openParams, null, this);
    },
    
    /**
     * Will add an additional rule to the selected thematic
     * @param {String} containerId The container id
     * @param {String} thematicId The thematic id
     */
    addRule: function (containerId, thematicId)
    {
        this._containerId = containerId;
        this._thematicId = thematicId;
        this._ruleId = null;
        
        this._ruleForm = this._ruleForm || this._createRuleForm();
        this._ruleBox = this._ruleBox || this._createRuleDialogBox();
        
        this._ruleForm.reset();
        this._ruleBox.show();
    },
    
    /**
     * Will edit an additional rule to the selected thematic
     * @param {String} containerId The container id
     * @param {String} thematicId The thematic id
     * @param {String} ruleId The rule id
     */
    editRule: function (containerId, thematicId, ruleId)
    {
        this._containerId = containerId;
        this._thematicId = thematicId;
        this._ruleId = ruleId;
        
        Ametys.data.ServerComm.callMethod({
            role: "org.ametys.plugins.odfpilotage.rule.RulesManager",
            methodName: "getRuleValues",
            parameters: [this._containerId, this._ruleId],
            callback: {
                handler: this._editRuleCB,
                scope: this
            },
            waitMessage: true
        });
    },
    
    /**
     * @private
     * Callback to edit rule after getting values
     * @param {Object} response the response
     * @param {Object} response.values the rule values
     */
    _editRuleCB: function(response)
    {
        this._ruleForm = this._ruleForm || this._createRuleForm();
        this._ruleBox = this._ruleBox || this._createRuleDialogBox();
        
        this._ruleForm.reset();
        this._ruleForm.setValues(response);
        
        this._ruleBox.show();
    },
    
    /**
     * @private
     * Create the rule form
     */
    _createRuleForm: function()
    {
        var elementsConfiguration = {
            text: this._richTextConfiguration("{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_CREATE_RULE_TEXT_LABEL}}", "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_CREATE_RULE_TEXT_DESC}}", "text", true),
            motivation: this._richTextConfiguration("{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_CREATE_RULE_MOTIVATION_LABEL}}", "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_CREATE_RULE_MOTIVATION_DESC}}", "motivation", false)
        }
        
        var form = Ext.create('Ametys.form.ConfigurableFormPanel', this._getConfigurableFormPanelConf("top"));
        form.additionalWidgetsConf.contentInfo = {
            contentId: this._containerId
        };
        
        form.configure(elementsConfiguration);
        form.setValues(null);
        
        return form;
    },
    
    /**
     * @private
     * Create the rule dialog box
     */
    _createRuleDialogBox: function()
    {
        return this._createDialogBox(
            "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_CREATE_RULE_DIALOG_TITLE}}", 
            "ametysicon-desktop-book",
            [this._ruleForm],
            Ext.bind(this._validateRule, this),
            function () {this._ruleBox.close();},
            true,
            false
        )
    },
    
    /**
     * @private
     * Validate the rule
     */
    _validateRule: function()
    {
        if (!this._ruleForm.isValid())
        {
            return;
        }
        
        var text = this._ruleForm.getField('content.input.text').getValue();
        var motivation = this._ruleForm.getField('content.input.motivation').getValue();
        
        if (this._ruleId)
        {
            Ametys.data.ServerComm.callMethod({
                role: "org.ametys.plugins.odfpilotage.rule.RulesManager",
                methodName: "editAdditionalRule",
                parameters: [this._containerId, this._ruleId, text, motivation],
                callback: {
                    handler: this._validateRuleCB,
                    scope: this
                },
                waitMessage: true
            });
        }
        else
        {
            Ametys.data.ServerComm.callMethod({
                role: "org.ametys.plugins.odfpilotage.rule.RulesManager",
                methodName: "addAdditionalRule",
                parameters: [this._containerId, this._thematicId, text, motivation],
                callback: {
                    handler: this._validateRuleCB,
                    scope: this
                },
                waitMessage: true
            });
        }
    },
    
    /**
     * @private
     * Callback after validate rule
     * @param {Object} response the response
     * @param {Object} response.ruleId the rule id
     */
    _validateRuleCB: function(response)
    {
        if (!this._handleError(response, "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_CREATE_OR_EDIT_RULE_ERROR}}"))
        {
            this._ruleBox.close();
            this._sendMessageBus(this._containerId, this._thematicId, "general", response.ruleId, "additional", false, false);
        }
    },
    
    /**
     * Will delete an additional rule to the selected thematic
     * @param {String} containerId The container id
     * @param {String} ruleId The rule id
     */
    deleteRule: function (containerId, ruleId)
    {
        this._containerId = containerId;
        var ruleId = ruleId;
        
        // Confirm deletion
        Ametys.Msg.confirm(
            "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_DELETE_RULE_CONFIRM_DIALOG_TITLE}}",
            Ext.String.format("{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_DELETE_RULE_CONFIRM_DIALOG_MSG}}", "<strong>" + ruleId + "</strong>"),
            function (answer)
            {
                if (answer == 'yes')
                {
                    Ametys.data.ServerComm.callMethod({
                        role: "org.ametys.plugins.odfpilotage.rule.RulesManager",
                        methodName: "deleteRule",
                        parameters: [this._containerId, ruleId],
                        callback: {
                            handler: this._deleteRuleCB,
                            scope: this
                        },
                        waitMessage: true
                    });
                }
            },
            this
        );
    },
    
    /**
     * @private
     * Callback after deleting rule
     * @param {Object} response the response
     * @param {Object} response.thematicId the thematic id containing the deleted rule
     */
    _deleteRuleCB: function(response)
    {
        if (!this._handleError(response, "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_DELETE_RULE_ERROR}}"))
        {
            this._sendMessageBus(this._containerId, response.thematicId, "general");
        }
    },
    
    /**
     * Will derogate a rule to the selected thematic
     * @param {String} containerId The container id
     * @param {String} thematicId The thematic id
     * @param {String} ruleId The rule id
     */
    derogateRule: function (containerId, thematicId, ruleId)
    {
        this._containerId = containerId;
        this._thematicId = thematicId;
        this._ruleId = ruleId;
        
        Ametys.data.ServerComm.callMethod({
            role: "org.ametys.plugins.odfpilotage.rule.RulesManager",
            methodName: "getRuleInfo",
            parameters: [this._containerId, this._thematicId, this._ruleId],
            callback: {
                handler: this._derogateRuleCB,
                scope: this
            },
            waitMessage: true
        });
    },
   
    /**
     * @private
     * Callback calling after getting rule information to derogate the rule
     * @param {Object} response the server response
     */
    _derogateRuleCB: function (response)
    {
        this._createDerogateRuleForms();
        this._derogateRuleBox = this._derogateRuleBox || this._createDerogateRuleDialogBox();
        this._derogateRuleBox.down('#code').update(this._ruleId);
        this._derogateRuleBox.down('#text').update(response.text);
        this._derogateRuleBox.down('#derogate-msg').update(Ext.String.format("{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_DEROGATE_TEXT_LABEL}}", "<strong>" + this._ruleId + "</strong>"));
        
        if (response.helpTextDerogation)
        {
            this._derogateRuleBox.down('#helpTextDerogation').update(response.helpTextDerogation);
        }
        if (response.helpTextMotivation)
        {
            this._derogateRuleBox.down('#helpTextMotivation').update(response.helpTextMotivation);
        }

        this._formDerogateText.reset();
        this._formDerogateText.setValues({values : {
            text: response.derogationText || response.text
        }});
        
        this._formDerogateMotif.reset();
        if (response.derogationMotivation)
        {
            this._formDerogateMotif.setValues({values : {
                motivation: response.derogationMotivation
            }});
        }
        
        this._derogateRuleBox.show();
    },
   
    /**
     * @private
     * Create the derogate rule forms
     */
    _createDerogateRuleForms: function()
    {
        if (!this._formDerogateText)
        {
            this._formDerogateText = Ext.create('Ametys.form.ConfigurableFormPanel', this._getConfigurableFormPanelConf("left"));
            this._formDerogateText.additionalWidgetsConf.contentInfo = {
                contentId: this._containerId
            };
            this._formDerogateText.configure({
                text: this._richTextConfiguration("", "", "text", true)
            });
            this._formDerogateText.setValues(null);
        }

        if (!this._formDerogateMotif)
        {
            this._formDerogateMotif = Ext.create('Ametys.form.ConfigurableFormPanel', this._getConfigurableFormPanelConf("left"));
            this._formDerogateMotif.additionalWidgetsConf.contentInfo = {
                contentId: this._containerId
            };
            this._formDerogateMotif.configure({
                motivation: this._richTextConfiguration("", "", "motivation", true)
            });
            this._formDerogateMotif.setValues(null);
        }        
    },
    
    /**
     * @private
     * Create the rule dialog box to derogate it
     */
    _createDerogateRuleDialogBox: function()
    {
        var items = [
            {
                xtype: 'component',
                cls: 'a-text a-text-rule',
                id: "derogate-msg",
                html: "",
            },
            {
                xtype: 'panel',
                layout: {
                    type: 'table',
                    columns: 2,
                },
                cls: 'rule-table',
                items: [
                    {
                        xtype: 'component',
                        cls: 'a-text rule-code',
                        id: 'code'
                    },
                    {
                        xtype: 'component',
                        cls: 'a-text rule-text',
                        id: 'text',
                        scrollable: true,
                        height: 150,
                    },
                ]
            },
            {
                xtype: 'component',
                cls: 'a-text a-text-derogation',
                id: 'helpTextDerogation',
                html: "<p>{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_DEROGATE_TEXT_HELP_DEFAULT}}</p>"
            },
            this._formDerogateText,
            {
                xtype: 'component',
                cls: 'a-text a-motivation',
                id: 'helpTextMotivation',
                html: "<p>{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_DEROGATE_MOTIVATION_HELP_DEFAULT}}</p>"
            },
            this._formDerogateMotif
        ];
        
        return this._createDialogBox(
            "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_DEROGATE_DIALOG_TITLE}}", 
            "ametysicon-desktop-book", 
            items,
            Ext.bind(this._validateDerogateRule, this),
            function () {this._derogateRuleBox.close();},
            true,
            false
        )
    },
    
    /**
     * @private
     * Validate the derogation for the rule
     */
    _validateDerogateRule: function()
    {
        if (!this._formDerogateText.isValid() || !this._formDerogateMotif.isValid())
        {
            return;
        }
        
        var text = this._formDerogateText.getField('content.input.text').getValue();
        var motivation = this._formDerogateMotif.getField('content.input.motivation').getValue();
        
        Ametys.data.ServerComm.callMethod({
            role: "org.ametys.plugins.odfpilotage.rule.RulesManager",
            methodName: "derogateRule",
            parameters: [this._containerId, this._thematicId, this._ruleId, text, motivation],
            callback: {
                handler: this._validateDerogateRuleCB,
                scope: this
            },
            waitMessage: true
        });
    },
    
    /**
     * @private
     * Callback after validate the derogation for the rule
     * @param {Object} response the response
     * @param {Object} response.ruleId the derogated rule id
     */
    _validateDerogateRuleCB: function(response)
    {
        if (!this._handleError(response, "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_DEROGATE_RULE_ERROR}}"))
        {
            this._sendMessageBus(this._containerId, this._thematicId, "general", response.ruleId, "general", true, true);
            this._derogateRuleBox.close();
        }
    },
    
    /**
     * Will cancel the derogation for a rule to the selected thematic
     * @param {String} containerId The container id
     * @param {String} thematicId The thematic id
     * @param {String} ruleId The rule id
     */
    deleteRuleDerogation: function(containerId, thematicId, ruleId)
    {
        this._containerId = containerId;
        this._thematicId = thematicId;
        this._ruleId = ruleId;
        
        // Confirm deletion
        Ametys.Msg.confirm(
            "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_DELETE_DEROGATE_CONFIRM_TITLE}}",
            Ext.String.format("{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_DELETE_DEROGATE_CONFIRM_MSG}}", "<strong>" + this._ruleId + "</strong>"),
            function (answer)
            {
                if (answer == 'yes')
                {
                    Ametys.data.ServerComm.callMethod({
                        role: "org.ametys.plugins.odfpilotage.rule.RulesManager",
                        methodName: "deleteRule",
                        parameters: [this._containerId, this._ruleId],
                        callback: {
                            handler: this._deleteRuleDerogationCB,
                            scope: this
                        },
                        waitMessage: true
                    });
                }
            },
            this
        );
    },
    
    /**
     * @private
     * Callback after delete the derogation for an rule
     */
    _deleteRuleDerogationCB: function(response)
    {
        if (!this._handleError(response, "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_DELETE_DEROGATE_RULE_ERROR}}"))
        {
            this._sendMessageBus(this._containerId, this._thematicId, "general", this._ruleId, "general", true, false);
        }
    },
    
    /**
     * Will copy rule derogation or the additional rule to a container
     * @param {String} containerId The container id
     * @param {String} thematicId The thematic id
     * @param {String} ruleId The rule id
     */
    copyRule: function(containerId, thematicId, ruleId)
    {
        this._containerId = containerId;
        this._thematicId = thematicId;
        this._ruleId = ruleId;
        
        Ametys.data.ServerComm.callMethod({
            role: "org.ametys.plugins.odfpilotage.rule.RulesManager",
            methodName: "getRuleInfo",
            parameters: [this._containerId, this._thematicId, this._ruleId],
            callback: {
                handler: this._copyRuleCB,
                scope: this
            },
            waitMessage: true
        });
    },
    
    /**
     * Callback called after getting rule information for copying rule
     * @param {Object} response the server response
     */
    _copyRuleCB: function(response)
    {
        this._copyRuleBox = this._createCopyRuleDialogBox(
            "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_RULE_DIALOG_TITLE}}",
            "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_RULE_HINT_MSG}}",
            Ext.bind(this._validateCopyRule, this), 
            true,
            false
        );
        
        this._clearFilter();
        this._copyRuleBox.down('#copy-code').update(this._ruleId);
        var html = response.derogationText;
        if (response.derogationMotivation)
        {
            html += "<strong>{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_RULE_DIALOG_MOTIVATION}}</strong><br/>" + response.derogationMotivation;
        }
        this._copyRuleBox.down('#copy-text').update(html);
        
        let mask = Ametys.mask.GlobalLoadMask.mask("{{i18n plugin.core-ui:PLUGINS_CORE_UI_LOADMASK_DEFAULT_MESSAGE}}");
        this._containerStore.load({
            callback: Ext.bind(function(){
                Ametys.mask.GlobalLoadMask.unmask(mask);
                if (this._containerStore.queryRecords().length)
                {
                    this._copyRuleBox.show();
                }
                else
                {
                    this._copyRuleBox.close();
                    Ametys.Msg.show({
                        title: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_RULE_DIALOG_TITLE}}",
                        msg: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_RULE_DIALOG_NO_CONTAINER_ERROR}}",
                        buttons: Ext.Msg.OK,
                        icon: Ext.Msg.INFO
                     });
                }
            }, this)
        });
    },
    
    /**
     * @private
     * Create the copy rule dialog box
     * @param {String} title the title
     * @param {String} helpText the help text
     * @param {Function} validateFn the validate function
     * @param {Boolean} withRule true to get the rule
     * @param {Boolean} isComplementary true if the dialog box is for complementary rules
     */
    _createCopyRuleDialogBox: function(title, helpText, validateFn, withRule, isComplementary)
    {
        this._containerStore = this._getGridStore();
        this._containerGrid = Ext.create('Ext.grid.Panel', {
            store: this._containerStore,
            itemId: "container-grid",
            columns: [
                { text: '{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_GRID_COLUMN_TITLE}}', dataIndex: 'title', flex: 1, renderer: isComplementary ? Ext.bind(this._renderComplementaryTitle, this) : Ext.bind(this._renderTitle, this) },
                { text: '{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_GRID_COLUMN_CODE}}', dataIndex: 'code'},
            ],
            flex: 1,
            selModel:{   
                checkOnly : true,
                mode:'MULTI',
                headerWidth: 50
            },
            selType: 'checkboxmodel',

            listeners: {
                'cellclick': function (grid, td, cellIndex, record) {
                    if (cellIndex != 0)
                    {
                        var selectionModel = grid.getSelectionModel();
                        if (selectionModel.isSelected(record))
                        {
                            selectionModel.deselect(record)
                        }
                        else
                        {
                            var selection = selectionModel.getSelection();
                            selection.push(record);
                            selectionModel.select(selection);
                        }
                    }
                }
            }
        });
        
        var filterText = Ext.create('Ext.form.field.Text', {
            cls: 'ametys',
            flex: 1,
            maxWidth: 300,
            itemId: 'search-filter-input',
            emptyText: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_FILTER}}",
            enableKeyEvents: true,
            minLength: 3,
            minLengthText: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_FILTER_INVALID}}",
            msgTarget: 'qtip',
            listeners: {change: Ext.Function.createBuffered(this._searchFilter, 500, this)},
            style: {
                marginRight: '0px'
            }
        })
        
        var items = [{
            xtype: 'component',
            cls: 'a-text',
            html: helpText
        }];
        if (withRule)
        {
            items.push({
                xtype: 'container',
                layout: {
                    type: 'table',
                    columns: 2,
                },
                
                id: 'rule-table',
                cls: 'rule-table',
                items: [
                    {
                        xtype: 'component',
                        cls: 'a-text rule-code',
                        id: 'copy-code'
                    },
                    {
                        xtype: 'component',
                        cls: 'a-text rule-text',
                        id: 'copy-text',
                        scrollable: true,
                        maxHeight: 200,
                    },
                ]
            });
        }
        items.push(
            {
                xtype: 'panel',
                cls: 'grid-filter',
                style: {
                    marginTop: '10px'
                },
                layout: { 
                    type: 'hbox', 
                },
                items: [
                    {
                        xtype: 'component',
                        flex: 0.000001
                    },
                    filterText,
                    {
                        // Clear filter
                        xtype: 'button',
                        tooltip: "{{i18n plugin.web:PLUGINS_WEB_UITOOL_SITEMAP_TREE_CLEAR_FILTER}}",
                        handler: Ext.bind (this._clearFilter, this),
                        iconCls: 'a-btn-glyph ametysicon-eraser11 size-16',
                    },
                ]
            },
            this._containerGrid
        );
        
        return this._createDialogBox(
            title, 
            "ametysicon-desktop-book",
            items,
            validateFn,
            function () {this._copyRuleBox.close();},
            false,
            true
        );
    },
    
    /**
     * @private
     * Render title for copy
     * @param {String} value the value
     * @param {Object} grid the grid
     * @param {Object} record the record
     */
    _renderTitle: function(value, grid, record)
    {
        return record.data.isDerogated
            ? value + '<span style="margin-left:5px;" class="ametysicon-sign-caution warning-icon" title="' + "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_RULE_WARNING_MSG}}" + '"></span>'
            : value;
    },
    
    /**
     * @private
     * Render title for copy complementary title
     * @param {String} value the value
     * @param {Object} grid the grid
     * @param {Object} record the record
     */
    _renderComplementaryTitle: function(value, grid, record)
    {
        return record.data.hasComplementaryRules
            ? value + '<span style="margin-left:5px;" class="ametysicon-sign-caution warning-icon" title="' + "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_COMPLEMENTARY_RULE_WARNING_MSG}}" + '"></span>'
            : value;
    },
    
    /**
     * This listener is called on 'change' event on filter input field.
     * Filters the tree by text input.
     * @param {Ext.form.Field} field The field
     * @private
     */
    _searchFilter: function (field)
    {
        var value = new String(field.getValue()).trim();
        this._filterField = field;
        
        if (this._filterValue == value)
        {
            // Do nothing
            return;
        }
        
        this._filterValue = value;
        if (value.length > 2)
        {   
            this._containerStore.clearFilter();
            this._containerStore.filterBy(function(record) {
                var title = Ext.String.deemphasize(record.get('title').trim().toLowerCase())
                var code = Ext.String.deemphasize(record.get('code').trim().toLowerCase())
                var val = Ext.String.deemphasize(value.trim().toLowerCase())
                return title.indexOf(val) != -1 || code.indexOf(val) != -1;
            });
            
            if (this._containerStore.getCount() == 0)
            {
                this._copyRuleBox.down("#container-grid").getView().mask("{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_RULE_DIALOG_NO_CONTAINER_FILTER_MSG}}", 'ametys-mask-unloading');
            }
            else
            {
                this._copyRuleBox.down("#container-grid").getView().unmask();
            }
        }
        else
        {
            this._copyRuleBox.down("#container-grid").getView().unmask();
            this._containerStore.clearFilter();
        }
    },
    
    /**
     * @private
     * Clear the filter
     */
    _clearFilter: function()
    {
        this._copyRuleBox.down("#search-filter-input").setValue(null);
        this._copyRuleBox.down("#container-grid").getView().unmask();
        this._containerStore.clearFilter();
    },
    
    /**
     * @private
     * Get the containers grid store
     */
    _getGridStore: function()
    {
        var cfg = {
            proxy: {
                type: 'ametys',
                role: 'org.ametys.plugins.odfpilotage.rule.RulesManager',
                methodName: 'getAllowedContainers',
                reader: {
                    type: 'json',
                    rootProperty: 'containers'
                }
            },
            
            listeners: {
                'beforeload': this._onBeforeLoad,
                scope: this
            },
            
            sorters: [{property: 'title', direction:'ASC'}],
            
            fields: [
                {name: 'id', mapping: 'id'},
                {name: 'title', mapping: 'title'},
                {name: 'code', mapping: 'code'},
                {name: 'isDerogated', mapping: 'isDerogated'}
            ]
        }
        
        return Ext.create('Ext.data.Store', cfg);
    },
    
    /**
     * @private
     * Function called before loading the store
     * @param {Ext.data.Store} store The store
     * @param {Ext.data.operation.Operation} operation The object that will be passed to the Proxy to load the store
     */
    _onBeforeLoad: function(store, operation)
    {
        operation.setParams( Ext.apply(operation.getParams() || {}, {
            ruleId: this._ruleId,
            containerId: this._containerId
        }));
    },
    
    /**
     * @private
     * Copy the rule
     */
    _validateCopyRule: function()
    {
        var records = this._containerGrid.getSelectionModel().getSelection();
        var containerIds = records.map(r => r.data.id);
        if (!containerIds.length)
        {
            Ametys.Msg.show({
                title: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_RULE_DIALOG_TITLE}}",
                msg: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_RULE_NO_SELECT_MSG}}",
                buttons: Ext.Msg.OK,
                icon: Ext.Msg.WARNING
            });
            return;
        }
        
        Ametys.data.ServerComm.callMethod({
            role: "org.ametys.plugins.odfpilotage.rule.RulesManager",
            methodName: "copyRule",
            parameters: [this._containerId, this._thematicId, this._ruleId, containerIds],
            callback: {
                handler: this._validateCopyRuleCB,
                scope: this
            },
            waitMessage: true
        });
    },
    
    /**
     * @private
     * Callback after copying the rule
     * @param {Object} response the server response
     */
    _validateCopyRuleCB: function(response)
    {
        if (!this._handleError(response, "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_RULE_ERROR}}"))
        {
            this._copyRuleBox.close();
            if (!this._handleErrorContainers(response))
            {
                Ametys.Msg.show({
                    title: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_RULE_OK_TITLE}}",
                    msg: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_RULE_OK_MSG}}",
                    buttons: Ext.Msg.OK,
                    icon: Ext.Msg.INFO
                });
            }
        }
        
        if (response["updated-containers"] && response["updated-containers"].length)
        {
            Ext.create("Ametys.message.Message", {
                type: Ametys.message.Message.MODIFIED,
                targets: {
                    id: Ametys.message.MessageTarget.CONTENT,
                    parameters: { ids: response["updated-containers"].map(c => c.id) }
                }
            });
        }
    },
    
    /**
     * Action function to be called by the controller.
     * Will copy rule derogation, additional rule and complematory rule to a container
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     */
    copyAllRules: function(controller)
    {
        var contentTarget = controller.getMatchingTargets()[0];
        this._containerId = contentTarget.getParameters().id;
        this._thematicId = null;
        this._ruleId = null;
        
        this._copyRuleBox = this._createCopyRuleDialogBox(
            Ext.String.format("{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_RULE_DIALOG_TITLE}}", contentTarget.getParameters().title),
            "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_RULE_HINT_MSG}}",
            Ext.bind(this._validateCopyAllRules, this), 
            false,
            false
        );
        
        this._clearFilter();
        
        let mask = Ametys.mask.GlobalLoadMask.mask("{{i18n plugin.core-ui:PLUGINS_CORE_UI_LOADMASK_DEFAULT_MESSAGE}}");
        this._containerStore.load({
            callback: Ext.bind(function(){
                Ametys.mask.GlobalLoadMask.unmask(mask);
                if (this._containerStore.queryRecords().length)
                {
                    this._copyRuleBox.show();
                }
                else
                {
                    this._copyRuleBox.close();
                    Ametys.Msg.show({
                        title: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_RULE_DIALOG_TITLE}}",
                        msg: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_RULE_DIALOG_NO_CONTAINER_ERROR}}",
                        buttons: Ext.Msg.OK,
                        icon: Ext.Msg.INFO
                     });
                }
            }, this)
        });
    },
    
    /**
     * @private
     * Copy all the rule
     */
    _validateCopyAllRules: function()
    {
        var records = this._containerGrid.getSelectionModel().getSelection();
        var containerIds = records.map(r => r.data.id);
        if (!containerIds.length)
        {
            Ametys.Msg.show({
                title: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_RULE_DIALOG_TITLE}}",
                msg: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_RULE_NO_SELECT_MSG}}",
                buttons: Ext.Msg.OK,
                icon: Ext.Msg.WARNING
            });
            return;
        }
        
        Ametys.data.ServerComm.callMethod({
            role: "org.ametys.plugins.odfpilotage.rule.RulesManager",
            methodName: "copyAllRules",
            parameters: [this._containerId, containerIds],
            callback: {
                handler: this._validateCopyAllRulesCB,
                scope: this
            },
            errorMessage: true,
            waitMessage: true
        });
    },
    
    /**
     * @private
     * Callback after copying all the rules
     * @param {Object} response the server response
     */
    _validateCopyAllRulesCB: function(response)
    {
        if (!this._handleError(response, "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_RULE_ERROR}}"))
        {
            this._copyRuleBox.close();
            if (!this._handleErrorContainers(response))
            {
                Ametys.Msg.show({
                    title: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_RULE_OK_TITLE}}",
                    msg: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_RULE_OK_MSG}}",
                    buttons: Ext.Msg.OK,
                    icon: Ext.Msg.INFO
                });
            }
        }
        
        if (response["updated-containers"] && response["updated-containers"].length)
        {
            Ext.create("Ametys.message.Message", {
                type: Ametys.message.Message.MODIFIED,
                targets: {
                    id: Ametys.message.MessageTarget.CONTENT,
                    parameters: { ids: response["updated-containers"].map(c => c.id) }
                }
            });
        }
    },
    
    /**
     * Will copy all rules from the view
     * @param {String} containerTitle The container title
     * @param {String} containerId The container id
     * @param {String} viewName The view name to copy
     */
    copyRulesView: function(containerTitle, containerId, viewName)
    {
        this._containerId = containerId;
        this._thematicId = null;
        this._ruleId = null;
        
        var isComplementaryRules = viewName == "thematics-edition";
        
        var boxTitle = Ext.String.format(
            isComplementaryRules ? "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_COMPLEMENTARY_RULE_DIALOG_CONTENT_TITLE}}" : "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_SPECIFIC_RULE_DIALOG_CONTENT_TITLE}}",
            containerTitle
        )
        this._copyRuleBox = this._createCopyRuleDialogBox(
            boxTitle,
            isComplementaryRules ? "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_COMPLEMENTARY_RULE_HINT_MSG}}" : "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_SPECIFIC_RULE_HINT_MSG}}",
            Ext.bind(this._validateCopyRulesView, this, [viewName]), 
            false,
            isComplementaryRules
        );
        
        this._clearFilter();
        
        let mask = Ametys.mask.GlobalLoadMask.mask("{{i18n plugin.core-ui:PLUGINS_CORE_UI_LOADMASK_DEFAULT_MESSAGE}}");
        this._containerStore.load({
            callback: Ext.bind(function(){
                Ametys.mask.GlobalLoadMask.unmask(mask);
                if (this._containerStore.queryRecords().length)
                {
                    this._copyRuleBox.show();
                }
                else
                {
                    this._copyRuleBox.close();
                    Ametys.Msg.show({
                        title: boxTitle,
                        msg: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_RULE_DIALOG_NO_CONTAINER_ERROR}}",
                        buttons: Ext.Msg.OK,
                        icon: Ext.Msg.INFO
                     });
                }
            }, this)
        });
    },
    
    /**
     * @private
     * Copy all the rule from the view
     * @param {String} viewName The view name to copy
     */
    _validateCopyRulesView: function(viewName)
    {
        var records = this._containerGrid.getSelectionModel().getSelection();
        var containerIds = records.map(r => r.data.id);
        if (!containerIds.length)
        {
            var isComplementaryRules = viewName == "thematics-edition";
            Ametys.Msg.show({
                title: isComplementaryRules ? "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_COMPLEMENTARY_RULE_DIALOG_TITLE}}" : "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_SPECIFIC_RULE_DIALOG_TITLE}}",
                msg: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_RULE_NO_SELECT_MSG}}",
                buttons: Ext.Msg.OK,
                icon: Ext.Msg.WARNING
            });
            return;
        }
        
        Ametys.data.ServerComm.callMethod({
            role: "org.ametys.plugins.odfpilotage.rule.RulesManager",
            methodName: "copyRulesFromView",
            parameters: [this._containerId, containerIds, viewName],
            callback: {
                handler: Ext.bind(this._validateCopyRulesViewCB, this, [viewName], 1),
                scope: this
            },
            waitMessage: true
        });
    },
    
    /**
     * @private
     * Callback after copying all rules from view
     * @param {Object} response the server response
     * @param {String} viewName The view name to copy
     */
    _validateCopyRulesViewCB: function(response, viewName)
    {
        var isComplementaryRules = viewName == "thematics-edition";
        if (!this._handleError(response, isComplementaryRules ? "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_COMPLEMENTARY_RULE_ERROR}}" : "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_SPECIFIC_RULE_ERROR}}"))
        {
            this._copyRuleBox.close();
            if (!this._handleErrorContainers(response))
            {
                Ametys.Msg.show({
                    title: isComplementaryRules ? "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_COMPLEMENTARY_RULE_DIALOG_TITLE}}" : "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_SPECIFIC_RULE_DIALOG_TITLE}}",
                    msg: isComplementaryRules ? "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_COMPLEMENTARY_RULE_OK_MSG}}" : "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ALL_SPECIFIC_RULE_OK_MSG}}",
                    buttons: Ext.Msg.OK,
                    icon: Ext.Msg.INFO
                });
            }
        }
        
        if (response["updated-containers"] && response["updated-containers"].length)
        {
            Ext.create("Ametys.message.Message", {
                type: Ametys.message.Message.MODIFIED,
                targets: {
                    id: Ametys.message.MessageTarget.CONTENT,
                    parameters: { ids: response["updated-containers"].map(c => c.id) }
                }
            });
        }
    },
    
    /**
     * @private
     * Handle error containers of response
     * @param {Object} response the server response
     * @returns true if errors
     */
    _handleErrorContainers: function(response)
    {
        var hasError = false;
        if (response["error-containers"])
        {
            var errorMsg = Ext.String.format("{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_COPY_ERROR_MSG}}", response["error-containers"].length, response.size)
            errorMsg += "<ul>";
            for (var containerMap of response["error-containers"])
            {
                errorMsg += "<li>" + containerMap.title + " (" + containerMap.code + ")</li>";
            }
            errorMsg += "</ul>";
            
            Ametys.Msg.show({
                title: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_ERROR}}",
                msg: errorMsg,
                buttons: Ext.Msg.OK,
                icon: Ext.Msg.ERROR
            });
            
            hasError = true;
        }
        
        return hasError;
    },
    
    /**
     * Action function to be called by the controller.
     * Will edit specific rules to a container
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     */
    editSpecificRules: function (controller)
    {
        var contentTarget = controller.getMatchingTargets()[0];
        var containerId = contentTarget.getParameters().id;
        
        Ametys.cms.content.ContentDAO.getContent(containerId, Ext.bind(this._editSpecificRulesCB, this));
    },
    
    /**
     * Callback to edit specific rules
     * @param {Content} content The content to edit
     */
    _editSpecificRulesCB: function(content)
    {
        var openParams = {
            content: content,
            editWorkflowActionId: 2,
            workflowName: "container",
            viewName: "specific-rules",
            width: Math.min(1000, window.innerWidth * 0.9),
            dialogConfig: {
                height: window.innerHeight * 0.90,
                displayGroupsDescriptions: true
            }
        }
        
        Ametys.cms.uihelper.EditContent.open(openParams, null, this);
    },
    
    /**
     * @private
     * Handle error of response
     * @param {Object} response the server response
     * @param {String} errorMsg The error message
     * @returns true if errors
     */
    _handleError: function(response, errorMsg)
    {
        var hasError = false;
        var error = response.error;
        if (error)
        {
            if (error == "not-exist")
            {
                Ametys.Msg.show({
                    title: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_ERROR}}",
                    msg: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_NOT_EXIST_ERROR_MSG}}",
                    buttons: Ext.Msg.OK,
                    icon: Ext.Msg.ERROR
                });
            }
            else
            {
                Ametys.Msg.show({
                    title: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_ERROR}}",
                    msg: errorMsg,
                    buttons: Ext.Msg.OK,
                    icon: Ext.Msg.ERROR
                });
            }
            hasError = true;
        }
        return hasError;
    },
    
    /**
     * @private
     * Send a message bus for a given rule in a thematic
     * @param {String} containerId The container id
     * @param {String} thematicId The thematic id
     * @param {String} thematicType The thematic type
     * @param {String} ruleId The rule id. Can be null if no rule selected
     * @param {String} type The rule type. Can be null if no rule selected
     * @param {Boolean} derogable true if the rule is derogable. Can be null if no rule selected
     * @param {Boolean} derogated true if the rule is derogated. Can be null if no rule selected
     */
    _sendMessageBus: function(containerId, thematicId, thematicType, ruleId, type, derogable, derogated)
    {
        var contentTarget = {
            id: Ametys.message.MessageTarget.CONTENT,
            parameters: {
                ids: [containerId]
            }
        };
        
        var thematicTarget = {
            id: Ametys.message.MessageTarget.THEMATIC,
            parameters: {
                id: thematicId,
                type: thematicType
            }
        };
        
        if (ruleId)
        {
            var ruleTarget = {
                id: Ametys.message.MessageTarget.RULE,
                parameters: {
                    id: ruleId,
                    derogable: derogable,
                    derogated: derogated,
                    type: type
                }
            };
            
            thematicTarget.subtargets = ruleTarget;
        }
        
        contentTarget.subtargets = thematicTarget;
        
        Ext.create("Ametys.message.Message", {
            type: Ametys.message.Message.MODIFIED,
            targets: [contentTarget]
        });
    },
    
    /**
     * @private
     * Create a configuration for a rich text
     * @param {String} label The rich text label
     * @param {String} description The rich text description
     * @param {String} name The rich text name
     * @param {Boolean} mandatory If the rich text is mandatory
     */
    _richTextConfiguration: function(label, description, name, mandatory)
    {
        return {
            label: label,
            description: description,
            multiple: false,
            name: name,
            path: name,
            validation: {
                mandatory: mandatory
            },
            type: "rich-text",
            editableSource: true,
            "widget-params": {
                height: 200,
                resizable: false
            }
        }
    },
    
    /**
     * @private
     * Get the configurable form panel configuration
     * @param {String} labelAlign the label align
     */
    _getConfigurableFormPanelConf: function(labelAlign)
    {
        return {
            labelAlign: labelAlign,
            
            bodyStyle : {
                padding : 0
            },
            
            additionalWidgetsConf: {
                standaloneEdition: true, // standalone edition (use case: a popup without ribbon (for example richtext buttons) or FO edition),
                category: "content"
            },
            
            additionalWidgetsConfFromParams: {
                contentType: 'contentType', // some widgets require the contentType configuration
                editableSource: false
            },
            
            fieldNamePrefix : 'content.input.',
            displayGroupsDescriptions: false,
        };
    },
    
    /**
     * @private
     * Create a configured dialog box
     * @param {String} title the dialog box title
     * @param {String} icon the dialog box icon
     * @param {Object[]} items the dialog box items
     * @param {Function} validateFn the dialog box validate function
     * @param {Function} cancelFn the dialog box cancel function
     * @param {Boolean} withMargin true to get margin
     * @param {Boolean} destroy true to destroy on close
     */
    _createDialogBox: function(title, icon, items, validateFn, cancelFn, withMargin, destroy)
    {
        return Ext.create('Ametys.window.DialogBox', {
            title : title,
            iconCls : icon,
            width: Math.min(1000, window.innerWidth * 0.9),
            maxHeight: window.innerHeight * 0.90,
            scrollable: true,

            layout: {
                type: 'vbox',
                align: 'stretch'
            },
            defaultType: 'textfield',
            defaults: {
                cls: 'ametys',
                labelAlign: 'right',
                labelSeparator: '',
                labelWidth: 130,
                style: withMargin ? 'margin-right:' + (Ametys.form.ConfigurableFormPanel.OFFSET_FIELDSET + Ametys.form.ConfigurableFormPanel.PADDING_TAB) + 'px' : ''
            },
            
            items: items,
            freezeHeight: true,
            
            selectDefaultFocus: true,
            closeAction: destroy ? 'destroy' : 'hide',
            
            referenceHolder: true,
            defaultButton: 'validate',
            
            buttons : [
                {
                    reference: 'validate',
                    text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_OK}}",
                    handler: validateFn,
                    scope: this
                }, {
                    text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_RULES_ACTION_CANCEL}}",
                    handler: cancelFn,
                    scope: this
            }]
        });
    },
    
    exportWorkRules: function(controller)
    {
        this._exportContainerRules(controller, "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TS_RULES_BUTTON_EXPORT_PDF_WORK_RULES_LABEL}}", "work");
    },
    
    exportDiffRules: function(controller)
    {
        this._exportContainerRules(controller, "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TS_RULES_BUTTON_EXPORT_PDF_DIFF_RULES_LABEL}}", "diff");
    },
    
    exportFinalRules: function(controller)
    {
        this._exportContainerRules(controller, "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TS_RULES_BUTTON_EXPORT_PDF_FINAL_RULES_LABEL}}", "final");
    },
    
    _exportContainerRules: function(controller, title, mode)
    {
        var containerId = controller.getMatchingTargets()[0].getParameters().id;
        var values = {
            "reportId": "org.ametys.plugins.odfpilotage.report.MCCReport",
            "programItem": containerId
        }
        
        Ametys.plugins.odf.pilotage.helper.ExportHelper.export(
            title,
            "ametysicon-desktop-book",
            `${Ametys.getPluginDirectPrefix('odf-pilotage')}/${mode}/container-rules`,
            values,
            ['pdf', 'doc'],
            'pdf'
        );
    }
});