/*
 *  Copyright 2014 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 is a grid that can only have contents and that allow to edit it (with the correct columns).
 * It does handle messages for the bus, and request to save the content for you.
 * Consider the provided events to set your tool as dirty, or lock your tool while modifications are running.
 * 
 *      Ext.create("Ametys.cms.content.EditContentsGrid", { 
 *          store: this.store,
 *          
 *          columns: [
 *              {header: "Title", width: 250, sortable: true, dataIndex: 'title', renderer: Ametys.plugins.cms.search.SearchGridHelper.renderTitle, hideable: false, editor: { allowBlank: false}},
 *              {header: "Author", width: 200, sortable: true, dataIndex: 'contributor', editor: { allowBlank: false}},
 *              {header: "Last modified", width: 200, sortable: true, renderer: Ext.util.Format.dateRenderer(Ext.Date.patterns.ShortDateTime), dataIndex: 'lastModified'},
 *              {header: "Type", width: 150, sortable: true, dataIndex: 'content-type'},
 *              {header: "Workflow", width: 50, sortable: true, renderer: Ametys.plugins.cms.search.SearchGridHelper.renderWorkflowStep, dataIndex: 'worflow-step'}
 *          ],
 *          
 *          selModel : {
 *              mode: 'MULTI'
 *          },
 *          
 *          listeners: {
 *              'outofdate': Ext.bind(this.showOutOfDate, this, [false], false),
 *              'dirtychange': Ext.bind(this.onDirtyChange, this)
 *          }
 *      });
 *
 *      onDirtyChange: function(dirty)
 *      {
 *          this.setDirty(dirty);
 *      
 *          // Hide the search form
 *          var topDockItems = this.grid.getDockedItems("*[form]")[0].setVisible(!dirty);
 *      },
 *
 *      refresh: function()
 *      {
 *          if (this.grid.isModeEdition())
 *          {
 *              var me = this;
 *              this.grid.discardChanges(true, function() {
 *                  me.refresh();
 *              });
 *          }
 *          else
 *          {
 *              ...
 *          }
 *      }
 *
 *          
 * The only restriction is that record ids has to be content ids.
 * 
 * Consider Ametys.cms.content.EditContentsGrid#openContent and Ametys.cms.content.EditContentsGrid#openContent for the action on the double click on a cell. 
 */
