/*
 *  Copyright 2017 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * This tool displays the details of an extraction
 * @private
 */
Ext.define('Ametys.plugins.extraction.execution.ExtractionDetailsTool', {
    extend: "Ametys.tool.Tool",
    
    /**
     * @readonly
     * @property {Boolean} extractionTabCompatible Specify this tool is compatible with the 'extraction tab'.
     */
    extractionTabCompatible: true,
    
    /**
     * @private
     * @property {Ext.tree.Panel} _tree The tree panel displaying the extraction details
     */
    
    /**
     * @private
     * @property {String} _definitionFilePath Definition file path.
     */
    
    /**
     * @private
     * @property {String} _definitionFileName Definition file name.
     */
    
    /**
     * @private
     * @property {String} _descriptionId Identifier of the description.
     */
          
    /**
     * @private
     * @property {Object} _author the extraction definition author
     * @property {String} _author.login the author's login
     * @property {String} _author.populationId the author's population identifier
     */
     
    /**
     * @private
     * @property {String} _canRead true if the current user can read the extraction definition
     */
     
    /**
     * @private
     * @property {String} _canWrite true if the current user can write the extraction definition
     */

    /**
     * @private
     * @property {String} _canAssignRights true if the current user can edit rights of the extraction definition
     */
    
    /**
     * @private
     * @property {Boolean} _hasCreatedNodes <code>true</code> if some nodes have been created, <code>false</code> otherwise.
     */
    
    /**
     * @private
     * @property {Boolean} _hasDeletedNodes <code>true</code> if some nodes have been deleted, <code>false</code> otherwise.
     */
    
    /**
     * @private
     * @property {Boolean} _isConcernedByCurrentModification <code>true</code> if this tool is concerned by the las MODIFYNG sent message, <code>false</code> otherwise.
     */
    
    /**
     * @private
     * @property {String} _oldFilePath the extraction definition old file path
     */
    
    constructor: function(config)
    {
        this.callParent(arguments);
        this._hasCreatedNodes = false;
        this._hasDeletedNodes = false;
        this._isConcernedByCurrentModification = false;
        
        // Bus messages listeners
        Ametys.message.MessageBus.on(Ametys.message.Message.MODIFYING, this._onDefinitionFilesModifying, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onDefinitionFilesModified, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onDefinitionFilesDeleted, this);
    },
    
    createPanel: function()
    {
        
        this._tree = Ext.create("Ext.tree.Panel", {
            scrollable: true,
            animate: true,
            flex: 1,
            cls : 'extraction-tree',
            store: this._createTreeStore(),
            root: {
                text: "{{i18n PLUGINS_EXTRACTION_TREE_ROOT_NODE_DEFULT_TEXT}}",
                tag: 'root',
                iconCls: "ametysicon-puzzle-piece1",
                expanded: true,
                loaded: true
            },
            viewConfig: {
                markDirty: false
            },
            listeners: {                
                selectionchange: Ext.bind(this.sendCurrentSelection, this),
                itemdblclick: Ext.bind(this._editNode, this)
            }
        });
        
        return this._tree;
    },
    
    getMBSelectionInteraction: function()
    {
        return Ametys.tool.Tool.MB_TYPE_ACTIVE;
    },
    
    /**
     * @private
     * Create the tree store
     * @return {Ext.data.TreeStore} The created tree store
     */
    _createTreeStore: function()
    {
        var store = Ext.create('Ext.data.TreeStore', {
            model: 'Ametys.plugins.extraction.execution.ExtractionNodeEntry',
            proxy: {
                type: 'ametys',
                serverCaller: this,
                methodName: 'getExtractionDefinitionDetails',
                methodArguments: ['definitionFilePath'],
                reader: {
                    type: 'json',
                    messageProperty: 'file-error'
                }
            },
            listeners: {
                'beforeload': this._onBeforeStoreLoad,
                'update': this._onStoreUpdate,
                scope: this
            },
            autoLoad: false
        });
        
        return store;
    },
    
    /**
     * @private
     * Fired when a Model instance has been updated.
     * @param {Ext.data.Store} store The store
     * @param {Ext.data.Model} record The Model instance that was updated
     * @param {String} operation The update operation being performed. Value may be one of:
     *
     *     Ext.data.Model.EDIT
     *     Ext.data.Model.REJECT
     *     Ext.data.Model.COMMIT
     * @param {String[]} modifiedFieldNames Array of field names changed during edit.
     * @param {Object} details An object describing the change. See the {@link Ext.util.Collection#event-itemchange itemchange event} of the store's backing collection     
     */
    _onStoreUpdate: function(store, record, operation, modifiedFieldNames, details)
    {
        this.setDirty(store.findBy(function(n) { return n.dirty; }) != -1 || this._hasCreatedNodes || this._hasDeletedNodes);
    },
    
    /**
     * Listener when definition files are beeing modifying
     * @param {Ametys.message.Message} message The edition message.
     * @private
     */
    _onDefinitionFilesModifying: function(message)
    {
        var target = message.getTarget(Ametys.message.MessageTarget.EXTRACTION_DEFINITION_FILE) || message.getTarget(Ametys.message.MessageTarget.EXTRACTION_DEFINITION_FOLDER);
        
        if (target != null)
        {
            this._isConcernedByCurrentModification = Ext.String.startsWith(this._definitionFilePath, target.getParameters().path);
            this._oldFilePath = target.getParameters().path;
        }
    },
    
    /**
     * Listener when definition files have been edited
     * Will refresh the tool
     * @param {Ametys.message.Message} message The edition message.
     * @private
     */
    _onDefinitionFilesModified: function(message)
    {
        if (this._isConcernedByCurrentModification)
        {
            var fileTarget = message.getTarget(Ametys.message.MessageTarget.EXTRACTION_DEFINITION_FILE);
            var folderTarget = message.getTarget(Ametys.message.MessageTarget.EXTRACTION_DEFINITION_FOLDER);
        
            if (fileTarget != null)
            {
            
                this._definitionFilePath = fileTarget.getParameters().path;
                this._definitionFileName = fileTarget.getParameters().name;
                
                // Get the description id from the target
                var descriptionTarget = fileTarget.getSubtarget(Ametys.message.MessageTarget.CONTENT);
                var description = descriptionTarget ? descriptionTarget.getParameters().content : undefined;
                this._descriptionId = description ? description.getId() : undefined
                
                this._definitionAuthor = fileTarget.getParameters().auhtor;
                this._definitionCanRead = fileTarget.getParameters().canRead;
                this._definitionCanWrite = fileTarget.getParameters().canWrite;
                this._definitionCanDelete = fileTarget.getParameters().canDelete;
                this._definitionCanEditRight = fileTarget.getParameters().canAssignRights;
                
                this.refresh(false, message.getParameters().major);
            }
            else if (folderTarget != null)
            {
                this._definitionFilePath = this._definitionFilePath.replace(this._oldFilePath, folderTarget.getParameters().path);
                this.refresh(false, message.getParameters().major);
            }
        }
    },
    
    /**
     * Listener when definition files have been deleted
     * @param {Ametys.message.Message} message The deletion message.
     * @private
     */
    _onDefinitionFilesDeleted: function(message)
    {
        var target = message.getTarget(Ametys.message.MessageTarget.EXTRACTION_DEFINITION_FILE) || message.getTarget(Ametys.message.MessageTarget.EXTRACTION_DEFINITION_FOLDER);
        
        if (target != null)
        {
            if (Ext.String.startsWith(this._definitionFilePath, target.getParameters().path))
            {
                this._closeCb();
            }
        }
    },
    
    sendCurrentSelection: function()
    {
        var targetParameters = {
            path: this._definitionFilePath,
            name: this._definitionFileName,
            descriptionId: this._descriptionId,
            author: this._author,
            canRead: this._canRead,
            canWrite: this._canWrite,
            canAssignRights: this._canAssignRights
        }
        
        var selection = this._tree.getSelectionModel().getSelection();
        if (selection.length > 0)
        {
            var node = selection[0];
            targetParameters.nodeTag = node.get('tag');
        }
        
        Ext.create('Ametys.message.Message', {
            type: Ametys.message.Message.SELECTION_CHANGED,
            targets: {
                id: Ametys.message.MessageTarget.EXTRACTION_DEFINITION_FILE,
                parameters: targetParameters
            }
        });
    },
    
    /**
     * @private
     * Set params to send definition file path to the callable
     * @param {Ext.data.Store} store This Store
     * @param {Ext.data.operation.Operation} operation The Ext.data.operation.Operation object that will be passed to the Proxy to
     */
    _onBeforeStoreLoad: function(store, operation)
    {
        operation.setParams(Ext.apply(operation.getParams() || {}, {
            'definitionFilePath': this._definitionFilePath
        }));
    },
    
    setParams: function(params)
    {
        this.callParent(arguments);
        this._definitionFilePath = params['id']
        this._definitionFileName = params['name'];
        this._descriptionId = params['descriptionId'];
        this._author = params['author'];
        this._canRead = params['canRead'];
        this._canWrite = params['canWrite'];
        this._canDelete = params['canDelete'];
        this._canAssignRights = params['canAssignRights'];
        this.refresh(false, true);
    },
    
    refresh: function(manual, isMajorModification)
    {
        this.showRefreshing();
        this.setTitle(this._definitionFilePath);
        this._tree.getRootNode().set('text', this._definitionFilePath, {dirty: false});
        
        if (isMajorModification)
        {
            if (manual)
            {
                Ametys.Msg.confirm("{{i18n PLUGINS_EXTRACTION_REFRESH_DETAILS_TOOL_CONFIRM_DIALOG_TITLE}}", 
                        "{{i18n PLUGINS_EXTRACTION_REFRESH_DETAILS_TOOL_CONFIRM_DIALOG_MSG}}", 
                        Ext.bind(this._refreshTree, this), 
                        this
                );
            }
            else
            {
                this._refreshTree();
            }
        }
        else
        {
            this.showRefreshed();
        }
    },
    
    /**
     * Refreshes the tree store
     * @param {String} btn which button was pressed
     */
    _refreshTree: function(btn)
    {
        if (!btn || btn == 'yes')
        {
            this._hasCreatedNodes = false;
            this._hasDeletedNodes = false;
            var selection = this._tree.getSelectionModel().getSelection()[0];
            this._tree.getStore().load({
                callback: Ext.bind(this._refreshTreeCb, this, [selection], 0)
            });
        }
        else
        {
            this.showRefreshed();
        }
    },
    
    /**
     * @private
     * Callback function after #refresh is processed
     * @param {Ext.data.Model} selection The selection
     * @param {Ext.data.TreeModel[]} records An array of records.
     * @param {Ext.data.Operation} operation The operation that triggered this load.
     * @param {Boolean} success True if the operation was successful.
     */
    _refreshTreeCb: function(selection, records, operation, success)
    {
        if (success)
        {
            this._selectNode(selection)
        }
        else
        {
            if (operation.error == 'unexisting')
            {
                this.close();
                var description = Ext.String.format("{{i18n PLUGINS_EXTRACTION_LOAD_UNEXISTING_FILE_NOTIF_DESCRIPTION}}", this._definitionFilePath);
                Ametys.notify({
                    title: "{{i18n PLUGINS_EXTRACTION_LOAD_UNEXISTING_FILE_NOTIF_TITLE}}",
                    description: description
                });
            }
        }
        this.showRefreshed();
    },
    
    /**
     * Create an new node below the selected node
     * @param {String} tag The tag of the component to create
     */
    createNode: function(tag)
    {
        var selection = this._tree.getSelectionModel().getSelection();
        if (selection.length > 0)
        {
            var parentNode = selection[0];
            
            switch (tag)
            {
                case 'query':
                    Ametys.plugins.extraction.edition.EditQueryExtractionComponentDialog.add(Ext.bind(this._createNodeCb, this, [parentNode, tag], 1));
                    break;
                case 'thesaurus':
                    Ametys.plugins.extraction.edition.EditThesaurusExtractionComponentDialog.add(Ext.bind(this._createNodeCb, this, [parentNode, tag], 1));
                    break;
                case 'count':
                    Ametys.plugins.extraction.edition.EditCountExtractionComponentDialog.add(Ext.bind(this._createNodeCb, this, [parentNode, tag], 1));
                    break;
                case 'mapping-query':
                    Ametys.plugins.extraction.edition.EditMappingQueryExtractionComponentDialog.add(Ext.bind(this._createNodeCb, this, [parentNode, tag], 1));
                    break;
                default:
                    break;
            }
        }
    },
    
    /**
     * @private
     * Callback on node creation
     * @param {Object} formValues The values from creation form
     * @param {Object} parentNode The parent node of the ont to create
     * @param {String} tag The tag of the component to create
     */
    _createNodeCb: function(formValues, parentNode, tag)
    {
        var iconCls;
        switch (tag)
        {
            case 'query':
                iconCls = "ametysicon-query-search";
                break;
            case 'thesaurus':
                iconCls = "ametysicon-books";
                break;
            case 'count':
                iconCls = "ametysicon-abacus";
                break;
            case 'mapping-query':
                iconCls = "ametysicon-squares";
                break;
            default:
                break;
        }
        var node = {
            text: formValues.componentTagName || tag,
            tag: tag,
            data: formValues,
            leaf: true,
            iconCls: iconCls
        };
        
        this._hasCreatedNodes = true;
        parentNode.appendChild(node, false, true);
        this._selectNode(node);
    },
    
    /**
     * Edit the selected extraction node
     */
    editSelectedNode: function()
    {
        var selection = this._tree.getSelectionModel().getSelection();
        if (selection.length > 0)
        {
            this._editNode(null, selection[0]);
        }
    },
    
    /**
     * @private
     * Edit an extraction node on double-click event
     * @param {Ext.view.View} view The tree view
     * @param {Ext.data.Model} node The node to edit
     */
    _editNode: function(view, node)
    {
        var tag = node.get('tag');
        var data = node.get('data');
        switch (tag)
        {
            case 'query':
                Ametys.plugins.extraction.edition.EditQueryExtractionComponentDialog.edit(data, Ext.bind(this._editNodeCb, this, [node], 1));
                break;
            case 'thesaurus':
                Ametys.plugins.extraction.edition.EditThesaurusExtractionComponentDialog.edit(data, Ext.bind(this._editNodeCb, this, [node], 1));
                break;
            case 'count':
                Ametys.plugins.extraction.edition.EditCountExtractionComponentDialog.edit(data, Ext.bind(this._editNodeCb, this, [node], 1));
                break;
            case 'mapping-query':
                Ametys.plugins.extraction.edition.EditMappingQueryExtractionComponentDialog.edit(data, Ext.bind(this._editNodeCb, this, [node], 1));
                break;
            case 'clauses-variables':
                Ametys.plugins.extraction.edition.EditClausesVariablesNodeDialog.edit(data, Ext.bind(this._editNodeCb, this, [node], 1));
                break;
            case 'optional-columns':
                Ametys.plugins.extraction.edition.EditOptionalColumnsNodeDialog.edit(data, Ext.bind(this._editNodeCb, this, [node], 1));
                break;
            default:
                break;
        }
    },
    
    /**
     * @private
     * Callback on node edition
     * @param {Object} formValues The values from edition form
     * @param {Object} node The node to edit
     */
    _editNodeCb: function(formValues, node)
    {
        switch (node.get('tag'))
        {
            case 'query':
            case 'count':
            case 'mapping-query':
            case 'thesaurus':
                node.set('text', formValues.componentTagName || node.get('tag'));
                break;
            default:
                break;
        }
        node.set('data', formValues);
    },
    
    /**
     * Delete the selected extraction node
     */
    deleteSelectedNode: function()
    {
        var selection = this._tree.getSelectionModel().getSelection();
        if (selection.length > 0)
        {
            var node = selection[0];
            
            var message = Ext.String.format("{{i18n PLUGINS_EXTRACTION_DELETE_NODE_CONFIRM_DIALOG_MSG}}", node.get('text'));
            Ametys.Msg.confirm("{{i18n PLUGINS_EXTRACTION_DELETE_NODE_CONFIRM_DIALOG_TITLE}}", 
                    message, 
                    Ext.bind(this._doDeleteNode, this, [node], 1), 
                    this
            );
        }
    },
    
    /**
     * Delete an extraction node
     * @param {String} btn which button was pressed
     * @param {Object} node The node to delete
     */
    _doDeleteNode: function(btn, node)
    {
        if (btn == 'yes')
        {
            this._hasDeletedNodes = true;
            node.remove();
        }
    },
    
    /**
     * Save modifications on the current extraction
     * @param {Function} [callback] The callback function to execute
     * @param {Boolean} [callback.success] True if the saving succeeded
     */
    saveExtraction: function(callback)
    {
        var encodedDefinitionFilePath = encodeURIComponent(this._definitionFilePath);
        var extraction = this._convertNodeToJSON(this._tree.getRootNode());
        
        Ext.create('Ametys.message.Message', {
            type: Ametys.message.Message.MODIFYING,
            targets: {
                id: Ametys.message.MessageTarget.EXTRACTION_DEFINITION_FILE,
                parameters: {
                    path: this._definitionFilePath,
                    name: this._definitionFileName,
                    descriptionId: this._descriptionId,
                    author: this._author,
                    canRead: this._canRead,
                    canWrite: this._canWrite,
                    canDelete: this._canDelete,
                    canAssignRights: this._canAssignRights
                }
            }
        });
        
        this.serverCall('saveExtraction', [encodedDefinitionFilePath, extraction], Ext.bind(this._saveExtractionCb, this, [callback], 1));
    },
    
    /**
     * Converts a node to JSON
     * @param {Object} node The node to convert
     * @return {Object} JSON conversion of the node
     */
    _convertNodeToJSON: function(node)
    {
        var obj = {};
        
        obj.tag = node.get('tag');
        obj.data = node.get('data');
        
        obj.children = [];
        Ext.Array.forEach(node.childNodes, function(childNode) {
            obj.children.push(this._convertNodeToJSON(childNode));
        }, this);
        
        return obj;
    },
    
    /**
     * @private
     * Callback on extraction saving
     * @param {boolean} success <code>true</code> if extraction saving succeed, <code>false</code> otherwise
     * @param {Function} [callback] The callback function to execute
     * @param {Boolean} [callback.success] True if the saving succeeded
     */
    _saveExtractionCb: function(success, callback)
    {
        if (!success)
        {
            Ametys.Msg.show({
                title: "{{i18n PLUGINS_EXTRACTION_SAVE_EXTRACTION_ERROR_DIALOG_TITLE}}",
                msg: "{{i18n PLUGINS_EXTRACTION_SAVE_EXTRACTION_ERROR_DIALOG_MSG}}",
                buttons: Ext.Msg.OK,
                icon: Ext.MessageBox.ERROR
            });
        }
        else
        {
            Ext.create('Ametys.message.Message', {
                type: Ametys.message.Message.MODIFIED,
                parameters: {
                    major: true
                },
                targets: {
                    id: Ametys.message.MessageTarget.EXTRACTION_DEFINITION_FILE,
                    parameters: {
                        path: this._definitionFilePath,
                        name: this._definitionFileName,
                        descriptionId: this._descriptionId,
                        author: this._author,
                        canRead: this._canRead,
                        canDelete: this._canDelete,
                        canWrite: this._canWrite,
                        canAssignRights: this._canAssignRights
                    }
                }
            });
        }
        
        if (Ext.isFunction(callback))
        {
            callback(success);
        }
    },
    
    /**
     * Cancel modifications on the current extraction
     */
    cancelModifications: function()
    {
        this.refresh(true, true);
    },
    
    /**
     * Selects the specified node.
     * @param {Ext.data.Model} selection The node to select
     * @private
     */
    _selectNode: function(selection)
    {
        if (selection) 
        {
            var node = this._tree.getStore().getNodeById(selection.id);
            if (node)
            {
                this._tree.setSelection(node);
                this._tree.ensureVisible(node.getPath());
            }
            else
            {
                this._tree.setSelection(this._tree.getRootNode());
            }
        }
        else 
        {
            this._tree.setSelection(this._tree.getRootNode());
        }
    },
    
    close: function()
    {
        if (this.isDirty())
        {
            Ametys.Msg.confirm("{{i18n PLUGINS_EXTRACTION_CLOSE_DETAILS_TOOL_CONFIRM_DIALOG_TITLE}}", 
                    "{{i18n PLUGINS_EXTRACTION_CLOSE_DETAILS_TOOL_CONFIRM_DIALOG_MSG}}", 
                    Ext.bind(this._closeCb, this), 
                    this
            );
        }
        else
        {
            this._closeCb();
        }
    },
    
    /**
     * CLose the tool if user confirmed
     * @param {String} btn which button was pressed
     */
    _closeCb: function(btn)
    {
        if (!btn || btn == 'yes')
        {
            Ametys.tool.ToolsManager.removeTool(this);
        }
    }
    
});