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

/**
 * Class defining the preview tool for workflows
 */
Ext.define('Ametys.plugins.workflow.tools.WorkflowPreviewTool', {
    extend: "Ametys.tool.SelectionTool",
    
    /**
     * @private
     * @property {String} _workflowName The id of the workflow displayed.
     */
    
    /**
     * @private
     * @property {String} _stepId The id of the workflow's step displayed.
     */
    
    /**
     * @private
     * @property {String} _actionId The id of the workflow's action displayed.
     */
    
    /**
     * @private
     * @property {String} _elementType The type workflow element displayed.
     */
    
    statics:
    {
        /**
         * Set svg width and height so it fill its space
         * @private
         */
        _onLoadSVG : function(svgDiv, workflowName)
        {
            svgDiv.contentDocument.getElementsByTagName("svg")[0].style.width = "100%";
            svgDiv.contentDocument.getElementsByTagName("svg")[0].style.height = "100%";
            
            svgPanZoom(document.getElementById('workflowSVG-' + workflowName));
            svgDiv.style.display = 'flex';
            svgDiv.style.opacity=1; //avoid blink on load
        }
    },
    
    displayWorkflowDetails: true,
    
    constructor: function(config)
    {
        this.callParent(arguments);
        Ametys.message.MessageBus.on(Ametys.message.Message.CREATED, this._handleMessages, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._handleMessages, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._handleMessages, this);
        Ametys.message.MessageBus.on(Ametys.plugins.workflow.tools.WorkflowEditorTool.WORKFLOW_SAVED, this.showOutOfDate, this);
        this._isGraphvizSupported = config["isGraphvizSupported"] == true || config["isGraphvizSupported"] == 'true';
    },
    
    getMBSelectionInteraction: function() 
    {
        return Ametys.tool.Tool.MB_TYPE_ACTIVE;
    },

    setParams: function (params)
    {
        this._workflowName = params.workflowId;
        this._forcedElementType = params.elementType;
        this._elementType = this._forcedElementType;
        this._stepId = params.stepId;
        this._actionId = params.actionId;
        this.callParent(arguments);
        this.showOutOfDate();
    },

    refresh: function()
    {
        this._isInitialized = true;
        if (this._forcedElementType)
        {
            this._elementType = this._forcedElementType;
            this._forcedElementType = null;
        }
        else
        {
            var targets = this.getCurrentSelectionTargets();
            if (targets.length == 1)
            {
                this._setElementTypeAndId(targets[0]);
            }
        }
        
        if (this._elementType == "workflow")
        {
            this.getContentPanel().getLayout().setActiveItem(0);
            this._displayWorkflowDiagram();
        }
        else if (this._elementType == "step")
        {
            this.getContentPanel().getLayout().setActiveItem(1);
            this._stepPropertyGrid.initGridParameters(this._workflowName, this._stepId);
            this._stepPropertyGrid.getStore().load();
            this._stepActionGrid.getStore().load();
            this._displayStepDiagram();
        }
        else
        {
            var me = this;
            me.getContentPanel().getLayout().setActiveItem(2);
            me._transitionPropertyGrid.initGridParameters(me._workflowName, me._stepId, me._actionId);
            me._transitionPropertyGrid.getStore().load();
            me._transitionActionGrid.getStore().load();
            me._displayActionDiagram();
            me._resultConditionTree.initRootNodeParameter(me._workflowName, me._actionId, function() {
                this.getSelectionModel().select(0);
                this.expandAll();
            });
            me._conditionTree.initRootNodeParameter(me._workflowName, me._actionId, function() {
                this.getSelectionModel().select(0);
                this.expandAll();
            }); 
        }
        this.showUpToDate();
    },
    
    sendCurrentSelection: function ()
    {
        var realTarget = this._getCurrentSelection();
        
        Ext.create('Ametys.message.Message', {
            type: Ametys.message.Message.SELECTION_CHANGED,
            targets: realTarget,
            parameters: {}
        });
    },
    
    /**
     * @private
     * Get current selection targets
     * @returns {Ametys.message.MessageTarget} the targets
     */
    _getCurrentSelection: function()
    {
        var realTarget = this._getMessageTargetConfiguration("workflow") || [];
            
        if (this._elementType == "step")
        {
            var stepTarget = this._getMessageTargetConfiguration('step');
            stepTarget.subtargets = [];
            var propertyTarget= this._getPropertySelection(stepTarget);
            if (propertyTarget)
            {
                stepTarget.subtargets.push(propertyTarget);
            }
            var functionTarget = this._getFunctionSelection(stepTarget);
            if (functionTarget)
            {
                stepTarget.subtargets.push(functionTarget);
            }
            realTarget.subtargets = stepTarget;
        }
        else if (this._elementType == "action")
        {
            var actionTarget = this._getMessageTargetConfiguration('action');
            actionTarget.subtargets = [];
            
            var propertyTarget= this._getPropertySelection(actionTarget);
            if (propertyTarget)
            {
                actionTarget.subtargets.push(propertyTarget);
            }
            
            var functionTarget = this._getFunctionSelection(actionTarget);
            if (functionTarget)
            {
                actionTarget.subtargets.push(functionTarget);
            }
            
            var conditionTarget = this._getConditionSelection(actionTarget);
            if (conditionTarget)
            {
                actionTarget.subtargets.push(conditionTarget);
            }
            
            var resultTarget = this._getResultSelection(actionTarget);
            if (resultTarget)
            {
                actionTarget.subtargets.push(resultTarget);
            }
            
            var steptarget = this._getMessageTargetConfiguration('step');
            steptarget.subtargets = actionTarget
            realTarget.subtargets = steptarget;
        }
        
        return realTarget;
    },
    
    /**
     * @private
     * Get the target configuration object for given workflow element type
     * @param {String} type The type of workflow element displayed in the preview
     * @return {Object} The configuration to create a Ametys.message.MessageTarget. Can be null, if the record is null or not relevant to be a messagetarget.
     */
    _getMessageTargetConfiguration: function (type)
    {
        if (type == 'workflow')
        {
            return {
                id: Ametys.message.MessageTarget.WORKFLOW_OBJECT,
                parameters: {
                    id: this._workflowName
                }
            };
        }
        else if (type == 'step')
        {
            return {
                id: Ametys.message.MessageTarget.WORKFLOW_STEP,
                parameters: {
                    id: this._stepId,
                    label: this._stepLabel,
                    workflowId: this._workflowName
                },
            };
        }
        else if (type == 'action')
        {
            return {
                id: Ametys.message.MessageTarget.WORKFLOW_ACTION,
                parameters: {
                    id: this._actionId,
                    label: this._actionLabel,
                    stepId: this._stepId,
                    workflowId: this._workflowName
                }
            };
        }
        return null;
    },
    
    /**
     * @private 
     * Get selected property in propertyGrid as target
     * @param {Object} currentTarget the workflow element being displayed in the tool
     * @returns {Object} the property target
     */
    _getPropertySelection: function(currentTarget)
    {
        var targetIsStep = currentTarget.id == Ametys.message.MessageTarget.WORKFLOW_STEP;
        var grid = targetIsStep ? this._stepPropertyGrid : this._transitionPropertyGrid;
        var selection = grid.getSelectionModel().getSelection();
        if(selection.length > 0)
        {
            var currentRecord = selection[0].getData();
            return {
                id: Ametys.message.MessageTarget.WORKFLOW_PROPERTY,
                parameters: { 
                    name: currentRecord.id,
                    value: currentRecord.value,
                    stepId: targetIsStep ? currentTarget.parameters.id : currentTarget.parameters.stepId,
                    actionId: targetIsStep ? null : currentTarget.parameters.id,
                    workflowId: this._workflowName
                }
            };
        }
        
        return null;
    },
    
    /**
     * @private 
     * Get selected function in actionGrid as target
     * @param {Object} currentTarget the workflow element being displayed in the tool
     * @returns {Object} the function target
     */
    _getFunctionSelection: function(currentTarget)
    {
        var targetIsStep = currentTarget.id == Ametys.message.MessageTarget.WORKFLOW_STEP;
        var grid = targetIsStep ? this._stepActionGrid : this._transitionActionGrid;
        var selection = grid.getSelectionModel().getSelection();
        if(selection.length > 0)
        {
            var currentRecord = selection[0].getData();
            return {
                id: Ametys.message.MessageTarget.WORKFLOW_FUNCTION,
                parameters: { 
                    index: currentRecord.index,
                    id: (currentRecord.id.split('-'))[1],
                    type: currentRecord.type,
                    isLast: currentRecord.isLast,
                    stepId: targetIsStep ? currentTarget.parameters.id : currentTarget.parameters.stepId,
                    actionId: targetIsStep ? null : currentTarget.parameters.id,
                    workflowId: this._workflowName
                }
            };
        }
        
        return null;
    },
    
    /**
     * @private 
     * Get selected condition or operator in conditionTree as target
     * @param {Object} currentTarget the workflow element being displayed in the tool
     * @returns {Object} the condition target
     */
    _getConditionSelection: function(currentTarget)
    {
        var selection = this._conditionTree.getSelectionModel().getSelection();
        if(selection.length > 0)
        {
            var currentRecord = selection[0].getData();
            return this._getConditionTarget(currentTarget, currentRecord);
        }
        return null;
    },
    
    /**
     * @private
     * Get selected step result or child condition in result tree 
     * @param {Object} currentTarget the workflow element being displayed in the tool
     * @returns {Object} a target
     */
    _getResultSelection: function(currentTarget)
    {
        var selection = this._resultConditionTree.getSelectionModel().getSelection();
        if(selection.length > 0)
        {
            var currentRecord = selection[0].getData();
            var nodeId = currentRecord.id,
                path = nodeId.split('-')
            if (path.length == 1 || path.length == 2 && path[1] == 1)
            {
                return {
                    id: Ametys.message.MessageTarget.WORKFLOW_RESULT,
                    parameters: { 
                        nodeId: nodeId,
                        nodeLabel: currentRecord.label,
                        isConditional: currentRecord.isConditional,
                        actionId: currentTarget.parameters.id,
                        stepId: currentTarget.parameters.stepId,
                        workflowId: this._workflowName
                    }
                };
            }
            else
            {
                return this._getConditionTarget(currentTarget, currentRecord);
            }
        }
        return null;
    },
    
    /**
     * @private
     * Get selected condition target
     * @param {Object} currentTarget the workflow element being displayed in the tool
     * @param {Object} currentRecord the data stocked in the selected row in the tree
     * @returns {Object} a condition(s) target
     */
    _getConditionTarget: function(currentTarget, currentRecord)
    {
        var nodeId = currentRecord.id;
        var id = nodeId.includes("condition") ? Ametys.message.MessageTarget.WORKFLOW_CONDITION : Ametys.message.MessageTarget.WORKFLOW_CONDITIONS_OPERATOR;
        if (id == Ametys.message.MessageTarget.WORKFLOW_CONDITIONS_OPERATOR)
        {
            var type = nodeId.endsWith("and", nodeId.length-1) 
                ? "AND" 
                : "OR";
            return {
                id: id,
                parameters: { 
                    nodeId: nodeId,
                    type: type,
                    actionId: currentTarget.parameters.id,
                    stepId: currentTarget.parameters.stepId,
                    workflowId: this._workflowName
                }
            };
        }
        else
        {
            return {
                id: id,
                parameters: { 
                    nodeId: nodeId,
                    conditionId: currentRecord.conditionId,
                    actionId: currentTarget.parameters.id,
                    stepId: currentTarget.parameters.stepId,
                    workflowId: this._workflowName
                }
            };
        }
    },
    
    areSameTargets: function (target1, target2)
    {
        var targetGrids = target2.getSubtarget(function(target) {
            return (
                target.getId() == Ametys.message.MessageTarget.WORKFLOW_CONDITION 
                || target.getId() == Ametys.message.MessageTarget.WORKFLOW_CONDITIONS_OPERATOR
                || target.getId() == Ametys.message.MessageTarget.WORKFLOW_PROPERTY
                || target.getId() == Ametys.message.MessageTarget.WORKFLOW_FUNCTION
                || target.getId() == Ametys.message.MessageTarget.WORKFLOW_RESULT
            );
        }, 0);
        
        if (targetGrids)
        {
            //no action or step has been modified we only need to reload the grid store
            return true;
        }
        
        // There is only one target in selection at a time, so this function is called when sending selection changed message
        // Because the is more object properties being displayed than there is in the bus message, we can't check every changes made on the target 
        // so we assume there is a change when this function is called and a refresh is needed
        return false;
    },
    
    /**
     * On selection changed, update id and type of current element to change display
     * @param {Ametys.message.MessageTarget} workflowTarget the current selection target
     * @private
     */
    _setElementTypeAndId: function(workflowTarget)
    {
        this._workflowName = workflowTarget.getParameters().id;
        var stepTargets = workflowTarget.getSubtargets(function(target){return target.getId() == Ametys.message.MessageTarget.WORKFLOW_STEP});
        if (stepTargets.length == 1)
        {
            this._stepId = stepTargets[0].getParameters().id;
            this._stepLabel = stepTargets[0].getParameters().label;
            var actionTargets = stepTargets[0].getSubtargets(function(target){return target.getId() == Ametys.message.MessageTarget.WORKFLOW_ACTION});
            if (actionTargets.length == 1)
            {
                this._elementType = "action";
                this._actionId = actionTargets[0].getParameters().id;
                this._actionLabel = actionTargets[0].getParameters().label;
            }
            else
            {
                this._elementType = "step";
            }
        }
        else
        {
            this._elementType = "workflow";
        }
    },
    
    /**
     * Display the state diagram for this workflow
     * @private
     */
    _displayWorkflowDiagram: function()
    {
        if (!this._isGraphvizSupported)
        {
            var me = this;
            window.setTimeout(function() { me._diagram.mask("{{i18n plugin.workflow:UITOOL_WORKFLOW_EDITOR_UNSUPPORTED_GRAPHVIZ}}", 'ametys-mask-unloading'); }, 0);
        }
        else
        {
            var actionArgs = {
                "workflowName": this._workflowName,
                "context.parameters": encodeURIComponent(Ext.JSON.encode(Ametys.getAppParameters()))
            };
            var html = `<object onload='Ametys.plugins.workflow.tools.WorkflowPreviewTool._onLoadSVG(this, "${this._workflowName}")' `
                                + `id='workflowSVG-${this._workflowName}' class='workflowSVG' `
                                + "style='opacity: 0;"
                                + "width: 100%;"
                                + "height: 100%;' " 
                                + "type='image/svg+xml' "
                                + `data='${this._buildDiagramURL("workflow", actionArgs)}'/>`;
            this._diagram.setHtml(html);
        }
    },
    
    /**
     * Display the step diagram
     * @private
     */
    _displayStepDiagram: function()
    {
        var actionArgs = {
            "workflowName": this._workflowName,
            "stepId": this._stepId,
            "context.parameters": encodeURIComponent(Ext.JSON.encode(Ametys.getAppParameters()))
        };
        this._displayDiagram(this._stepDiagram, "step", actionArgs);
    },
    
    /**
     * Display the transition diagram
     * @private
     */
    _displayActionDiagram: function()
    {
        var actionArgs = {
            "workflowName": this._workflowName,
            "actionId": this._actionId,
            "context.parameters": encodeURIComponent(Ext.JSON.encode(Ametys.getAppParameters()))
        };
        this._displayDiagram(this._actionDiagram, "transition", actionArgs);
    },
    
    /**
     * @private
     */
    _displayDiagram: function(diagram, action, actionArgs)
    {
        diagram.setHtml(
            "<div style='opacity: 0;"
                    + "min-width: 100%;"
                    + "width: fit-content;"
                    + "height: fit-content;"
                    + "min-height: 100%;"
                    + "justify-content: center;"
                    + "align-items: center;'>"
                + `<object onload='this.parentNode.style.display="flex";` // hack to get svg centered and on full size
                    + "this.parentNode.style.opacity=1'" // avoid blink on load
                    + "type='image/svg+xml'"
                    + `data='${this._buildDiagramURL(action, actionArgs)}'/>`
            + "</div>"
        );
    },
    
    /**
     * @private
     */
    _buildDiagramURL: function(action, actionArgs)
    {
        var urlArgs = "";
        for (var argName in actionArgs)
        {
            urlArgs += argName + "=" + actionArgs[argName] + "&";
        }
        return `${Ametys.CONTEXT_PATH}/plugins/workflow/${action}-graph.svg?${urlArgs}`;
    },
    
    createPanel: function()
    {
        return new Ext.container.Container ({
            layout: { 
                type: 'card', 
                deferredRender: false,
                activeItem: 0
            },
            cls: 'uitool-workflow',
            items: [
                this._createWorkflowPanel(),
                this._createStepPanel(),
                this._createActionPanel()
            ]
        });
    },
    
    /**
     * Create the panel for displaying the whole workflow diagram
     * @return {Object} the panel
     * @private
     */
    _createWorkflowPanel: function()
    {
        this._diagram = Ext.create("Ext.Component", {
            flex: 1,
            scrollable: false
        }); 
        return this._diagram;
    },
    
    /**
     * Create the panel for displaying a workflow step diagram and grid showing its properties
     * @return {Object} the panel
     * @private
     */
    _createStepPanel: function()
    {
        this._stepDiagram = Ext.create("Ext.Component", {
            flex: 1
        }); 
        this._stepPropertyGrid = this._createPropertyGrid("step");
        this._stepActionGrid = this._createActionGrid("step");
        var gridsPanel = Ext.create('Ext.Container', {
            flex: 0.1,
            layout: {
                type: 'hbox',
                align: 'stretch'
            },
            minHeight: 250,
            border: false,
            items: [
                this._stepActionGrid,
                {xtype: 'splitter'},
                this._stepPropertyGrid
            ],
        });
        var panel = Ext.create('Ext.Container', {
            cls: 'uitool-workflow',
            layout: {
                type: 'vbox',
                align: 'stretch'
            },
            border: false,
            items: [
                this._stepDiagram,
                {xtype: 'splitter'},
                gridsPanel
            ]
        });
        return panel;
    },
    
    /**
     * Create the panel for displaying a workflow action diagram and grids or trees showing its properties
     * @return {Object} an Ext.Container as the action panel
     * @private
     */
    _createActionPanel: function()
    {
        this._actionDiagram = Ext.create("Ext.Component", {
            flex: 1
        }); 
        
        this._transitionActionGrid = this._createActionGrid("action");
        this._transitionPropertyGrid = this._createPropertyGrid("action");
        this._resultConditionTree = this._createResultTree();
        this._conditionTree = this._createConditionTree();
        
        var gridsPanel = Ext.create('Ext.Container', {
            flex: 0.1,
            layout: {
                type: 'hbox',
                align: 'stretch'
            },
            
            minHeight: 280,
            border: false,
            items: [
                this._conditionTree,
                {xtype: 'splitter'},
                this._transitionActionGrid,
                {xtype: 'splitter'},
                this._resultConditionTree,
                {xtype: 'splitter'},
                this._transitionPropertyGrid
            ],
        });
        
        var panel = Ext.create('Ext.Container', {
            cls: 'uitool-workflow',
            layout: {
                type: 'vbox',
                align: 'stretch'
            },
            border: false,
            items: [
                this._actionDiagram,
                {xtype: 'splitter'},
                gridsPanel
            ]
        });
        return panel;
    },
    
    /**
     * Create the action grid panel
     * @param {String} elementType the element type can be 'step' or 'action'
     * @returns {Object} a Ext.grid.Panel as this action grid
     * @private
     */
    _createActionGrid : function(elementType)
    {
        var actionGrid = Ext.create('Ext.grid.Panel', {
            title: "{{i18n plugin.workflow:PLUGINS_WORKFLOW_COLUMN_FUNCTIONS_LABEL}}",
            collapsible: true,
            titleCollapse: true,
            collapseDirection: 'left',
            flex: 0.4,
            minWidth: 150,
            store: (elementType == "step") ? this._getStepActionStore() : this._getTransitionActionStore(), 
            columns: [
                {
                    dataIndex: "index",
                    hidden: true,
                    sortable: false
                },
                {
                    dataIndex: "displayValue",
                    flex: 0.8,
                    sortable: false
                },
                {
                    dataIndex: 'typeLabel',
                    flex: 0.2,
                    hidden: true,
                    sortable: false
                }
            ],
            features: [
                {
                    ftype: 'grouping',
                    enableGroupingMenu: false,
                    groupHeaderTpl: [
                        '{name:this.formatState}', 
                        {
                            formatState: function(group) {
                                switch (group) {
                                    case "1":
                                        return elementType == "step" ? "{{i18n PLUGINS_WORKFLOW_ADD_FUNCTION_DIALOG_STEP_PREFUNCTION_TYPE}}" : "{{i18n PLUGINS_WORKFLOW_ADD_FUNCTION_DIALOG_PREFUNCTION_TYPE}}";
                                    case "2":
                                    default:
                                        return elementType == "step" ? "{{i18n PLUGINS_WORKFLOW_ADD_FUNCTION_DIALOG_STEP_POSTFUNCTION_TYPE}}" : "{{i18n PLUGINS_WORKFLOW_ADD_FUNCTION_DIALOG_POSTFUNCTION_TYPE}}";
                                }
                            }
                        }
                    ]
                } 
            ],
            listeners: {
                'cellclick': {fn: this._onFunctionSelected, scope: this},
                'selectionchange': {fn: this.sendCurrentSelection, scope: this}
            }
        });
        return actionGrid;
    },
    
    /**
     * @private
     * Unselect every grids other than the functions one
     */
    _onFunctionSelected: function()
    {
        if (this._elementType == 'action')
        {
            this._resultConditionTree.getSelectionModel().deselectAll();
            this._conditionTree.getSelectionModel().deselectAll();
            this._transitionPropertyGrid.getSelectionModel().deselectAll();
        }
        else
        {
            this._stepPropertyGrid.getSelectionModel().deselectAll();
        }
    },
    
    /**
     * Create the action grid panel's store
     * @returns {Object} a Ext.data.Store as the store of this _stepActionGrid
     * @private
     */
    _getStepActionStore: function()
    {
        var store = Ext.create('Ext.data.Store', {
            model: Ametys.plugins.workflow.models.FunctionsGridModel, 
            proxy: {
                type: 'ametys',
                role: 'org.ametys.plugins.workflow.dao.WorkflowFunctionDAO',
                methodName: 'getStepFunctions',
                methodArguments: ["workflowName", "stepId"],
                reader: {
                    type: 'json',
                    rootProperty: 'data'
                }
            },
            groupField: 'typeOrder',
            listeners: {
                 'beforeload': {fn: this._onStepBeforeLoad, scope: this},
                 'load': {fn: this._onActionGridLoad , scope: this }
             }
        });
        return store;
    },
    
    /**
     * Create the action grid panel's store
     * @returns {Object} a Ext.data.Store as the store of this _transitionActionGrid
     * @private
     */
    _getTransitionActionStore: function()
    {
        var store = Ext.create('Ext.data.Store', {
            model: 'Ametys.plugins.workflow.models.FunctionsGridModel', 
            proxy: {
                type: 'ametys',
                role: 'org.ametys.plugins.workflow.dao.WorkflowFunctionDAO',
                methodName: 'getActionFunctions',
                methodArguments: ["workflowName", "actionId"],
                reader: {
                    type: 'json',
                    rootProperty: 'data'
                }
             },
             groupField: 'typeOrder',
             listeners: {
                 'beforeload': {fn: this._onActionBeforeLoad, scope: this},
                 'load': {fn: this._onActionGridLoad , scope: this }
             }
        })
        return store;
    },
    
    /**
     * Listener on action grid store load, display number of rows in the panel's title
     * @param {Object} store the store being loaded
     * @param {record} record the store content
     * @private
     */
    _onActionGridLoad: function(store, record)
    {
        var grid = (this._elementType == 'action')
            ? this._transitionActionGrid
            : this._stepActionGrid;
        grid.expand();
        grid.setTitle(grid.getInitialConfig('title') + " ("+ record.length + ")");
    },
    
    /**
     * Create the property grid
     * @param {String} elementType the element type can be 'step' or 'action'
     * @returns {Object} the property grid
     * @private 
     */
    _createPropertyGrid: function(elementType)
    {
        var grid = Ext.create('Ametys.plugins.workflow.grids.PropertyGridPanel', {
            title: "{{i18n plugin.workflow:PLUGINS_WORKFLOW_COLUMN_PROPERTIES_LABEL}}",
            collapsible: true,
            titleCollapse: true,
            collapseDirection: 'left',
            allowEdition: true,
            flex: 0.4,
            minWidth: 150,
            elementType: elementType,
            columns: [
                {
                    dataIndex: "id",
                    editor: {
                        xtype: 'textfield',
                        allowBlank: false,
                        selectOnFocus: true
                    },
                    flex: 1,
                    sortable: false
                },
                {
                    dataIndex: "value",
                    editor: {
                        xtype: 'textfield',
                        selectOnFocus: true
                    },
                    flex: 1,
                    sortable: false
                }
            ],
            listeners: {
                'cellclick': {fn: this._onPropertySelected, scope: this},
                'selectionchange': {fn: this.sendCurrentSelection, scope: this}
            }
        });
        return grid;
    },
    
    /**
     * @private
     * Unselect every grids other than the property's one
     */
    _onPropertySelected: function()
    {
        if (this._elementType == 'action')
        {
            this._resultConditionTree.getSelectionModel().deselectAll();
            this._conditionTree.getSelectionModel().deselectAll();
            this._transitionActionGrid.getSelectionModel().deselectAll();
        }
        else
        {
            this._stepActionGrid.getSelectionModel().deselectAll();
        }
    },
    
    /**
     * Create the condition tree
     * @returns {Object} a ConditionTreePanel 
     * @private
     */
    _createConditionTree: function()
    {
        this._conditionTree = Ext.create('Ametys.plugins.workflow.trees.ConditionTreePanel', {
            title: "{{i18n plugin.workflow:PLUGINS_WORKFLOW_COLUMN_CONDITION_LABEL}}",
            titleCollapse: true,
            rootVisible: false,
            allowDeselect: true,
            collapsible: true,
            collapseDirection: 'left',     
            scrollable: true,
            flex: 0.4,
            minWidth: 150,
            cls: 'uitool-conditions-tree',
            displayField: 'label',
            listeners: {
                'cellclick': {fn: this._onConditionSelected, scope: this},
                'selectionchange': {fn: this.sendCurrentSelection, scope: this}
            }
        });
        return this._conditionTree;
    },
    
    /**
     * @private
     * Unselect every grids other than the condition's one
     */
    _onConditionSelected: function()
    {
        if (this._elementType == 'action')
        this._resultConditionTree.getSelectionModel().deselectAll();
        if (this._elementType == 'action')
        {
            this._transitionActionGrid.getSelectionModel().deselectAll();
            this._transitionPropertyGrid.getSelectionModel().deselectAll();
        }
        else
        {
            this._stepActionGrid.getSelectionModel().deselectAll();
            this._stepPropertyGrid.getSelectionModel().deselectAll();
        }
    },
    
    /**
     * Create the result tree
     * @returns {Object} a ResultTreePanel 
     * @private
     */
    _createResultTree: function()
    {
        var tree = Ext.create('Ametys.plugins.workflow.trees.ResultTreePanel', {
            title: "{{i18n plugin.workflow:PLUGINS_WORKFLOW_COLUMN_RESULTS_CONDITIONS_LABEL}}",
            rootVisible: false,
            titleCollapse: true,
            collapsible: true,
            collapseDirection: 'left',
            scrollable: true,
            flex: 0.4,
            minWidth: 150,
            cls: 'uitool-result-tree',
            displayField: 'label',
            listeners: {
                'cellclick': {fn: this._onResultSelected, scope: this},
                'selectionchange': {fn: this.sendCurrentSelection, scope: this}
            }
        });
        return tree;
    },
    
    /**
     * @private
     * Unselect every grids other than the result's one
     */
    _onResultSelected: function()
    {
        this._conditionTree.getSelectionModel().deselectAll();
        if (this._elementType == 'action')
        {
            this._transitionActionGrid.getSelectionModel().deselectAll();
            this._transitionPropertyGrid.getSelectionModel().deselectAll();
        }
        else
        {
            this._stepActionGrid.getSelectionModel().deselectAll();
            this._stepPropertyGrid.getSelectionModel().deselectAll();
        }
    },
    
    /**
     * Function called before loading a transition grid 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
     * @private
     */
    _onActionBeforeLoad: function(store, operation)
    {
        operation.setParams(Ext.apply(operation.getParams() || {}, {
            workflowName: this._workflowName,
            actionId: parseInt(this._actionId)
        }));
    },
        
    /**
     * Function called before loading step grids stores
     * @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
     * @private
     */
    _onStepBeforeLoad: function(store, operation)
    {
        operation.setParams(Ext.apply(operation.getParams() || {}, {
            workflowName: this._workflowName,
            stepId: parseInt(this._stepId)
        }));
    },
    
    /**
     * Listener on created, modified and deleted messages, select the new element
     * @param {Ametys.message.Message} message The selection message.
     * @private
     */
    _handleMessages: function(message)
    {
        if (!this._isInitialized)
        {
            return;
        }
        var targetProperty = message.getTarget(function(target) {
            return target.getId() == Ametys.message.MessageTarget.WORKFLOW_PROPERTY
        }, 0);
        
        if (targetProperty)
        {
            this._onPropertySelected();
            this._onPropertyModifiedOrDeleted(targetProperty);
            return;
        }
        
        var targetFunction = message.getTarget(function(target) {
            return target.getId() == Ametys.message.MessageTarget.WORKFLOW_FUNCTION
        }, 0);
        
        if (targetFunction)
        {
            this._onFunctionSelected();
            this._onFunctionModifiedOrDeleted(targetFunction);
            return;
        }

        var targetResult = message.getTarget(function(target) {
            return (target.getId() == Ametys.message.MessageTarget.WORKFLOW_RESULT);
        }, 0);
        
        if (targetResult)
        {
            this._onResultSelected();
            this._onConditionModifiedOrDeleted(targetResult);
            return;
        }
        
        var targetCondition = message.getTarget(function(target) {
            return (target.getId() == Ametys.message.MessageTarget.WORKFLOW_CONDITION || target.getId() == Ametys.message.MessageTarget.WORKFLOW_CONDITIONS_OPERATOR);
        }, 0);
        
        if (targetCondition)
        {
            this._onConditionSelected()
            this._onConditionModifiedOrDeleted(targetCondition);
            return;
        }
        
        var actionTarget = message.getTarget(function(target) {
            return (target.getId() == Ametys.message.MessageTarget.WORKFLOW_ACTION);
        }, 0);
        
        if (actionTarget)
        {
            this._onActionSelected(actionTarget);
            return;
        }
        var stepTarget = message.getTarget(function(target) {
            return (target.getId() == Ametys.message.MessageTarget.WORKFLOW_STEP);
        }, 0);
        
        if (stepTarget)
        {
            this._onStepSelected(stepTarget);
            return;
        }
        
        var workflowTarget = message.getTarget(function(target) {
            return target.getId() == Ametys.message.MessageTarget.WORKFLOW_OBJECT
        }, 0);
        
        if (workflowTarget)
        {
            this._onWorkflowSelected();
            return;
        }
        
    },
    
    /**
     * @private
     * Update the tool's action properties
     */
    _onActionSelected: function(actionTarget)
    {
        this._elementType = "action";
        this._actionId = actionTarget.getParameters().id;
        this._actionLabel = actionTarget.getParameters().label;
        this.sendCurrentSelection();
    },
    
    /**
     * @private
     * Update the tool's step properties
     */
    _onStepSelected: function(stepTarget)
    {
        this._actionId = null;
        this._actionLabel = null;
        this._elementType = "step";
        this._stepId = stepTarget.getParameters().id;
        this._stepLabel = stepTarget.getParameters().label;
        this.sendCurrentSelection();
    },
    
    /**
     * @private
     * Update the tool's workflow properties
     */
    _onWorkflowSelected: function()
    {
        this._actionId = null;
        this._actionLabel = null;
        this._elementType = "workflow";
        this._stepId = null;
        this._stepLabel = null;
        this.sendCurrentSelection();
    },
    
    /**
     * @private
     * Reload property store
     * @param {Ametys.message.MessageTarget} target The property target.
     */
    _onPropertyModifiedOrDeleted(target)
    {
        this.focus();
        var grid = target.getParameters().actionId != null ? this._transitionPropertyGrid : this._stepPropertyGrid;
        var store = grid.getStore();
        store.load({
            callback: Ext.bind(function() {
                grid.getSelectionModel().select(store.getById(target.getParameters().name));
            }, grid)
        });
    },
    
    /**
     * @private
     * Reload function store
     * @param {Ametys.message.MessageTarget} target The function target.
     */
    _onFunctionModifiedOrDeleted(target)
    {
        this.focus();
        var grid = target.getParameters().actionId != null ? this._transitionActionGrid : this._stepActionGrid;
        var newNodeId = target.getParameters().type + "-" + target.getParameters().id + "-" + target.getParameters().index;
        var store = grid.getStore();
        store.load({
            callback: Ext.bind(function() {
                grid.getSelectionModel().select(store.getById(newNodeId));
            }, grid)
        });
    },

    /**
     * @private
     * Reload store and select edited node
     * @param {Ametys.message.MessageTarget} target The condition(s) target.
     */
    _onConditionModifiedOrDeleted(target)
    {
        this.focus();
        var isInResultTree = target.getParameters().nodeId.startsWith("step");
        var tree = isInResultTree ? this._resultConditionTree :  this._conditionTree;
        var nodeId = target.getParameters().nodeId;
        var indexOf = nodeId.lastIndexOf("-");
        var parentId = indexOf != -1 ? nodeId.substring(0, indexOf) : null;
        if (parentId != null && isInResultTree)
        {
            var parentPath = parentId.split('-');
            if (parentPath.length == 2 && parentPath[1] == "and0")
            {
                parentId = parentPath[0];
            }
        }
        var store = tree.getStore();
        var parentNode = store.getById(parentId);
        store.load({
            scope: tree,
            callback: function () 
            {
                this.expandAll(selectNewNode, tree);
            }
        });
        function selectNewNode()
        {
            if(parentNode == null)
            {
                parentNode = store.getRootNode();
            }
            var path = parentNode.getPath();
            tree.expandPath(path, {
                field: "id", 
                separator: "/", 
                callback: function (successful) {
                    if (successful)
                    {
                        tree.getSelectionModel().select(store.getById(nodeId));
                    }
                }
            })
        }
    }
    
 });
 
 /**
 * Define the model for pre and post functions
 */
Ext.define('Ametys.plugins.workflow.models.FunctionsGridModel', {
        extend: 'Ext.data.Model',
        
        fields: [
            {name: 'index', type: 'number'},
            {name: 'type', type: 'string'},
            {
                name: 'typeOrder',
                convert: function(value, record)
                {
                    var data = record.data;
                    return data.type == "PRE" ? 1 : 2;
                }
            },
            {name: 'description', type: 'string'},
            {name: 'displayValue', 
                convert: function(value, record)
                {
                    var data = record.data;
                    if (data.description)
                    {
                        return data.description;
                    }
                    return data.id;
                }
            },
            {
                name: 'id', 
                convert: function(value, record)
                {
                    var data = record.data;
                    return data.type + "-" + data.id + "-" + data.index;
                }
            }
        ]
    });