Ext.define('Ametys.cms.content.EditContentsGrid', {
    extend: "Ext.grid.Panel",
    
    mixins: {
        editContentsView: "Ametys.cms.content.EditContentsView"
    },
    
    statics: {
        /**
         * Function to open a content.
         * To use like this to open a content on a double click on it.
         * Will use the default 'uitool-content'
         * 
         *          listeners: {
         *              'itemdblclick': function (view, record, item, index, e) { Ametys.cms.content.EditContentsGrid.openContent (record); }
         *          }
         * 
         * or do this to choose another Tool
         * 
         *          listeners: {
         *              'itemdblclick': function (view, record, item, index, e) { Ametys.cms.content.EditContentsGrid.openContent (record, {role: 'myfactory'}); 
         *          }
         * @param {Ext.data.Model} record The record
         * @param {Object} [options] The options
         * @param {String} [options.role='uitool-content'] The role of the factory to use, when opening the tool
         * @param {String} [options.toolParams] The parameters to passe to the opened tool
         */
        openContent: function (record, options) 
        {
            var opt = options || {};
            var role = opt.role || 'uitool-content';
            var toolParams = opt.toolParams || {};
            Ametys.tool.ToolsManager.openTool(role || 'uitool-content', Ext.apply({'id' : record.get('id')}, toolParams));
        },
        
        /**
         * Opens the double-clicked content in edition. To use like this to open a content on a double click on it.
         * Will use the default 'uitool-content'
         * 
         *          listeners: {
         *              'itemdblclick': Ametys.cms.content.EditContentsGrid.editContent
         *          }
         * 
         * or do this to choose another Tool
         * 
         *          listeners: {
         *              'itemdblclick': { fn: Ametys.cms.content.EditContentsGrid.editContent, options: {role: 'myfactory'}}
         *          }
         * 
         * @param {Ext.grid.View} view The view of the grid
         * @param {Ext.data.Model} record The record
         * @param {HTMLElement} item The html element that was clicked
         * @param {Number} index The items index
         * @param {Ext.event.Event} e The event object
         * @param {Object} [eOpts] The request parameters passed to the listener
         * @param {Object} [eOpts.options] The options for the listeners
         * @param {String} [eOpts.options.role='uitool-content'] The role of the factory to use, when opening the tool on a double-click
         */
        editContent: function (view, record, item, index, e, eOpts)
        {
            var role = eOpts.options ? eOpts.options.role : null;
            Ametys.tool.ToolsManager.openTool(role || 'uitool-content', {'id': record.get('id'), 'mode': 'edit', 'edit-workflow-action': this.workflowEditActionId, 'view-name': this.viewName });// FIXME this.workflowEditActionId will always be empty since it is static 
        }
    },

    /**
     * @private
     * @cfg {String} cls
     * @inheritdoc
     */
    cls: ['edit-contents-grid', 'a-visible-focus'],
    
    selModel : {
        mode: 'MULTI'
    },
    
    /**
     * @cfg {String} [messageTarget=Ametys.message.MessageTarget#CONTENT] The message target factory role to use for the event of the bus thrown and listened by the grid
     */
    
    /**
     * @cfg {Boolean} [enablePagination=true] Enable the pagination for the grid.
     */
    /**
     * @property {Boolean} paginationEnabled Is the pagination enabled for the grid?
     * @readonly
     */

    
    /**
     * @cfg {Boolean} [sendSelectionOnChange=true] When selecting a line, the selection message is sent to the bus automatically
     */

    constructor: function(config)
    {
        config.id = config.id || Ext.id();
        
        config.messageTarget = config.messageTarget || Ametys.message.MessageTarget.CONTENT;
        
        config.viewConfig = config.viewConfig || {};
        config.viewConfig.plugins = [{
            ptype: 'ametysgridviewdragdrop',
            dragTextField: 'title',
            setAmetysDragInfos: Ext.bind(this.getDragInfo, this),
            setAmetysDropZoneInfos: Ext.bind(this.getDropInfo, this)
        }];
        
        config.dockedItems = config.dockedItems || [];
        
        this.paginationEnabled = config.enablePagination !== false; // default to true
        if (this.paginationEnabled)
        {
            config.dockedItems.push(this._createPageBar(config));
        }
        
        this.callParent(arguments);
        
        this.mixins.editContentsView.constructor.call(this, config);
        
        // Protect the method before registering the listener to protect them
        this._protectMethods();
        
        this.store.on('remove', this._updatePagingToolbarInfo, this);
        
        if (config.sendSelectionOnChange !== false)
        {
            this.on('selectionchange', this.sendCurrentSelection, this);
        }
        
        Ametys.message.MessageBus.on(Ametys.message.Message.CREATED, this._onContentCreated, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onContentEdited, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.WORKFLOW_CHANGED, this._onContentEdited, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onContentDeleted, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.ARCHIVED, this._onContentDeleted, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.UNARCHIVED, this._onContentCreated, this);
    },

    /**
     * This method will protect many tools methods from beeing called at bad time
     * Do not call "sendCurrentSelection" if tool has not the focus, do not call "onFocus" if the tool is destroyed... 
     * @private
     */
    _protectMethods: function()
    {
        this.sendCurrentSelection = Ext.Function.createInterceptor(this.sendCurrentSelection, (_) => !this.destroyed , this);
        this._updatePagingToolbarInfo = Ext.Function.createInterceptor(this._updatePagingToolbarInfo, (_) => !this.destroyed , this);
        this._onContentDeleted = Ext.Function.createInterceptor(this._onContentDeleted, (_) => !this.destroyed , this);
        this._onContentEdited = Ext.Function.createInterceptor(this._onContentEdited, (_) => !this.destroyed , this);
        this._onContentCreated = Ext.Function.createInterceptor(this._onContentCreated, (_) => !this.destroyed , this);
        
    },
    
    /**
     * @event outofdate
     * Fires when the grid is now out of date and the store should be reloaded (you should take care of #isModeEdition before reloading).
     */
    /**
     * @event dirtychange
     * Fires when the dirty state of the grid has changed. 
     * @param {Boolean} dirty True if the grid is now dirty. False otherwise.
     */

    /**
     * Fires a event of selection on message bus, from the selected contents in the grid.
     * @protected
     */
    sendCurrentSelection: function ()
    {
        Ext.create("Ametys.message.Message", {
            type: Ametys.message.Message.SELECTION_CHANGED,
            
            targets: this.getCurrentSelectionTargets()
        });
    },
    
    /**
     * Get the current selection targets.
     * @return {Object} A target configuration object, usable in Message's {@link Ametys.message.Message#cfg-targets targets} or subtargets configuration properties.
     */
    getCurrentSelectionTargets: function()
    {
        var contentIds = [];
        
        var selection = this.getSelectionModel().getSelection();
        for (var i=0; i < selection.length; i++)
        {
            contentIds.push(selection[i].get('id'));
        }
        
        return {
            id: this.messageTarget,
            parameters: { ids: contentIds }
        };
    },
    
    /**
     * @private
     * This event is thrown by the getDragData to add the 'source' of the drag.
     * @param {Object} item The default drag data that will be transmitted. You have to add a 'source' item in it: 
     * @param {Object} item.source The source (in the relation way) of the drag operation. A Ametys.relation.RelationPoint config. 
     */
    getDragInfo: function(item)
    {
        var contentIds = [];        
        for (var i = 0; i < item.records.length; i++)
        {
            contentIds.push(item.records[i].get('id'));
        }
    
        if (contentIds.length > 0)
        {
            item.source = {
                relationTypes: [Ametys.relation.Relation.COPY, Ametys.relation.Relation.REFERENCE], 
                targets: {
                    id: this.messageTarget,
                    parameters: { ids: contentIds }
                }
            };
        }
    },
    
    /**
     * @private
     * This event is thrown before the beforeDrop event and create the target of the drop operation relation.
     * @param {Ext.data.Model[]} targetRecords The target records of the drop operation.
     * @param {Object} item The default drag data that will be transmitted. You have to add a 'target' item in it: 
     * @param {Object} item.target The target (in the relation way) of the drop operation. A Ametys.relation.RelationPoint config. 
     */ 
    getDropInfo: function(targetRecords, item)
    {
        var contentIds = [];        
        for (var i = 0; i < targetRecords.length; i++)
        {
            contentIds.push(targetRecords[i].get('id'));
        }
        
        if (contentIds.length > 0)
        {
            item.target = {
                relationTypes: [Ametys.relation.Relation.MOVE, Ametys.relation.Relation.REFERENCE, Ametys.relation.Relation.COPY], 
                targets: {
                    id: this.messageTarget,
                    parameters: { ids: contentIds }
                }
            };
        }
    },
    
    onDestroy: function()
    {
        Ametys.message.MessageBus.unAll(this);
    },
    
    getState: function()
    {
        var state = this.callParent(arguments),
            column;
        
        if (this.headerCt && this.headerCt.items)
        {
            Ext.Array.each(state.columns || [], function(columnState) {
                
                column = this.headerCt.items.findBy(function(item) {
                    return item.stateId == columnState.id;
                });
                
                if (column)
                {
                    Ext.applyIf(columnState, Ametys.plugins.cms.search.SearchGridHelper.getStateColumnPropertiesToForce(column));
                }
            }, this);
        }
        
        return state;
    },
    
    
    /**
     * @private
     * Create the paging bar
     * @param {Object} config The constructor configuration
     */
    _createPageBar: function(config)
    {
        return {
            xtype: 'pagingtoolbar',
            id: config.id + "-pagebar",
            store: config.store,
            dock: 'bottom',
            displayInfo: true,
            displayMsg: "{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_RESULT_1}}{0}{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_RESULT_2}}{1}{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_RESULT_3}}{2}",
            emptyMsg: "{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_NO_RESULT}}"
        };
    },
    
    /**
     * Get the paging bar
     * @return {Ext.toolbar.Paging} The paging toolbar
     */
    getPageBar: function()
    {
        return this.paginationEnabled ? Ext.getCmp(this.getId() + "-pagebar") : null;
    },

    /**
     * Update the paging toolbar information when store count has changed
     * @private
     */
    _updatePagingToolbarInfo: function ()
    {
        var pagingtoolbar = this.getPageBar();
        
        if (pagingtoolbar)
        {
            var displayItem = pagingtoolbar.child('#displayItem');
            if (displayItem)
            {
                var count = this.store.getCount();
                var pageData = pagingtoolbar.getPageData();
                    
                var msg = count == 0 ?
                        pagingtoolbar.emptyMsg :
                        Ext.String.format(
                            pagingtoolbar.displayMsg,
                            pageData.fromRecord, pageData.fromRecord + count - 1, count
                        );
                displayItem.setText(msg);
            }
        }
    },
	
    /**
     * Listener when contents have been edited
     * Will set the tool in "out of date" mode
     * @param {Ametys.message.Message} message The edition message.
     * @protected
     */
    _onContentEdited: function (message)
    {
        var me = this;
        var targets = message.getTargets(
                function (target)
                {
                    return new RegExp("^" + me.messageTarget + "$").test(target.getId());
                }
        );
        
        if (targets.length > 0)
        {
            // Determine the content fields
            var contentFields = [];
            Ext.Array.each(this.store.getModel().getFields(), function(field) {
                if (field.ftype == "content")
                {
                    contentFields.push(field);
                }
            });
            
            for (var i=0; i < targets.length; i++)
            {
                if (this.store.getById(targets[i].getParameters().id) != null)
                {
                    this.fireEvent("outofdate");
                    return;
                }
                
                // If the modified content is referenced, update the title
                for (var j=0; j < contentFields.length; j++)
                {
                    var contentField = contentFields[j];
                    
                    this.store.getData().each(function(record) {
                        var value = Ext.Array.from(record.get(contentField.getName()));
                        if (Ext.Array.contains(value, targets[i].getParameters().id))
                        {
                            me.fireEvent("outofdate");
                            return;
                        }
                    });
                }
            }
        }
    },
    /**
     * Listener when contents have been created
     * Will set the tool in "out of date" mode
     * @param {Ametys.message.Message} message The creation message.
     * @protected
     */
    _onContentCreated: function (message)
    {
        var me = this;
        var targets = message.getTargets(
                function (target)
                {
                    return new RegExp("^" + me.messageTarget + "$").test(target.getId());
                }
        );
        
        if (targets.length > 0)
        {
            this.fireEvent("outofdate");
        }
    },
    /**
     * Listener when contents have been deleted
     * Will remove the records if present
     * @param {Ametys.message.Message} message The deletion message.
     * @protected
     */
    _onContentDeleted: function (message)
    {
        var me = this;
        var targets = message.getTargets(
                function (target)
                {
                    return new RegExp("^" + me.messageTarget + "$").test(target.getId());
                }
        );
        
        if (targets.length > 0)
        {
            var toRemove = [];
            for (var i=0; i < targets.length; i++)
            {
                var record = this.store.getById(targets[i].getParameters().id);
                if (record != null)
                {
                    toRemove.push(record);
                }
            }
            
            // CMS-12057: Wait for removed of all elements before sending a selection change event
            this.suspendEvent('selectionchange');
            this.store.remove(toRemove);
            this.resumeEvent('selectionchange');
            this.fireEvent('selectionchange', this.getSelectionModel(), this.getSelectionModel().getSelection());
            
            this._showHideSaveBar();
        }
    }	
});