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

 /**
  * CostModeling panel
  * @private
  */
Ext.define('Ametys.plugins.odf.pilotage.tool.CostModelingTreeGridPanel', {
	extend: 'Ametys.plugins.odf.tree.AbstractODFTreeGridPanel',
    
    cls: ['costmodelingtreegrid', 'treegrideditionfirst'],
    
	/**
     * @property {Object} _catalog
     * @private
     */
	
     /**
      * @property {Object} _lang
      * @private
      */
     
	constructor: function(config)
	{
        let me = this;
        
		config.serverRole = config.serverRole || "org.ametys.plugins.odfpilotage.helper.CostComputationTreeHelper";
		config.methodArguments = ['contentId', 'path', 'tree', 'namePath', 'catalog', 'lang']
		var natureColumns = this._getNaturesColumns(config.natures);
		config.columns = this._getGridConfig(natureColumns);
		config.dockedItems = config.dockedItems || [];
		config.dockedItems.push( {
			id: "costModelingHint",
			dock: 'top',
			ui: 'tool-hintmessage',
			xtype: 'component',
			hidden: true,
			html: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_HINT_MESSAGE}}"
		});
        config.toolbarPosition = 1; 
		this._data = config.oldDdata;
		this.callParent(arguments);
		config.store.on('beforeload', function(store, operation) {
        		if (operation.node.get('contentId'))
        		{
        			var params = Ext.apply(operation.getParams() || {}, {'namePath':  operation.node.getPath('name').substring(1), 'path':  me.getNodePath(operation.node), 'catalog': operation.node.data.catalog, 'lang': operation.node.data.lang});
        			operation.setParams(params);
        		}
		});
	},
	
    _getToolBarConfig: function(config)
    {
        let toolbarCfg = this.callParent(arguments);
        
        let toolBarItems = toolbarCfg.items;
        
        // Insert the button that hide or shows previousYears columns
        toolBarItems.push({
            xtype: 'button',
            text: '<strong>{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_BUTTON_PREVIOUS_YEARS_LABEL}}</strong>',
            itemId: 'show-hide-previous-years-button',
            tooltip: '{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_BUTTON_PREVIOUS_YEARS_TOOLTIP}}',
            handler: Ext.bind (this.showHidePreviousYearsColumns, this),
        });
        
        return toolbarCfg;
    },
    
    _getPreviousYearsColumns: function()
    {
        let previousYearsColumnsIds = ["precedingYearGroups", "currentYearGroups", "precedingYearEffectives", "currentYearEffectives"];
        let columns = this.getColumns();
        
        function _findColumn(id, columns)
        {
            let columnsFound = columns.filter(c => (c.dataIndex || c.name) == id);
            return columnsFound.length == 1 ? columnsFound[0] : null;
        }
        
        return previousYearsColumnsIds.map(id => _findColumn(id, columns));
    },
    
    showHidePreviousYearsColumns:function()
    {
        let previousYearsColumns = this._getPreviousYearsColumns();
        
        for (let previousYearColumn of previousYearsColumns)
        {
            // If one of the column is hidden, show all of them
            if (!previousYearColumn.isVisible())
            {
                Ext.Array.forEach(previousYearsColumns, 
                                  function(column) {
                                      column.setVisible(true);
                                  },
                                  this);
                
                return;
            }
        }
        
        // If all the columns are visible, hide them all
        Ext.Array.forEach(previousYearsColumns,
                          function(column) {
                              column.setVisible(false);
                          },
                          this);
    },
    
	/**
	 * Function creating the JSON structure of hourly volumes grid
	 * @param {Object} natures The list of hourly volumes codes
	 * @private
	 */
    _getNaturesColumns: function(natures)
    {
        var json = [];
        var i = 0;
        for (var [key, nature] of Object.entries(natures))
        {
            json[i++] = {
                text: key,
                stateId: key,
                dataIndex: key,
                align: 'center',
                sortable: false,
                editor: 'numberfield',
                renderer: this._renderVolumeOfHours,
                tooltip: nature.label,
                hidden: nature.archived,
                lockable: false
            };
        }
        return json;
    },

	setContentRootNode: function(contentId, otherConfig, callback)
	{
		Ametys.data.ServerComm.callMethod({
			role: this.getInitialConfig('serverRole'),
			methodName: "getRootNodeInformations",
			parameters: [
				contentId,
				otherConfig.catalog,
				otherConfig.lang,
				otherConfig.overriddenData
			],
			callback: {
				handler: this._setContentRootNodeCb,
				scope: this,
				arguments: {
					configToSet: otherConfig || {},
					callback: callback
				}
			},
            errorMessage: true,
			waitMessage: false
		});
	},
	
	_setContentRootNodeCb: function(response, config)
	{
		this.callParent(arguments);
		if (!this._catalog && !this._lang)
		{
			this._catalog = response.catalog;
			this._lang = response.lang;
		}
	},
	
	/**
	 * Function returning the current language of the content
	 * @return {String} the language
	 * @private
	 */
    getLang: function()
    {
        return this._lang;
    },
    
    /**
     * Function returning the current catalog of the content
     * @return {String} the catalog
     * @private
     */
    getCatalog: function()
    {
        return this._catalog;
    },
    
    /**
     * Function creating the structure of the columns grid 
     * @param {Object} natures the structure of the hourly volumes columns
     * @private
     */
    _getGridConfig: function(natureColumns)
    {
        return [
        {
            xtype: 'treecolumn',
            text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_MODEL}}",
            stateId: 'title',
            sortable: false,
            dataIndex: 'title',
            width: 400,
            locked: true,
            lockable: false
        },
        {
            text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_EFF}}",
            columns: [
                {
                    text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_EFFGLOBAL}}",
                    stateId: 'effectivesGlobal',
                    dataIndex: 'effectivesGlobal',
                    sortable: false,
					minWidth: 82,
                    align:'center',
                    editor: 'numberfield',  
                    tooltip: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_EFFECTIF_GLOBAL}}",
                    renderer: Ext.bind(this._renderEffectivesGlobal, this),
                    lockable: false
                },
                {
                    text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_EFFSCOPE}}",
                    stateId: 'effectivesLocal',
                    dataIndex: 'effectivesLocal',
                    sortable: false,
                    align:'center',
                    width: 99,
                    tooltip: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_EFFECTIF_LOCAL}}",
                    lockable: false
                },
                {
                    text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_CURRENT_YEAR_DATA}}",
                    stateId: 'currentYearEffectives',
                    dataIndex: 'currentYearEffectives',
                    sortable: false,
                    align:'center',
                    tooltip: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_EFFECTIF_CURRENT_YEAR}}",
                    width: 54,
                    minWidth: 54,
                    renderer: Ext.bind(this._renderPreviousYearEffectives, this),
                    lockable: false
                },
                {
                    text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_PRECEDING_YEAR_DATA}}",
                    stateId: 'precedingYearEffectives',
                    dataIndex: 'precedingYearEffectives',
                    sortable: false,
                    align:'center',
                    tooltip: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_EFFECTIF_PRECEDING_YEAR}}",
                    width: 54,
                    minWidth: 54,
                    renderer: Ext.bind(this._renderPreviousYearEffectives, this),
                    lockable: false
                }
            ],
            lockable: false
        },
        {
            text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_GROUPS}}",
            columns: [
                {
                    text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_GROUPS_TO_OPEN}}",
                    stateId: 'groups',
                    dataIndex: 'groupsDisplay',
                    sortable: false,
                    align:'center',
                    editor: 'numberfield',  
                    tooltip: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_GROUPE}}",
                    width: 75,
                    minWidth: 70,
                    renderer: Ext.bind(this._renderGroups, this),
                    lockable: false
                },
                {
                    text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_CURRENT_YEAR_DATA}}",
                    stateId: 'currentYearGroups',
                    dataIndex: 'currentYearGroups',
                    sortable: false,
                    align:'center',
                    tooltip: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_GROUPE_CURRENT_YEAR}}",
                    width: 54,
                    minWidth: 54,
                    renderer: Ext.bind(this._renderPreviousYearGroups, this),
                    lockable: false
                },
                {
                    text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_PRECEDING_YEAR_DATA}}",
                    stateId: 'precedingYearGroups',
                    dataIndex: 'precedingYearGroups',
                    sortable: false,
                    align:'center',
                    tooltip: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_GROUPE_PRECEDING_YEAR}}",
                    width: 54,
                    minWidth: 54,
                    renderer: Ext.bind(this._renderPreviousYearGroups, this),
                    lockable: false
                }
            ]
        },
        {
            text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_HORVOL}}",
            columns: natureColumns,
            lockable: false
        },
        {
            text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_EQTD}}",
            columns: [
                {
                    text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_EQTDGLOBAL}}",
                    stateId: 'eqTDglobal',
                    dataIndex: 'eqTDglobal',
                    align:'center',
                    sortable: false,
                    tooltip: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_EQTD_GLOBAL}}",
                    renderer: Ext.bind(this._renderEqTDGlobal, this),
                    lockable: false
                        
                },
                {
                    text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_EQTDPRORATED}}",
                    stateId: 'eqTDprorated',
                    dataIndex: 'eqTDprorated',
                    align:'center',
                    sortable: false,
                    tooltip: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_EQTD_PRORATISE}}",
                    renderer: Ext.bind(this._renderEqTDProrated, this),
                    lockable: false
                },
                {
                    text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_EQTDSCOPE}}",
                    stateId: 'eqTDlocal',
                    dataIndex: 'eqTDlocal',
                    align:'center',
                    tooltip: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_EQTD_PORTE}}",
                    sortable: false,
                    renderer: Ext.bind(this._renderEqTDHeld, this),
                    lockable: false
                }
            ],
            lockable: false
        },
        {
            text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_REPORT}}",
            stateId: 'heRatio',
            dataIndex: 'heRatio',
            align:'center',
            tooltip: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_RAPPORT_HE}}",
            sortable: false,
            renderer: Ext.bind(this._renderHeRatio, this),
            lockable: false
        }]
    },
    
    /**
     * Render the H/E ratio column.
     * @param {Object} value The data value for the current cell.
     * @param {Object} metadata A collection of metadata about the current cell
     * @param {Ext.data.Model} record The record
     * @param {Number} rowIndex The index of the current row
     * @param {Number} cellIndex The index of the current cell
     * @param {Ext.data.Store} store The store
     * @param {Ext.view.View} view The current view
     * @private
     */
    _renderHeRatio: function(value, metadata, record, rowIndex, cellIndex, store, view)
    {
        // Never display heRatio on course lists
        if (!record.data.contenttypesIds.includes('org.ametys.plugins.odf.Content.courseList'))
        {
            // Style and value
            if (this.config.oldData)
            {
                var oldHERatio = 0;
                if (this.config.oldData[record.data.contentId].heRatio != undefined)
                {
                    oldHERatio = this.config.oldData[record.data.contentId].heRatio[record.getPath('name').substring(1)] || 0;
                }
                
                // Display the ratio even if new value is undefined and ratio is different of 0
                var newValue = value || 0;
                if (oldHERatio != newValue)
                {
                    return this._compareValues(oldHERatio.toFixed(2), newValue.toFixed(2), metadata);
                }
            }
            
            if (value != undefined)
            {
                // Value
                return value.toFixed(2);
            }
        }
    },
    
    /**
     * Render the pro-rated eqTD column.
     * @param {Object} value The data value for the current cell.
     * @param {Object} metadata A collection of metadata about the current cell
     * @param {Ext.data.Model} record The record
     * @param {Number} rowIndex The index of the current row
     * @param {Number} cellIndex The index of the current cell
     * @param {Ext.data.Store} store The store
     * @param {Ext.view.View} view The current view
     * @private
     */
    _renderEqTDProrated: function(value, metadata, record, rowIndex, cellIndex, store, view)
    {
        if (value != undefined)
        {
            // Style and value
            if (this.config.oldData)
            {
                var oldEqTD = 0;
                if (this.config.oldData[record.data.contentId].proratedEqTD != undefined)
                {
                    oldEqTD = this.config.oldData[record.data.contentId].proratedEqTD[record.getPath('name').substring(1)] || 0;
                }
                return this._compareValues(oldEqTD.toFixed(2), value.toFixed(2), metadata);
            }
            
            // Value
            return value.toFixed(2);
        }
    },
    
    /**
     * Render the eqTD local column.
     * @param {Object} value The data value for the current cell.
     * @param {Object} metadata A collection of metadata about the current cell
     * @param {Ext.data.Model} record The record
     * @param {Number} rowIndex The index of the current row
     * @param {Number} cellIndex The index of the current cell
     * @param {Ext.data.Store} store The store
     * @param {Ext.view.View} view The current view
     * @private
     */
    _renderEqTDHeld: function(value, metadata, record, rowIndex, cellIndex, store, view)
    {
        if (value != undefined)
        {
            // Style and value
            if (this.config.oldData)
            {
                var oldEqTD = 0;
                if (this.config.oldData[record.data.contentId].localEqTD != undefined)
                {
                    oldEqTD = this.config.oldData[record.data.contentId].localEqTD[record.getPath('name').substring(1)] || 0;
                }
                return this._compareValues(oldEqTD.toFixed(2), value.toFixed(2), metadata);
            }
            // Value
            return value.toFixed(2);
        }
    },
    
    /**
     * Render the global eqTD column.
     * @param {Object} value The data value for the current cell.
     * @param {Object} metadata A collection of metadata about the current cell
     * @param {Ext.data.Model} record The record
     * @param {Number} rowIndex The index of the current row
     * @param {Number} cellIndex The index of the current cell
     * @param {Ext.data.Store} store The store
     * @param {Ext.view.View} view The current view
     * @private
     */
    _renderEqTDGlobal: function(value, metadata, record, rowIndex, cellIndex, store, view)
    {
        if (value != undefined)
        {
            // Style and value
            if (this.config.oldData)
            {
                var oldEqTD = 0;
                if (this.config.oldData[record.data.contentId].globalEqTD != undefined)
                {
                    oldEqTD = this.config.oldData[record.data.contentId].globalEqTD || 0;
                }
                return this._compareValues(oldEqTD.toFixed(2), value.toFixed(2), metadata);
            }
            
            // Value
            return value.toFixed(2);
        }
	},
    
    /**
     * Compare two values and print the difference between them
     * @param {Number} oldValue the old value
     * @param {Number} newValue the new value 
     * @param {Object} metadata A collection of metadata about the current cell
     * @private
     */
    _compareValues: function(oldValue, newValue, metadata)
    {
        var diff = (newValue - oldValue).toFixed(2);
        if (diff != 0)
        {
            metadata.tdCls = 'diff-' + (diff > 0 ? 'plus' : 'minus');
            return newValue + ' (' + (diff > 0 ? '+' : '') + diff + ')';
        }
        
        return newValue;
    },
    
    /**
     * Render the hourly volume column.
     * @param {Object} value The data value for the current cell.
     * @param {Object} metadata A collection of metadata about the current cell
     * @param {Ext.data.Model} record The record
     * @param {Number} rowIndex The index of the current row
     * @param {Number} cellIndex The index of the current cell
     * @param {Ext.data.Store} store The store
     * @param {Ext.view.View} view The current view
     * @private
     */
    _renderVolumeOfHours: function(value, metadata, record, rowIndex, cellIndex, store, view)
    {
        if (value != undefined)
        {
            // Tooltip and style
            if (record.data.contenttypesIds.includes('org.ametys.plugins.odf.Content.coursePart') && record.data.volumesOfHours[this.ownerGrid.getInitialConfig()["natures"][metadata.column.stateId].id] !== undefined)
            {
                if (record.data.volumesOfHours.original)
                {
                    metadata.tdAttr = 'data-qtip="{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_ENTERED_NB_HOURS}} = <b>' + record.data.volumesOfHours.original.toFixed(2) + '</b>"';
                    metadata.tdCls = 'overridden';
                }
                else
                {
                    metadata.tdCls = 'entered';
                }
                metadata.innerCls = 'editable';
            }
            
            // Value
            return value.toFixed(2);
        }
    },
    
    /**
     * Render the group column.
     * @param {Object} value The data value for the current cell.
     * @param {Object} metadata A collection of metadata about the current cell
     * @param {Ext.data.Model} record The record
     * @param {Number} rowIndex The index of the current row
     * @param {Number} cellIndex The index of the current cell
     * @param {Ext.data.Store} store The store
     * @param {Ext.view.View} view The current view
     * @private
     */
    _renderGroups: function(value, metadata, record, rowIndex, cellIndex, store, view)
    {
        if (value != undefined)
        {
            // Style
            metadata.tdCls = record.data.groupType;
            if (record.data.contenttypesIds.includes('org.ametys.plugins.odf.Content.coursePart'))
            {
                metadata.innerCls = 'editable';
            }
            
            // Tooltip
            metadata.tdAttr = 'data-qtip="';
            switch (record.data.groupType)
            {
                case 'overridden':
                    if (record.data.groups.entered)
                    {
                        metadata.tdAttr += '{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_GROUPES_SAISIS}} = <b>' + record.data.groups.entered + '</b><br/>';
                    }
                case 'entered':
                    metadata.tdAttr += '{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_GROUPES_CALCULES}} = <b>' + record.data.groups.computed + '</b><br/>';
                case 'computed':
                    if (record.data.norm.effMax)
                    {
                        metadata.tdAttr += '{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_EFFMAX}} = <b>' + record.data.norm.effMax + '</b><br/>';
                    }
                    if (record.data.norm.effMinSup)
                    {
                        metadata.tdAttr += '{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_EFFMINSUPP}} = <b>' + record.data.norm.effMinSup + '</b>';
                    }
                    if (record.data.norm.label)
                    {
                        metadata.tdAttr += '<br/>{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_NORM}} = <b>' + record.data.norm.label + '</b>';
                    }
            }
            metadata.tdAttr += '"';
            
            // Value
            return value;
        }
    },
    
    /**
     * Render the global effective column.
     * @param {Object} value The data value for the current cell.
     * @param {Object} metadata A collection of metadata about the current cell
     * @param {Ext.data.Model} record The record
     * @param {Number} rowIndex The index of the current row
     * @param {Number} cellIndex The index of the current cell
     * @param {Ext.data.Store} store The store
     * @param {Ext.view.View} view The current view
     * @private
     */
     _renderEffectivesGlobal: function(value, metadata, record, rowIndex, cellIndex, store, view)
    {
    	// Style
    	metadata.tdCls = record.data.effectivesType;
        
        var contenttypes = record.data.contenttypesIds;
        if (contenttypes.includes('org.ametys.plugins.odf.Content.course')
            || contenttypes.includes('org.ametys.plugins.odf.Content.container')
            || contenttypes.includes('org.ametys.plugins.odf.Content.subProgram')
            || contenttypes.includes('org.ametys.plugins.odf.Content.program')
        )
        {
            metadata.innerCls = 'editable' 
            + (record.data.effectives && record.data.effectives.inconsistent ? ' inconsistent' : '');
        }
        
        if (value !== undefined)
        {
        	var tdAttrInconsistent = '<span style=\'margin: 0px; padding: 0; \'>{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_TOOLTIP_EFFECTIVES_INCONSISTENCIES}}</span>';
        	var valueHTMLSurrounded = '<span style="display: inline-block;width:5px; margin-right:5px;"></span> <span class="costmodelingtreegrid">' + value + '</span> <span class="odf-indicator orange-color ametysicon-sign-caution-light"/>'; 
        	
            // Tooltip
            if ((record.data.effectivesType == 'estimated' || record.data.effectivesType == 'computed') && (record.data.effectives.distribution != undefined && record.data.effectives.distribution.length > 0))
            {
                metadata.tdAttr = ' data-anchor="left" data-qtip="';
                
                if (record.data.effectives.inconsistent)
                {
                    metadata.tdAttr += tdAttrInconsistent
                    value =  valueHTMLSurrounded;
                }
                
                metadata.tdAttr += '<ul style=\'margin: 0px; padding: 0; list-style: none;\'>';
                for (let line of record.data.effectives.distribution)
                {
                    let text = "";
                    for (let c of line.path)
                    {
                        text += (text ? " > " : "") + "<a href='javascript:void(0)' onclick=&quot;Ametys.tool.ToolsManager.openTool('uitool-content', {id: '" + c.id + "'})&quot;>" + c.label.replaceAll("\"", "&quot;") + "</a>"
                    }
                    metadata.tdAttr += "<li style='margin: 5px 0 5px 0'>" + text + ' = <b>' + Number(line.value.toFixed(2)) + '</b></li>';
                }
                metadata.tdAttr += '</ul>"';
            }
            else if (record.data.effectives && record.data.effectives.inconsistent)
            {
                metadata.tdAttr = ' data-anchor="left" data-qtip="' + tdAttrInconsistent +'"';
                value =  valueHTMLSurrounded;
            }
            
        }
        return value;
    },
    
    /**
     * Render the previous years effective columns.
     * @param {Object} value The data value for the current cell.
     * @param {Object} metadata A collection of metadata about the current cell
     * @param {Ext.data.Model} record The record
     * @param {Number} rowIndex The index of the current row
     * @param {Number} cellIndex The index of the current cell
     * @param {Ext.data.Store} store The store
     * @param {Ext.view.View} view The current view
     * @private
     */
    _renderPreviousYearEffectives: function(value, metadata, record, rowIndex, cellIndex, store, view)
    {
        return this._renderCopiableData("effectivesGlobal", value, record, view);
    },
    
    /**
     * Render the previous years' groups columns.
     * @param {Object} value The data value for the current cell.
     * @param {Object} metadata A collection of metadata about the current cell
     * @param {Ext.data.Model} record The record
     * @param {Number} rowIndex The index of the current row
     * @param {Number} cellIndex The index of the current cell
     * @param {Ext.data.Store} store The store
     * @param {Ext.view.View} view The current view
     * @private
     */
    _renderPreviousYearGroups: function(value, metadata, record, rowIndex, cellIndex, store, view)
    {
        return this._renderCopiableData("groupsDisplay", value, record, view);
    },
    
    /**
     * Render data of a copiable column
     * @param {String} targetColumnDataIndex The data index of the target column.
     * @param {Object} valueToCopy The value to copy.
	 * @param {Ext.data.Model} record the record
	 * @param {Ext.view.View} view the data view
     * @private
    */
    _renderCopiableData: function(targetColumnDataIndex, valueToCopy, record, view)
    {
        if (!valueToCopy)
        {
            return '';
        }

        function _findColumn(id, columns)
        {
            let columnsFound = columns.filter(c => (c.dataIndex || c.name) == id);
            return columnsFound.length == 1 ? columnsFound[0] : null;
        }
        
        let targetColumn = _findColumn(targetColumnDataIndex, view.ownerGrid.getColumns());

        if (targetColumn)
        {
            let targetColumnLabel = targetColumn.text || targetColumn.label;
            if (targetColumn.ownerCt.xtype == 'gridcolumn')
            {
                // This is a sub-column => find the label of the group column 
                targetColumnLabel = targetColumn.ownerCt.text + ' > ' + targetColumnLabel;		
            }
            
            let tooltipMessage = Ext.String.format("{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_COPY_TOOLTIP}}", targetColumnLabel);

            // Add the copy button
            let result = "<div class=\"previousdata\">"
                    + '<span>' + valueToCopy + '</span>'
                    + '<button class="copybutton" onclick="Ametys.plugins.odf.pilotage.tool.CostModelingTool.copyPreviousData('
                        + '\'' + targetColumnDataIndex + '\', '
                        + '\'' + targetColumn.stateId + '\', '
                        + '\'' + record.data.contentId + '\', '
                        + '\'' + record.data.title + '\', '
                        + record.data[targetColumnDataIndex] + ', ' 
                        + valueToCopy 
                        + ')"'
                        + ' data-qtip="' + tooltipMessage + '"'
                        + '>'
                            + '<span class="ametysicon-file229"></span>'
                    + "</button>"
            + "</div>";
            return result;
        }
        else
        {
            // Target column not found
            return value;
        }
    }
});