/*
 *  Copyright 2016 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
/**
 * Tool for displaying the scheduled tasks (the 'Runnables') of the application.
 * @private
 */
Ext.define('Ametys.plugins.coreui.schedule.ScheduledTasksTool', {
    extend: "Ametys.tool.Tool",
    
    /**
     * @cfg {Number} [refreshTimer=5000] The wait time between refreshes of the tool, in milliseconds. 
     * The tool also waits for the previous request to resolve before launching a new timer, which can result in a few additional seconds between refreshes if there are a lot of data to process or if the server lags.
     */
    
    /**
     * @property {Ext.data.ArrayStore} _store The store with the tasks
     * @private
     */
    
    /**
     * @property {Ext.grid.Panel} _grid The grid panel displaying the tasks
     * @private
     */
    
    /**
     * @property {Boolean} _isRunning True if the timer is running.
     */
    
    /**
     * @property {Boolean} _filterComplete True if the filter on complete tasks is active
     * @private
     */
    
    /**
     * @property {String} _filterValue The value of search filter
     * @private
     */

    statics: {
        /**
         * @private
         * @readonly
         * @property {Number} __PROGRESSION_MAXCHILD Number max of displayed children before truncating
         */
        __PROGRESSION_MAXCHILDREN: 10
    },
    
    constructor: function(config)
    {
        this.callParent(arguments);
        
        // Add the schedulables in the store of the filter
        Ametys.plugins.core.schedule.Scheduler.getSchedulables([], function(schedulables) {
            this._grid.down('#grid-schedulable-id').filter.store.loadData(schedulables);
        }, {scope: this});
        
        Ametys.message.MessageBus.on(Ametys.message.Message.CREATED, this._onMessageCreated, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onMessageModified, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onMessageDeleted, this);
    },
    
    /**
     * Gets the grid of the tool
     * @return {Ext.grid.Panel} the grid of the tool
     */
    getGrid: function()
    {
        return this._grid;
    },
    
    createPanel: function()
    {
        this._store = this._createStore();
        
        // staful button to show/hide complete task
        var me = this;
        this._filterCompleteBtn = Ext.create("Ext.Button", {
            tooltip: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_FILTER_HIDE_COMPLETE}}",
            iconCls: 'a-btn-glyph ametysicon-sign-check decorator-ametysicon-body-part-eye-no size-16',
            cls: 'a-btn-light',
            stateful: true,
            stateId: this.self.getName() + "$complete",
            enableToggle: true,
            toggleHandler: function(btn, state) {
                // The starting applyState triggers this function... too soon
                if (me._filterCompleteBtn)
                {
                    me._activeCompleteFilter(btn, state);
                    btn.saveState();
                }
            },
            applyState: function(state) {
                this.setPressed(state && state.pressed);
            },
            getState: function() {
                return {
                    pressed: this.pressed
                };
            }
        });
        
        this._grid = Ext.create("Ext.grid.Panel", { 
            store: this._store,
            stateful: true,
            stateId: this.self.getName() + "$grid",
            cls: 'ametys-scheduled-tasks',
            
            selModel: {
                mode: 'MULTI'
            },
            
            // Grouping by state
            features: [
                {
                    ftype: 'grouping',
                    enableGroupingMenu: false,
                    groupHeaderTpl: [
                        '{name:this.formatState}', 
                        {
                            formatState: function(group) {
                                switch (group) {
                                    case "0":
                                        return "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_GROUP_RUNNING_LABEL}}";
                                    case "1":
                                    default:
                                        return "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_GROUP_COMPLETED_LABEL}}";
                                    case "2":
                                        return "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_GROUP_ENABLED_LABEL}}";
                                    case "3":
                                        return "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_GROUP_DISABLED_LABEL}}";
                                }
                            }
                        }
                    ]
                } 
            ],
            viewConfig : {
                loadMask: false,
            },
            plugins: 'gridfilters',
            columns: [
                 {stateId: 'grid-state', header: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_COLUMN_STATE}}", width: 50, dataIndex: 'state', renderer: this._renderState},
                 {stateId: 'grid-title', header: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_COLUMN_TITLE}}", flex: 1, minWidth: 100, sortable: true, dataIndex: 'label'},
                 {stateId: 'grid-description', header: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_COLUMN_DESCRIPTION}}", flex: 2, dataIndex: 'description', hidden: true},
                 {stateId: 'grid-id', header: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_COLUMN_ID}}", width: 120, dataIndex: 'id', hidden: true},
                 {stateId: 'grid-cron', header: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_COLUMN_CRON}}", width: 170, dataIndex: 'cronExpression'},
                 {
                    itemId: 'grid-schedulable-id', 
                    stateId: 'grid-schedulable-id', 
                    header: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_COLUMN_SCHEDULABLE_LABEL}}", 
                    flex: 1, 
                    dataIndex: 'schedulableId',
                    renderer: this._renderSchedulableId,
                    filter: {
                        type: 'list',
                        store: Ext.create('Ext.data.Store', {
                            fields: ['id','text'],
                            sorters: 'text', 
                            data: []
                        })
                    }
                 },
                 {stateId: 'grid-schedulable-description', header: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_COLUMN_SCHEDULABLE_DESCRIPTION}}", flex: 2, dataIndex: 'schedulableDescription', hidden: true},
                 {stateId: 'grid-schedulable-parameters', header: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_COLUMN_SCHEDULABLE_PARAMETERS}}", flex: 2, dataIndex: 'schedulableParameters', renderer: this._renderSchedulableParameters, hidden: true},
                 {stateId: 'grid-system-task', header: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_COLUMN_TYPE}}", width: 100, dataIndex: 'private', renderer: this._renderTaskType},
                 {stateId: 'grid-launch-user', header: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_COLUMN_LAUNCH_USER}}", width: 100, dataIndex: 'launchUser', renderer: this._renderUser},
                 {stateId: 'grid-next-fire', header: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_COLUMN_NEXT_FIRE}}", width: 200, dataIndex: 'nextFireTime', renderer: Ametys.grid.GridColumnHelper.renderDateTime},
                 {stateId: 'grid-previous-fire', header: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_COLUMN_PREVIOUS_FIRE}}", width: 200, dataIndex: 'previousFireTime', renderer: this._renderPreviousFireTime, scope: this},
                 {stateId: 'grid-last-duration', header: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_COLUMN_LAST_DURATION}}", width: 140, dataIndex: 'lastDuration', renderer: Ext.bind(this._renderDuration, this)}
            ],
            
            dockedItems: [{
                dock: 'top',
                xtype: 'toolbar',
                layout: {
                    type: 'hbox',
                    align: 'stretch'
                },
                
                border: false,
                defaults : {
                    cls: 'ametys',
                    labelWidth: 55,
                    labelSeparator: ''
                },
                
                items: [
                    {
                    // Filter input
                    xtype: 'textfield',
                    itemId: 'search-filter-input',
                    cls: 'ametys',
                    flex: 1,
                    maxWidth: 300,
                    emptyText: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_FILTER_EMPTY_TEXT}}",
                    enableKeyEvents: true,
                    msgTarget: 'qtip',
                    listeners: {change: Ext.Function.createBuffered(this._filterTasks, 300, this)}
                }, {
                    // Clear filter
                    tooltip: "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_FILTER_CLEAR}}",
                    handler: Ext.bind (this._clearSearchFilter, this),
                    iconCls: 'a-btn-glyph ametysicon-eraser11 size-16',
                    cls: 'a-btn-light'
                }, 
                // Filter button for complete tasks
                this._filterCompleteBtn
                ]
            }],
            
            listeners: {'selectionchange': Ext.bind(this.sendCurrentSelection, this)}
        });
        
        return this._grid;
    },
    
    /**
     * @private
     * Create the store for tasks.
     * @return {Ext.data.Store} The store
     */
    _createStore: function()
    {
        return Ext.create('Ext.data.Store', {
            autoDestroy: true,
            model: 'Ametys.plugins.coreui.schedule.ScheduledTasksTool.TaskEntry',
            proxy: {
                type: 'ametys',
                plugin: 'core-ui',
                url: 'scheduledTasks.json',
                reader: {
                    type: 'json',
                    rootProperty: 'tasks'
                }
             },
             
             statefulFilters: false, // do not save filters in state
             groupField: 'group',
             sortOnLoad: true,
             sorters: [{property: 'previousFireTime', direction:'DESC'}]
        });
    },
    
    getMBSelectionInteraction: function() 
    {
        return Ametys.tool.Tool.MB_TYPE_ACTIVE;
    },
    
    setParams: function(params)
    {
        if (this._timer)
        {
            clearTimeout(this._timer);
        }
        
        this.callParent(arguments);
        
        this._refreshTimer = params.refreshTimer || 10000;
        this._isRunning = true;
        
        this.showOutOfDate();
    },
    
    refresh: function(manual)
    {
        if (manual)
        {
            this.showRefreshing();
        }
        else
        {
            this.showUpToDate();
        }
        
        this.updateSchedulables(true);
    },
    
    updateSchedulables(force)
    {
        if (!this._isRunning && !force)
        {
            return;
        }
        
        // Load the store of the grid
        this._store.load({scope: this, callback: this._updateSchedulablesCb});
    },
    
     /**
     * Callback after refreshing the tool.
     * @private
     */
    _updateSchedulablesCb()
    {
        if (!this.isNotDestroyed())
        {
            return;
        }
        
        if (this._isRunning)
        {
            if (this._timer)
            {
                clearTimeout(this._timer);
            }
            this._timer = setTimeout(Ext.bind(this.showOutOfDate, this), this._refreshTimer);
        }
        
        // Apply active filters
        this._filterTasks();

        this.showRefreshed();
    },
    
    /**
     * True if the tool is fetching live update, false if the log rotation has been paused.
     */
    isRunning: function()
    {
        return this._isRunning;
    },
    
    onClose: function()
    {
        if (this._isRunning)
        {
            this._isRunning = false;
            clearTimeout(this._timer);
            this._timer = null;
        }
        
        this.callParent(arguments);
    },
    
    sendCurrentSelection: function()
    {
        var selection = this._grid.getSelection();
        
        var tasks = [];
        Ext.Array.forEach(selection, function(task) {
            tasks.push(
                Ext.create('Ametys.plugins.coreui.schedule.Task', {
                    id: task.get('id'),
                    schedulable: task.get('schedulable').id,
                    modifiable: task.get('modifiable'),
                    removable: task.get('removable'),
                    deactivatable: task.get('deactivatable'),
                    state: task.get('state')
                })
            );
        }, this);
        
        Ext.create('Ametys.message.Message', {
            type: Ametys.message.Message.SELECTION_CHANGED,
            targets: {
                id: Ametys.message.MessageTarget.TASK,
                parameters: {
                    tasks: tasks
                }
            }
        });
    },
    
    /**
     * @private
     * Schedulable renderer
     * @param {Object} value The data value
     * @param {Object} metaData A collection of metadata about the current cell
     * @param {Ext.data.Model} record The record
     * @return {String} The html representation
     */
    _renderSchedulableId: function(value, metaData, record)
    {
        var label = record.get('schedulableLabel'),
            iconGlyph = record.get('iconGlyph'),
            iconSmall = record.get('iconSmall');
        if (!Ext.isEmpty(iconGlyph))
        {
            return '<span class="' + iconGlyph + '"></span> ' + label;
        }
        else
        {
            return '<img src="' + Ametys.getPluginResourcesPrefix('core-ui') + '/' + iconSmall + '"></img> ' + label;
        }
    },
    
    /**
     * @private
     * Renders the parameters of the parameters
     * @param {Object} value The data value
     * @param {Object} metaData A collection of metadata about the current cell
     * @param {Ext.data.Model} record The record
     * @return {String} The html representation
     */
    _renderSchedulableParameters: function(value, metaData, record)
    {
        var result = "";
        Ext.Object.each(value, function(paramId, details) {
            result += details.label + ", ";
        }, this);
        return result.replace(/, $/, "");
    },
    
    /**
     * @private
     * Renders the value of the "system" column.
     * @param {Boolean} 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
     * @return {String} The html representation
     */
    _renderTaskType: function(value, metaData, record)
    {
        var isTrue = Ext.isBoolean(value) ? value : value == 'true';
        if (isTrue)
        {
            return "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_COLUMN_TYPE_SYSTEM}}"; 
        }
        else
        {
            return "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_COLUMN_TYPE_USER}}"; 
        }
    },
    
    /**
     * @private
     * Renders the value of the "running" column.
     * @param {Boolean} 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
     * @return {String} The html representation
     */
    _renderState: function(value, metaData, record)
    {
        if (record.get('group') == '0') // running
        {
            if (record.get('success'))
            {
                return '<span class="a-grid-glyph ametysicon-play124 task-running decorator-task-success decorator-ametysicon-checked34" title="{{i18n PLUGINS_CORE_UI_TASKS_TOOL_STATE_RUNNING}}"/>';
            }
            else if (record.get('previousFireTime'))
            {
                return '<span class="a-grid-glyph ametysicon-play124 task-running decorator-task-failure decorator-ametysicon-cross-1" title="{{i18n PLUGINS_CORE_UI_TASKS_TOOL_STATE_RUNNING}}"/>';
            }
            else
            {
                return '<span class="a-grid-glyph ametysicon-play124 task-running" title="{{i18n PLUGINS_CORE_UI_TASKS_TOOL_STATE_RUNNING}}"/>';
            }
        }
        else if (record.get('group') == '1') // completed
        {
            if (value == 'success')
            {
                return '<span class="a-grid-glyph ametysicon-checked34 task-success" title="{{i18n PLUGINS_CORE_UI_TASKS_TOOL_STATE_SUCCESS}}"/>';
            }
            else if (value == 'failure')
            {
                return '<span class="a-grid-glyph ametysicon-cross-1 task-failure" title="{{i18n PLUGINS_CORE_UI_TASKS_TOOL_STATE_FAILURE}}"/>';
            }
        }
        else if (record.get('group') == '2') // active
        {
            if (record.get('success'))
            {
                return '<span class="a-grid-glyph ametysicon-datetime-clock decorator-ametysicon-checked34 decorator-task-success" title="{{i18n PLUGINS_CORE_UI_TASKS_TOOL_STATE_SUCCESS}}"/>';
            }
            else if (record.get('previousFireTime'))
            {
                return '<span class="a-grid-glyph ametysicon-datetime-clock decorator-ametysicon-cross-1 decorator-task-failure" title="{{i18n PLUGINS_CORE_UI_TASKS_TOOL_STATE_FAILURE}}"/>';
            }
            else
            {
                return '<span class="a-grid-glyph ametysicon-datetime-clock" title="{{i18n PLUGINS_CORE_UI_TASKS_TOOL_STATE_AWAITING}}"/>';
            }
        }
        else // awaiting 3
        {
            if (record.get('success'))
            {
                return '<span class="a-grid-glyph ametysicon-datetime-clock decorator-ametysicon-checked34 decorator-task-success task-disabled" title="{{i18n PLUGINS_CORE_UI_TASKS_TOOL_STATE_SUCCESS}}"/>';
            }
            else if (record.get('previousFireTime'))
            {
                return '<span class="a-grid-glyph ametysicon-datetime-clock decorator-ametysicon-cross-1 decorator-task-failure task-disabled" title="{{i18n PLUGINS_CORE_UI_TASKS_TOOL_STATE_FAILURE}}"/>';
            }
            else
            {
                return '<span class="a-grid-glyph ametysicon-datetime-clock task-disabled" title="{{i18n PLUGINS_CORE_UI_TASKS_TOOL_STATE_DISABLED}}"/>';
            }
        }
    },
    
    /**
     * @private
     * Renders the value of the "running" column.
     * @param {Boolean} 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
     * @return {String} The html representation
     */
    _renderRunning: function(value, metaData, record)
    {
        var isTrue = Ext.isBoolean(value) ? value : value == 'true';
        if (isTrue)
        {
            return '<span class="a-grid-glyph ametysicon-play124"/>';
        }
        else
        {
            return '';
        }
    },
    
    /**
     * @private
     * Renders the value of the "success" column.
     * @param {Boolean} 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
     * @return {String} The html representation
     */
    _renderSuccess: function(value, metaData, record)
    {
        if (value == null)
        {
            // The task did not run yet or is currently running, display nothing
            return '';
        }
        else if (value)
        {
            return '<span class="a-grid-glyph ametysicon-check34"/>';
        }
        else
        {
            return '<span class="a-grid-glyph ametysicon-delete30"/>';
        }
    },

    /**
     * @private
     * User renderer
     * @param {Object} value The data value
     * @param {Object} metaData A collection of metadata about the current cell
     * @param {Ext.data.Model} record The record
     * @return {String} The html representation
     */
    _renderUser: function(value, metaData, record)
    {
        if (Ext.Object.isEmpty(value))
        {
            return "";
        }
        return Ametys.helper.Users.renderUser(value.login, value.populationLabel, value.sortablename);
    },
    
    /**
     * @private
     * Specific date renderer for previous fire time (add some information if the task is running)
     * @param {Object} value The data value
     * @param {Object} metaData A collection of metadata about the current cell
     * @param {Ext.data.Model} record The record
     * @return {String} The html representation
     */
    _renderPreviousFireTime: function(value, metaData, record)
    {
        if (record.get('running'))
        {
            if (value)
            {
                // Add a message saying the task is running and so the displayed fire time is the time the currently running job was triggered
                return Ametys.grid.GridColumnHelper.renderDateTime(value, metaData, record) + " {{i18n PLUGINS_CORE_UI_TASKS_TOOL_PREVIOUS_FIRE_TIME_PREVIOUS}}<br/>" 
                    + Ametys.grid.GridColumnHelper.renderDateTime(record.data.fireTime, metaData, record) + " {{i18n PLUGINS_CORE_UI_TASKS_TOOL_RUNNING}}";
            }
            else
            {
                return Ametys.grid.GridColumnHelper.renderDateTime(record.data.fireTime, metaData, record) + " {{i18n PLUGINS_CORE_UI_TASKS_TOOL_RUNNING}}";
            }
        }
        else
        {
            return Ametys.grid.GridColumnHelper.renderDateTime(value, metaData, record);
        }
    },
    
    /**
     * @private
     * Duration renderer
     * @param {Object} value The data value
     * @param {Object} metaData A collection of metadata about the current cell
     * @param {Ext.data.Model} record The record
     */
    _renderDuration: function(value, metaData, record)
    {
        if (record.get('running'))
        {
            let data = record.data;
            return this._renderDurationForProgressiveTask(value, data);
        }
        else
        {
            return !value ? '-' : Ext.util.Format.duration(value);
        }
    },
    
    /**
     * @private
     * Round a percentage to the greater int, except if it is greater or equals to 99% tip renderer
     * @param {Object} percentage The percentage
     * @param {Boolean} fortooltip is for tooltip rendering
     */
    _roundPercentage: function(percentage, fortooltip)
    {
        var round = fortooltip ? 1 : 100;

        if (percentage >= 99)
        {
            return (Math.floor(percentage * round) / round);
        }
        else
        {
            return (Math.round(percentage * round) / round);
        }
    },
        
    /**
     * @private
     * Duration progressive task tool tip renderer
     * @param {Object} progressionTracker A progressionTracker
     */
    _createProgressiveToolTipMessageForStep: function(progressionTracker)
    {
        let label = progressionTracker.label;
        let messageToolTip = "";
        
        if (label)
        {
            messageToolTip += "<li>";
            let progressionAndLabel = "<span class='percent'>" + this._roundPercentage(progressionTracker.progression, true) + " %</span> - " + label;
            
            let size = progressionTracker.size;
            let hasSteps = progressionTracker.hasOwnProperty("steps");
            if (progressionTracker.running)
            {
                if (size > 0 && !hasSteps)
                {
                    progressionAndLabel += " " + progressionTracker.index + " / " + size;
                }
                
                messageToolTip += "<strong>" + progressionAndLabel + "</strong>" ;
                
                if (hasSteps)
                {
                    messageToolTip += "<ol>";
                    messageToolTip += this._handleSubStepsMessageToolTip(progressionTracker.steps);
                    messageToolTip += "</ol>";
                }
            }
            else
            {
                messageToolTip += progressionAndLabel;
            }
            
            messageToolTip += "</li>";
        }
        else if (progressionTracker.running && progressionTracker.hasOwnProperty("steps"))
        {
            messageToolTip += this._handleSubStepsMessageToolTip(progressionTracker.steps);
        }
    
        return messageToolTip;
    },
    
    _handleSubStepsMessageToolTip: function(steps)
    {
        let messageToolTip = "";
        
        if (steps.length <= Ametys.plugins.coreui.schedule.ScheduledTasksTool.__PROGRESSION_MAXCHILDREN)
        {
            for (let step of steps)
            {
                messageToolTip += this._createProgressiveToolTipMessageForStep(step);
            }
        }
        else
        {        
            var countBefore = 0;
            for (let step of steps)
            {
                if (step.running)
                {
                    if (countBefore == 1)
                    {
                        messageToolTip += this._createProgressiveToolTipMessageForStep(steps[0]);
                    }
                    else if (countBefore > 1)
                    {
                        // display the .. before
                        messageToolTip += "<li><span class='percent'>100 %</span> - [1... " + countBefore + "]</li>"
                    }
                    messageToolTip += this._createProgressiveToolTipMessageForStep(step);
                    
                    countAfter = steps.length - countBefore - 1;
                    if (countAfter == 1)
                    {
                        messageToolTip += this._createProgressiveToolTipMessageForStep(steps[steps.length - 1]);
                    }
                    if (countAfter > 1)
                    {
                        // display the .. after
                        messageToolTip += "<li><span class='percent'>0 %</span> - [" + (countBefore + 2) + "..." + steps.length + "]</li>"
                    }
                    break;
                }
                else
                {
                    countBefore++;
                }
            }
        }
        
        return messageToolTip;
    },
    
    /**
     * @private
     * Render duration for progressive task
     * @param {Object} data The data
     */
    _renderDurationForProgressiveTask: function(value, data)
    {
        let result = "<div>";
        let progressionTracker = data.progressionTracker;
        
        let steps = [];
    
        if (progressionTracker.hasOwnProperty("steps"))
        {
            steps = progressionTracker.steps;
        }

        let progression = this._roundPercentage(progressionTracker.progression);
        
        let duration = " - ";
        if (data.duration)
        {
            duration = Ext.util.Format.duration(data.duration);
        }
        
        let progressionLabel = "";
        if (!steps || steps.length == 0)
        {
            progressionLabel = duration + " {{i18n PLUGINS_CORE_UI_TASKS_TOOL_RUNNING}}";
        }
        else 
        {
            progressionLabel = Ext.String.format("{{i18n PLUGINS_CORE_UI_TASKS_TOOL_PREVIOUS_FIRE_TIME_AND_RUNNING_PROGRESSIVE}}", 
                duration, 
                progression);
                
            let messageToolTip = "<ol class='schedule-tip'>";
            
            messageToolTip += this._handleSubStepsMessageToolTip(steps);
            
            messageToolTip += "</ol>";
            
            result = "<div data-qtip=\"" + messageToolTip + "\">";

            if (progressionTracker.size > 1)
            {
               progressionLabel += " " + Ext.String.format("{{i18n PLUGINS_CORE_UI_TASKS_TOOL_PREVIOUS_FIRE_TIME_AND_RUNNING_PROGRESSIVE_STEP}}", 
                progressionTracker.index, 
                progressionTracker.size);
            }
        }

        let progressionMessage = "";
        if (value)
        {
            progressionMessage += Ext.util.Format.duration(value) + " {{i18n PLUGINS_CORE_UI_TASKS_TOOL_PREVIOUS_FIRE_TIME_PREVIOUS}}<br/>";
        }
        progressionMessage += progressionLabel;
        
        result += progressionMessage + "</div>";
        
        return result;
    },
    
    /**
     * @private
     */
    _isFilterOnCompleteTaskActive: function()
    {
        return this._filterCompleteBtn.pressed;
    },
    
    /**
     * @private
     */
    _activeCompleteFilter: function(btn, state)
    {
        if (state)
        {
            btn.setTooltip("{{i18n PLUGINS_CORE_UI_TASKS_TOOL_FILTER_SHOW_COMPLETE}}")
        }
        else
        {
            btn.setTooltip("{{i18n PLUGINS_CORE_UI_TASKS_TOOL_FILTER_HIDE_COMPLETE}}");
        }
        
        this._filterTasks();
    },
    
    /**
     * @private
     * Clear the current filter
     */
    _clearSearchFilter: function()
    {
        this._grid.down("#search-filter-input").reset();
        this._filterValue = null;
        
        this._filterTasks();
    },
    
     /**
     * @private
     * Filters tasks by active filters (search input field and toggle filter button) 
     */
    _filterTasks: function()
    {
        var value = Ext.String.trim(this._grid.down("#search-filter-input").getValue());
        var hideComplete = this._isFilterOnCompleteTaskActive();
        
        if (this._filterValue == value && hideComplete == this._filterComplete)
        {
            // Do nothing (filters didn't change)
            return;
        }
        
        this._filterComplete = hideComplete;
        this._filterValue = value;
        
        if (this._filterValue == null && !this._filterComplete)
        {
            this._store.clearFilter();
            return;
        }
        
        var regexFilter = this._filterValue ? new RegExp(this._filterValue, 'i') : null;
        
        this._store.clearFilter();
        
        this._store.filterBy(function(record){
            return (regexFilter == null 
                        || regexFilter.test(record.data.label) 
                        || regexFilter.test(record.data.description) 
                        || regexFilter.test(record.data.schedulableLabel) 
                        || regexFilter.test(record.data.schedulableDescription))
                   && (!hideComplete || !record.data.completed);
        });
        
        var currentSelection = this._grid.getSelection();
        if (currentSelection.length > 0)
        {
            var me = this;
            Ext.Array.each(currentSelection, function (sel) {
                if (me._store.findExact(me._store.getModel().idProperty, sel.getId()) == -1)
                {
                    // The current selection is not visible, clear the selection
                    me._grid.getSelectionModel().deselect([sel]);
                }
            })
            
        }
    },
    
    /**
     * Listener on creation message.
     * @param {Ametys.message.Message} message The edition message.
     * @private
     */
    _onMessageCreated: function(message)
    {
        var targets = message.getTargets(Ametys.message.MessageTarget.TASK);
        if (targets.length > 0)
        {
            this.showOutOfDate();
        }
    },
    
    /**
     * Listener on edition message.
     * @param {Ametys.message.Message} message The edition message.
     * @private
     */
    _onMessageModified: function(message)
    {
        var targets = message.getTargets(Ametys.message.MessageTarget.TASK);
        if (targets.length > 0)
        {
            this.showOutOfDate();
        }
    },
    
    /**
     * Listener on deletion message.
     * @param {Ametys.message.Message} message The deletion message.
     * @private
     */
    _onMessageDeleted: function(message)
    {
        var targets = message.getTargets(Ametys.message.MessageTarget.TASK);
        
        let records = targets.map(target => this._store.getById(target.getParameters().id));
        this._grid.getSelectionModel().deselect(records);
        this._store.remove(records);
    }
    
});

