/*
 *  Copyright 2010 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 properties of a repository node in a table and its referrers.
 * @private
 */
Ext.define('Ametys.repository.tool.NodePropertiesTool',
{
    extend: 'Ametys.repository.tool.RepositoryTool',
    
    statics: {
        /**
         * Open the node properties tool.
         * @param {String} path The path of the node to open.
         * @param {String} workspaceName The workspace of the node to open.
         */
        open: function(path, workspaceName)
        {
            var toolParams = {
                id: path + '$' + workspaceName,
                path: path,
                workspaceName: workspaceName
            }
            
            Ametys.tool.ToolsManager.openTool('uitool-repository-nodeproperties', toolParams);
        }
    },
    
    /**
     * @private
     * @property {Boolean} _openedAtStartup Was the tool opened during the UI startup
     */
                                              
    constructor: function(config)
    {
        this.callParent(arguments);
        
        // Bus messages listeners
        Ametys.message.MessageBus.on(Ametys.message.Message.MODIFYING, this._onMessageModifying, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onMessageDeleted, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onSaveOrRevertChanges, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.REVERTED, this._onSaveOrRevertChanges, this);
    },
    
    createPanel: function()
    {
        this.referrersPanel = this._getReferrersPanel();
        
        this.propertiesPanel = Ext.create('Ametys.repository.tool.NodePropertiesTool.TablePropertiesPanel', {
            title: "{{i18n PLUGINS_REPOSITORYAPP_NODEPROPERTIES_PANEL_TITLE}}",
            
            saveProperty: Ext.bind (this._saveProperty, this),
            deleteProperty: Ext.bind(this._deleteProperty, this),
            
            downloadBinaryProperty: Ext.bind(this._downloadBinary, this),
            onClickReferenceProperty: Ext.bind(this._openReference, this)
        });
        
        return Ext.create('Ext.panel.Panel', {
            split: true,
            scrollable: true,
            
            // JCR node path
            dockedItems: {
                dock: 'top',
                ui: 'tool-hintmessage',
                xtype: 'component',
                html: ''
            },
            
            layout: {
                type: 'vbox',
                align: 'stretch'
            },
            items: [
                this.propertiesPanel,
                this.referrersPanel
            ],
            
            listeners: {
                'render': function() {
                    new Ametys.relation.dd.AmetysDropZone(this.getContentPanel().getEl(), {setAmetysDropZoneInfos: Ext.bind(this.getDropInfo, this)});
                },
                scope: this
            }
        });
    },
    
    getDropInfo: function(target, item)
    {
        item.target = {
            relationTypes: [Ametys.relation.Relation.MOVE, Ametys.relation.Relation.REFERENCE, Ametys.relation.Relation.COPY], 
            targets: [{
                id: Ametys.message.MessageTarget.REPOSITORY_NODE,
                parameters: { 
                    'paths': [this.getParams().path],
                    'workspaceName': this.getParams().workspaceName,
                }
            }]
        };
    },
    
    getMBSelectionInteraction: function() 
    {
        return Ametys.tool.Tool.MB_TYPE_ACTIVE;
    },
    
    getType: function()
    {
        return Ametys.tool.Tool.TYPE_REPOSITORY;  
    },

    sendCurrentSelection: function()
    {
        Ext.create('Ametys.message.Message', {
            type: Ametys.message.Message.SELECTION_CHANGED,
            targets: {
                id: Ametys.message.MessageTarget.REPOSITORY_NODE,
                parameters: {
                    'paths': [this.getParams().path],
                    'workspaceName': this.getParams().workspaceName,
                    'errorMessage': this._openedAtStartup ? false : undefined
                }
            }
        });
    },
    
    setParams: function(params)
    {
        this._openedAtStartup = !Ametys.tool.ToolsManager.isInitialized();
        
        var selectionHasChanged = params.path != this.getParams().path || params.workspaceName != this.getParams().workspaceName;
        
        this.callParent(arguments);
        
        if (selectionHasChanged)
        {
            this.refresh();
        }
    },
    
    refresh: function(manual)
    {
        this.showRefreshing();
        
        var nodePath = this.getParams().path;
        var workspaceName = this.getParams().workspaceName;
        
        Ametys.data.ServerComm.send({
            workspace: Ametys.WORKSPACE_NAME,
            url: 'repository/node',
            parameters: {
                workspace: workspaceName,
                path: nodePath.substring(1)
            },
            priority: Ametys.data.ServerComm.PRIORITY_MAJOR,
            callback: {
                handler: this._refreshCb,
                scope: this
            }
        });
    },
    
    /**
     * Callback: update all the UI (toolbar and panels) to reflect the selected repository node.
     * @param {HTMLElement} response The XML document.
     * @param {Array} args The callback arguments.
     * @private
     */
    _refreshCb: function(response, args)
    {
        if (Ametys.data.ServerComm.isBadResponse(response))
        {
            Ametys.notify({
                title: "{{i18n PLUGINS_REPOSITORYAPP_NODEPROPERTIES_ERROR_NOTFOUND_TITLE}}",
                description: "{{i18n PLUGINS_REPOSITORYAPP_NODEPROPERTIES_ERROR_NOTFOUND_DESCRIPTION}}",
                type: "warn"
            });

            this.close();
            return;
        }
        
        this._openedAtStartup = false;
                    
        var name = Ext.dom.Query.selectValue('repository > node > @name', response, '');
        if (name == '')
        {
            name = '/';
        }
        var pathWithGroups = Ext.dom.Query.selectValue('repository > node > @pathWithGroups', response, '');
        
        this.setTitle(name + ' (' + this.getParams().workspaceName + ')');
        
        // Update path
        this.getContentPanel().down("*[dock='top']").update(pathWithGroups);
        
        // Update properties
        var properties = Ext.dom.Query.select('> repository > node > property', response);
        this.propertiesPanel.updatePropertiesFromXml(properties);
        
        // Update referrers
        var referrers = Ext.dom.Query.select('> repository > node > referer', response);
        this._updateReferrers(referrers);
        
        this.showRefreshed();
    },
    
    /**
     * @private
     * Get the referrers panel.
     * @return {Ext.panel.Panel} the referrers panel.
     */
    _getReferrersPanel: function()
    {
        return Ext.create('Ext.grid.Panel', {
            title: "{{i18n PLUGINS_REPOSITORYAPP_REFERRERS_PANEL_TITLE}}",
            
            collapsible: true,
            titleCollapse: true,
            animCollapse: true,
            header: {
                titlePosition: 1
            },
            
            cls: 'referrers-panel',
            ui: 'light',
            border: false,
            
            hidden: false,
            forceLayout: true,
            scrollable: false,
            disableSelection: true,
            
            store: {
                autoDestroy: true,
                id: 'ReferrerStore',
                fields: [
                   {name: 'path'},
                   {name: 'property'}
                ]
            },
            
            // TODO Action
            columns: [{
                text: "{{i18n PLUGINS_REPOSITORYAPP_REFERRERS_PANEL_PATH_COLUMN_HEADER}}",
                xtype: 'templatecolumn',
                flex: 0.4,
                dataIndex: 'path',
                tpl: '<a href="#" onclick="Ametys.repository.tool.NodePropertiesTool.open(\'{path}\', \'{workspace}\')">{path}</a>'
            }, {
                text: "{{i18n PLUGINS_REPOSITORYAPP_REFERRERS_PANEL_PROPERTY_COLUMN_HEADER}}",
                flex: 0.6,
                dataIndex: 'property'
            }]
        });
    },
    
        
    /**
     * @private
     * This listener is invoked when a node has on-going changes
     * @param {Ametys.message.Message} message The bus message.
     */
    _onMessageModifying: function(message)
    {
        var target = message.getTarget(Ametys.message.MessageTarget.REPOSITORY_NODE);
        if (target != null && target.getParameters().workspaceName == this.getParams().workspaceName && target.getParameters().path == this.getParams().path)
        {
            if (message.getParameters().major)
            {
                this.showOutOfDate();
            }
        }
    },
    
    /**
     * @private
     * This listener is invoked when a node has been deleted
     * @param {Ametys.message.Message} message The bus message.
     */
    _onMessageDeleted: function(message)
    {
        var target = message.getTarget(Ametys.message.MessageTarget.REPOSITORY_NODE);
        if (target != null && target.getParameters().workspaceName == this.getParams().workspaceName && target.getParameters().path == this.getParams().path)
        {
            // Close the tool
            this.close();
        }
    },
    
    /**
     * @private
     * This listener is invoked when the repository session has been saved or rolled back
     * @param {Ametys.message.Message} message The bus message.
     */
    _onSaveOrRevertChanges: function(message)
    {
        var target = message.getTarget(Ametys.message.MessageTarget.REPOSITORY_SESSION);
        if (target != null && target.getParameters().workspaceName == this.getParams().workspaceName)
        {
            this.showOutOfDate();
        }
    },
    
    /**
     * @private
     * Update the referrers panel to reflect the currently selected node.
     * @param {HTMLElement[]} referrers The referrer XML elements.
     */
    _updateReferrers: function(referrers)
    {
        if (referrers == null || referrers.length == 0)
        {
            this.referrersPanel.hide();
        }
        else
        {
            var myData = [];
            for (var i=0; i < referrers.length; i++)
            {
                var referrer = referrers[i];
                
                var path = referrer.getAttribute('path');
                var property = referrer.getAttribute('name');
                
                myData.push({
                    path: path,
                    property: property,
                    workspace: this.getParams().workspaceName
                });      
            }   
            
            this.referrersPanel.getStore().loadData(myData);
            this.referrersPanel.setTitle("{{i18n PLUGINS_REPOSITORYAPP_REFERRERS_PANEL_TITLE}} (" + referrers.length + " {{i18n PLUGINS_REPOSITORYAPP_REFERRERS_ELEMENT}})");
            this.referrersPanel.show();
        }
    },
    
    /**
     * Search and open a node by identifier.
     * @param {String} identifier The node identifier.
     * @private
     */
    _openReference: function (identifier)
    {
        // Get the node path from its identifier.
        Ametys.data.ServerComm.callMethod({
            role: 'org.ametys.workspaces.repository.jcr.RepositoryDao', 
            methodName: 'getNodeByIdentifier',
            parameters: [identifier, this.getParams().workspaceName],
            callback: {
                handler: this._getNodeByIdentifierCb,
                scope: this
            }
        });
    },
    
    /**
     * Callback fired when the node path is known.
     * @param {Object} nodeInfo The node information, if found.
     * @param {Array} args The callback arguments.
     * @private
     */
    _getNodeByIdentifierCb: function(nodeInfo, args)
    {
        var nodePath = nodeInfo.path;
        var workspaceName = this.getParams().workspaceName;
        
        Ametys.repository.tool.NodePropertiesTool.open(nodePath, workspaceName);
    },
    
    /**
     * Read a binary from a binary property (using servercomm)
     * @param {String} name The binary property name.
     * @private
     */
    _downloadBinary: function(name)
    {
        // FIXME : binary received in XML response from servercomm, bug if not XML binary (eg picture)
        var path = this.getParams().path;
        
        var args = {
            'workspace': this.getParams().workspaceName,
            'path': path,
            'property': name
        };

        Ametys.openWindow(Ametys.getPluginDirectPrefix('repositoryapp') + '/repository/read-binary', args);
    },
    
    /**
     * Save the new property value.
     * @param {Ext.form.Panel} form The form panel to submit the value
     * @param {String} type The property type
     * @param {String} id The field id
     * @param {String} name The property name.
     * @param {String} value The property new value as string
     * @param {Boolean} multiple true if the metadata is multiple
     * @param {Function} [successCb] The callback function to call in case of success
     * @param {Function} [failureCb] The callback function to call in case of failure
     * @private
     */
    _saveProperty: function(form, type, id, name, value, multiple, successCb, failureCb)
    {
        form.getForm().submit({
            url: Ametys.getPluginDirectPrefix('repositoryapp') + '/repository/set-property',
            params: {
                path: this.getParams().path,
                workspace: this.getParams().workspaceName,
                name: name,
                value: value,
                type: type
            },
            success: Ext.bind(this._savePropertySuccess, this, [successCb], true),
            failure: Ext.bind(this._savePropertyFail, this, [failureCb], true)
        });
    },
    
    /**
     * Callback function invoked when a property has been successfully saved.
     * @param {Ext.form.Basic} form The form that requested the action.
     * @param {Ext.form.action.Action} action The Action class.
     * @param {Function} [callback] The callback function
     * @private
     */
    _savePropertySuccess: function (form, action, callback)
    {
        if (Ext.isFunction(callback))
        {
            callback();
        }
        
        Ext.create('Ametys.message.Message', {
            type: Ametys.message.Message.MODIFYING,
            parameters: {
                major: false
            },
            targets: {
                id: Ametys.message.MessageTarget.REPOSITORY_NODE,
                parameters: {
                    paths: [this.getParams().path],
                    workspaceName: this.getParams().workspaceName
                }
            }
        });
    },
    
    /**
     * Callback function invoked when a property failed to be saved.
     * @param {Ext.form.Basic} form The form that requested the action.
     * @param {Ext.form.action.Action} action The Action class.
     * @param {Function} [callback] The callback function
     * @private
     */
    _savePropertyFail: function(form, action, callback)
    {
        if (Ext.isFunction(callback))
        {
            callback();
        }
        
        Ametys.log.ErrorDialog.display({
            title: "{{i18n PLUGINS_REPOSITORYAPP_SAVE_PROPERTY_ERROR_TITLE}}",
            text: "{{i18n PLUGINS_REPOSITORYAPP_SAVE_PROPERTY_ERROR}}",
            details: action.result ? action.result.message : ''
        });
    },
    
    /**
     * Delete a property.
     * @param {String} name The property's name.
     * @param {Function} [successCb] The callback function to call in case of success
     * @param {Function} [failureCb] The callback function to call in case of failure
     * @private
     */
    _deleteProperty: function(name, successCb, failureCb)
    {
        Ametys.Msg.confirm(
            "{{i18n PLUGINS_REPOSITORYAPP_DELETE_PROPERTY_CONFIRM_TITLE}}", 
            "{{i18n PLUGINS_REPOSITORYAPP_DELETE_PROPERTY_CONFIRM_TEXT}} '" + name + "' ?", 
            function (btn) {
                if (btn == 'yes')
                {
                    this._doDelete(name, successCb);
                }
            },
            this
        );
    },
    
    /**
     * Delete a property (after user confirmation).
     * @param {String} name The property name.
     * @param {Function} [successCb] The callback function to call in case of success
     * @private
     */
    _doDelete: function(name, successCb)
    {
        Ametys.workspace.repository.actions.RemoveProperty.act(this.getParams().path, this.getParams().workspaceName, name, successCb);
    }
});