/**
 * This class is the model for entries in the grid of the scheduled tasks
 * @private
 */
Ext.define("Ametys.plugins.coreui.schedule.ScheduledTasksTool.TaskEntry", {
    extend: 'Ext.data.Model',
    
    fields: [
        {name: 'id'},
        {name: 'label', type: 'string'},
        {name: 'description'},
        
        {name: 'cronExpression', convert: function(v, rec) {
            switch (rec.get('fireProcess')) {
                case "NEVER":
                    return "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_NEVER_TASK}}";
                case "STARTUP":
                    return "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_STARTUP_TASK}}";
                case "NOW":
                    return "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_NOW_TASK}}";
                case "CRON":
                default:
                    return v || "-";
            }
        }},
        {name: 'enabled', defaultValue: false},
        {name: 'completed', defaultValue: false},
        {
            name: 'group', 
            calculate: function(data) {
                return data.running ? "0" : (data.completed ? "1" : (data.enabled ? "2" : "3"));
            }
        },
        
        {name: 'modifiable'},
        {name: 'removable'},
        {name: 'deactivatable'},
        
        {name: 'schedulableId', mapping: function(data) {return data.schedulable.id;}},
        {name: 'schedulableLabel', mapping: function(data) {return data.schedulable.label;}},
        {name: 'schedulableDescription', mapping: function(data) {return data.schedulable.description;}},
        {name: 'schedulableParameters', mapping: function(data) {return data.schedulable.parameters;}},
        {name: 'iconGlyph', mapping: function(data) {return data.schedulable.iconGlyph;}},
        {name: 'iconSmall', mapping: function(data) {return data.schedulable.iconSmall;}},
        {name: 'private', mapping: function(data) {return data.schedulable['private'];}},
        {name: 'launchUser'},
        {name: 'nextFireTime', convert: function(v, rec) {
            if (rec.get('completed'))
            {
                return "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_NO_NEXT_EXECUTION}}";
            }
            
            switch (rec.get('fireProcess')) {
                case "STARTUP":
                    return "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_NEXT_STARTUP_TASK}}";
                case "NOW":
                    return "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_NO_NEXT_EXECUTION}}";
                case "CRON":
                default:
                    return v || "{{i18n PLUGINS_CORE_UI_TASKS_TOOL_NO_NEXT_EXECUTION}}";
            }
        }},
        {name: 'previousFireTime'},
        {name: 'lastDuration'},
        {name: 'success'},
        {name: 'running'},
        {name: 'fireProcess'},
        {name: 'state'}
    ]
});