/**
 * A widget column is configured with a {@link #widget} config object which specifies an
 * {@link Ext.Component#cfg-xtype xtype} to indicate which type of Widget or Component belongs
 * in the cells of this column.
 *
 * When a widget cell is rendered, a {@link Ext.Widget Widget} or {@link Ext.Component Component} of the specified type
 * is rendered into that cell. Its {@link Ext.Component#defaultBindProperty defaultBindProperty} is set using this
 * column's {@link #dataIndex} field from the associated record.
 *
 * In the example below we are monitoring the throughput of electricity substations. The capacity being
 * used as a proportion of the maximum rated capacity is displayed as a progress bar. As new data arrives and the
 * instantaneous usage value is updated, the `capacityUsed` field updates itself, and
 * that is used as the {@link #dataIndex} for the `WidgetColumn` which contains the
 * progress bar. The progress Bar's
 * {@link Ext.ProgressBarWidget#defaultBindProperty defaultBindProperty} (which is
 * "value") is set to the calculated `capacityUsed`.
 *
 *     @example
 *     var grid = new Ext.grid.Panel({
 *         title: 'Substation power monitor',
 *         width: 600,
 *         columns: [{
 *             text: 'Id',
 *             dataIndex: 'id',
 *             width: 120
 *         }, {
 *             text: 'Rating',
 *             dataIndex: 'maxCapacity',
 *             width: 80
 *         }, {
 *             text: 'Avg.',
 *             dataIndex: 'avg',
 *             width: 85,
 *             formatter: 'number("0.00")'
 *         }, {
 *             text: 'Max',
 *             dataIndex: 'max',
 *             width: 80
 *         }, {
 *             text: 'Instant',
 *             dataIndex: 'instant',
 *             width: 80
 *         }, {
 *             text: '%Capacity',
 *             width: 150,
 *
 *             // This is our Widget column
 *             xtype: 'widgetcolumn',
 *             dataIndex: 'capacityUsed',
 *
 *             // This is the widget definition for each cell.
 *             // Its "value" setting is taken from the column's dataIndex
 *             widget: {
 *             xtype: 'progressbarwidget',
 *                 textTpl: [
 *                     '{percent:number("0")}% capacity'
 *                 ]
 *             }
 *         }],
 *         renderTo: document.body,
 *         disableSelection: true,
 *         store: {
 *            fields: [{
 *                name: 'id',
 *                type: 'string'
 *            }, {
 *                name: 'maxCapacity',
 *                type: 'int'
 *            }, {
 *                name: 'avg',
 *                type: 'int',
 *                calculate: function(data) {
 *                    // Make this depend upon the instant field being set which sets the sampleCount and total.
 *                    // Use subscript format to access the other pseudo fields which are set by the instant field's converter
 *                    return data.instant && data['total'] / data['sampleCount'];
 *                }
 *            }, {
 *                name: 'max',
 *                type: 'int',
 *                calculate: function(data) {
 *                    // This will be seen to depend on the "instant" field.
 *                    // Use subscript format to access this field's current value to avoid circular dependency error.
 *                    return (data['max'] || 0) < data.instant ? data.instant : data['max'];
 *                }
 *            }, {
 *                name: 'instant',
 *                type: 'int',
 *
 *                // Upon every update of instantaneous power throughput,
 *                // update the sample count and total so that the max field can calculate itself
 *                convert: function(value, rec) {
 *                    rec.data.sampleCount = (rec.data.sampleCount || 0) + 1;
 *                    rec.data.total = (rec.data.total || 0) + value;
 *                    return value;
 *                },
 *               depends: []
 *            }, {
 *                name: 'capacityUsed',
 *                calculate: function(data) {
 *                    return data.instant / data.maxCapacity;
 *                }
 *            }],
 *            data: [{
 *                id: 'Substation A',
 *                maxCapacity: 1000,
 *                avg: 770,
 *                max: 950,
 *                instant: 685
 *            }, {
 *                id: 'Substation B',
 *                maxCapacity: 1000,
 *                avg: 819,
 *                max: 992,
 *                instant: 749
 *            }, {
 *                id: 'Substation C',
 *                maxCapacity: 1000,
 *                avg: 588,
 *                  max: 936,
 *                instant: 833
 *            }, {
 *                id: 'Substation D',
 *                maxCapacity: 1000,
 *                avg: 639,
 *                max: 917,
 *                instant: 825
 *            }]
 *        }
 *     });
 *
 *     // Fake data updating...
 *     // Change one record per second to a random power value
 *     Ext.interval(function() {
 *         var recIdx = Ext.Number.randomInt(0, 3),
 *             newPowerReading = Ext.Number.randomInt(500, 1000);
 *
 *         grid.store.getAt(recIdx).set('instant', newPowerReading);
 *     }, 1000);
 *
 * @since 5.0.0
 */
Ext.define('Ext.grid.column.Widget', {
    extend: 'Ext.grid.column.Column',
    alias: 'widget.widgetcolumn',
 
    config: {
        /**
         * @cfg defaultWidgetUI
         * A map of xtype to {@link Ext.Component#ui} names to use when using Components in this column.
         *
         * Currently {@link Ext.Button Button} and all subclasses of {@link Ext.form.field.Text TextField} default
         * to using `ui: "default"` when in a WidgetColumn except for in the "classic" theme, when they use ui "grid-cell".
         */
        defaultWidgetUI: {}
    },
 
    ignoreExport: true,
 
    /**
     * @cfg
     * @inheritdoc
     */
    sortable: false,
 
    /**
     * @cfg {Object} renderer 
     * @hide
     */
 
    /**
     * @cfg {Object} scope 
     * @hide
     */
 
    /**
     * @cfg {Object} widget 
     * A config object containing an {@link Ext.Component#cfg-xtype xtype}.
     *
     * This is used to create the widgets or components which are rendered into the cells of this column.
     *
     * This column's {@link #dataIndex} is used to update the widget/component's {@link Ext.Component#defaultBindProperty defaultBindProperty}.
     *
     * The widget will be decorated with 2 methods:
     * `getWidgetRecord` - Returns the {@link Ext.data.Model record} the widget is associated with.
     * `getWidgetColumn` - Returns the {@link Ext.grid.column.Widget column} the widget 
     * was associated with.
     */
    
    /**
     * @cfg {Function/String} onWidgetAttach
     * A function that will be called when a widget is attached to a record. This may be useful for
     * doing any post-processing.
     * 
     *     Ext.create({
     *         xtype: 'grid',
     *         title: 'Student progress report',
     *         width: 250,
     *         renderTo: Ext.getBody(),
     *         disableSelection: true,
     *         store: {
     *             fields: ['name', 'isHonorStudent'],
     *             data: [{
     *                 name: 'Finn',
     *                 isHonorStudent: true
     *             }, {
     *                 name: 'Jake',
     *                 isHonorStudent: false
     *             }]
     *         },
     *         columns: [{
     *             text: 'Name',
     *             dataIndex: 'name',
     *             flex: 1
     *         }, {
     *             xtype: 'widgetcolumn',
     *             text: 'Honor Roll',
     *             dataIndex: 'isHonorStudent',
     *             width: 150,
     *             widget: {
     *                 xtype: 'button',
     *                 handler: function() {
     *                     // print certificate handler
     *                 }
     *             },
     *             // called when the widget is initially instantiated
     *             // on the widget column
     *             onWidgetAttach: function(col, widget, rec) {
     *                 widget.setText('Print Certificate');
     *                 widget.setDisabled(!rec.get('isHonorStudent'));
     *             }
     *         }]
     *     });
     * 
     * @param {Ext.grid.column.Column} column The column.
     * @param {Ext.Component/Ext.Widget} widget The {@link #widget} rendered to each cell.
     * @param {Ext.data.Model} record The record used with the current widget (cell).
     * @declarativeHandler
     */
    onWidgetAttach: null,
 
    preventUpdate: true,
 
    /**
     * @cfg {Boolean} [stopSelection=true]
     * Prevent grid selection upon click on the widget.
     */
    stopSelection: true,
 
    initComponent: function() {
        var me = this,
            widget;
 
        me.callParent(arguments);
 
        widget = me.widget;
        //<debug> 
        if (!widget || widget.isComponent) {
            Ext.raise('column.Widget requires a widget configuration.');
        }
        //</debug> 
        me.widget = widget = Ext.apply({}, widget);
 
        // Apply the default UI for the xtype which is going to feature in this column. 
        if (!widget.ui) {
            widget.ui = me.getDefaultWidgetUI()[widget.xtype] || 'default';
        }
        me.isFixedSize = Ext.isNumber(widget.width);
    },
 
    processEvent : function(type, view, cell, recordIndex, cellIndex, e, record, row) {
        var target;
         
        if (this.stopSelection && type === 'click') {
            // Grab the target that matches the cell inner selector. If we have a target, then, 
            // that means we either clicked on the inner part or the widget inside us. If  
            // target === e.target, then it was on the cell, so it's ok. Otherwise, inside so 
            // prevent the selection from happening 
            target = e.getTarget(view.innerSelector);
            if (target && target !== e.target) {
                e.stopSelection = true;
            }
        }
    },
 
    beforeRender: function() {
        var me = this,
            tdCls = me.tdCls,
            widget;
 
        me.listenerScopeFn = function (defaultScope) {
            if (defaultScope === 'this') {
                return this;
            }
            return me.resolveListenerScope(defaultScope);
        };
 
        me.liveWidgets = {};
        me.cachedStyles = {};
        me.freeWidgetStack = [widget = me.getFreeWidget()];
 
        tdCls = tdCls ? tdCls + ' ' : '';
        me.tdCls = tdCls + widget.getTdCls();
        me.setupViewListeners(me.getView());
        me.callParent();
    },
 
    afterRender: function() {
        var view = this.getView();
 
        this.callParent();
        // View already ready, means we were added later so go and set up our widgets, but if the grid 
        // is reconfiguring, then the column will be rendered & the view will be ready, so wait until 
        // the reconfigure forces a refresh 
        if (view && view.viewReady && !view.ownerGrid.reconfiguring) {
            this.onViewRefresh(view, view.getViewRange());
        }
    },
 
    // Cell must be left blank 
    defaultRenderer: Ext.emptyFn, 
 
    updater: function(cell, value, record) {
        this.updateWidget(record);
    },
 
    onResize: function(newWidth) {
        var me = this,
            liveWidgets = me.liveWidgets,
            view = me.getView(),
            id, cell;
 
        if (!me.isFixedSize && me.rendered && view && view.viewReady) {
            cell = view.getEl().down(me.getCellInnerSelector());
            if (cell) {
                // Subtract innerCell padding width 
                newWidth -= parseInt(me.getCachedStyle(cell, 'padding-left'), 10) + parseInt(me.getCachedStyle(cell, 'padding-right'), 10);
 
                for (id in liveWidgets) {
                    liveWidgets[id].setWidth(newWidth);
                }
            }
        }
    },
 
    onAdded: function() {
        var me = this,
            view;
 
        me.callParent(arguments);
 
        view = me.getView();
 
        // If we are being added to a rendered HeaderContainer 
        if (view) {
            me.setupViewListeners(view);
 
            if (view && view.viewReady && me.rendered && view.getEl().down(me.getCellSelector())) {
                // If the view is ready, it means we're already rendered. 
                // At this point the view may refresh "soon", however we don't have 
                // a way of knowing that the view is pending a refresh, so we need 
                // to ensure the widgets get hooked up correctly here 
                me.onViewRefresh(view, view.getViewRange());
            }
        }
    },
 
    onRemoved: function(isDestroying) {
        var me = this,
            liveWidgets = me.liveWidgets,
            viewListeners = me.viewListeners,
            id;
 
        if (me.rendered) {
            me.viewListeners = viewListeners && Ext.destroy(viewListeners);
 
            // If we are being removed, we have to move all widget elements into the detached body 
            if (!isDestroying) {
                for (id in liveWidgets) {
                    liveWidgets[id].detachFromBody();
                }
            }
        }
        me.callParent(arguments);
    },
 
    onDestroy: function() {
        var me = this,
            oldWidgetMap = me.liveWidgets,
            freeWidgetStack = me.freeWidgetStack,
            id, widget, i, len;
 
        if (me.rendered) {
            for (id in oldWidgetMap) {
                widget = oldWidgetMap[id];
                widget.$widgetRecord = widget.$widgetColumn = null;
                delete widget.getWidgetRecord;
                delete widget.getWidgetColumn;
                widget.destroy();
            }
        
            for (= 0, len = freeWidgetStack.length; i < len; ++i) {
                freeWidgetStack[i].destroy();
            }
        }
        
        me.freeWidgetStack = me.liveWidgets = null;
        
        me.callParent();
    },
 
    getWidget: function(record) {
        var liveWidgets = this.liveWidgets,
            widget;
 
        if (record && liveWidgets) {
            widget = liveWidgets[record.internalId];
        }
        return widget || null;
    },
 
    privates: {
        getCachedStyle: function(el, style) {
            var cachedStyles = this.cachedStyles;
          return cachedStyles[style] || (cachedStyles[style] = Ext.fly(el).getStyle(style));
        },
 
        getFreeWidget: function() {
            var me = this,
                result = me.freeWidgetStack ? me.freeWidgetStack.pop() : null;
 
            if (!result) {
                result = Ext.widget(me.widget);
 
                result.resolveListenerScope = me.listenerScopeFn;
                result.getWidgetRecord = me.widgetRecordDecorator;
                result.getWidgetColumn = me.widgetColumnDecorator;
                result.dataIndex = me.dataIndex;
                result.measurer = me;
                result.ownerCmp = me.getView();
                // The ownerCmp of the widget is the encapsulating view, which means it will be considered 
                // as a layout child, but it isn't really, we always need the layout on the 
                // component to run if asked. 
                result.isLayoutChild = me.returnFalse;
            }
            return result;
        },
 
        onBeforeRefresh: function () {
            var liveWidgets = this.liveWidgets,
                id;
 
            // Because of a memory leak bug in IE 8, we need to handle the dom node here before 
            // it is destroyed. 
            // See EXTJS-14874. 
            for (id in liveWidgets) {
                liveWidgets[id].detachFromBody();
            }
        },
 
        onItemAdd: function(records, index, items) {
            var me = this,
                view = me.getView(),
                hasAttach = !!me.onWidgetAttach,
                dataIndex = me.dataIndex,
                isFixedSize = me.isFixedSize,
                len = records.length, i,
                record,
                row,
                cell,
                widget,
                el,
                focusEl,
                width;
 
            // Loop through all records added, ensuring that our corresponding cell in each item 
            // has a Widget of the correct type in it, and is updated with the correct value from the record. 
            if (me.isVisible(true)) {
                for (= 0; i < len; i++) {
                    record = records[i];
                    if (record.isNonData) {
                        continue;
                    }
 
                    row = view.getRowFromItem(items[i]);
 
                    // May be a placeholder with no data row 
                    if (row) {
                        cell = row.cells[me.getVisibleIndex()].firstChild;
                        if (!isFixedSize && !width) {
                            width = me.lastBox.width - parseInt(me.getCachedStyle(cell, 'padding-left'), 10) - parseInt(me.getCachedStyle(cell, 'padding-right'), 10);
                        }
 
                        widget = me.liveWidgets[record.internalId] = me.getFreeWidget();
                        widget.$widgetColumn = me;
                        widget.$widgetRecord = record;
 
                        // Render/move a widget into the new row 
                        Ext.fly(cell).empty();
 
                        // Call the appropriate setter with this column's data field 
                        if (widget.defaultBindProperty && dataIndex) {
                            widget.setConfig(widget.defaultBindProperty, record.get(dataIndex));
                        }
                        
                        if (hasAttach) {
                            Ext.callback(me.onWidgetAttach, me.scope, [me, widget, record], 0, me);
                        }
 
                        el = widget.el || widget.element;
                        if (el) {
                            cell.appendChild(el.dom);
                            if (!isFixedSize) {
                                widget.setWidth(width);
                            }
                            widget.reattachToBody();
                        } else {
                            if (!isFixedSize) {
                                widget.width = width;
                            }
                            widget.render(cell);
                        }
                       
                        // If the widget has a focusEl, ensure that its tabbability status is synched with the view's 
                        // navigable/actionable state. 
                        focusEl = widget.getFocusEl();
                        if (focusEl) {
                            if (view.actionableMode) {
                                if (!focusEl.isTabbable()) {
                                    focusEl.restoreTabbableState();
                                }
                            } else {
                                if (focusEl.isTabbable()) {
                                    focusEl.saveTabbableState();
                                }
                            }
                        }
                    }
                }
            }
        },
 
        onItemRemove: function(records, index, items) {
            var me = this,
                liveWidgets = me.liveWidgets,
                widget, item, id, len, i, focusEl;
 
            if (me.rendered) {
 
                // Single item or Array. 
                items = Ext.Array.from(items);
                len = items.length;
 
                for (= 0; i < len; i++) {
                    item = items[i];
 
                    // If there was a record ID (collapsed placeholder will no longer be  
                    // accessible)... return ousted widget to free stack, and move its element  
                    // to the detached body 
                    id = item.getAttribute('data-recordId');
                    if (id && (widget = liveWidgets[id])) {
                        delete liveWidgets[id];
                        me.freeWidgetStack.unshift(widget);
                        widget.$widgetRecord = widget.$widgetColumn = null;
 
                        // Focusables in a grid must not be tabbable by default when they get put back in. 
                        focusEl = widget.getFocusEl();
                        if (focusEl) {
                            // Widgets are reused so we must reset their tabbable state 
                            // regardless of their visibility. 
                            // For example, when removing rows in IE8 we're attaching 
                            // the nodes to a document-fragment which itself is invisible, 
                            // so isTabbable() returns false. Next time when we're reusing 
                            // this widget it will be attached to the document with its 
                            // tabbable state unreset, which might lead to undesired results. 
                            if (focusEl.isTabbable(true)) {
                                focusEl.saveTabbableState({
                                    includeHidden: true
                                });
                            }
 
                            // Some browsers do not deliver a focus change upon DOM removal. 
                            // Force the issue here. 
                            focusEl.blur();
                        }
 
                        widget.detachFromBody();
                    }
                }
            }
        },
 
        onItemUpdate: function(record, recordIndex, oldItemDom) {
            this.updateWidget(record);
        },
 
        onViewRefresh: function(view, records) {
            var me = this,
                rows = view.all,
                hasAttach = !!me.onWidgetAttach,
                oldWidgetMap = me.liveWidgets,
                dataIndex = me.dataIndex,
                isFixedSize = me.isFixedSize,
                cell, widget, el, width, recordId,
                itemIndex, recordIndex, record, id, lastBox, dom;
 
            if (me.isVisible(true)) {
                me.liveWidgets = {};
                Ext.suspendLayouts();
                for (itemIndex = rows.startIndex, recordIndex = 0; itemIndex <= rows.endIndex; itemIndex++, recordIndex++) {
                    record = records[recordIndex];
                    if (record.isNonData) {
                        continue;
                    }
 
                    recordId = record.internalId;
                    cell = view.getRow(rows.item(itemIndex)).cells[me.getVisibleIndex()].firstChild;
 
                    // Attempt to reuse the existing widget for this record. 
                    widget = me.liveWidgets[recordId] = oldWidgetMap[recordId] || me.getFreeWidget();
                    widget.$widgetRecord = record;
                    widget.$widgetColumn = me;
                    delete oldWidgetMap[recordId];
 
                    lastBox = me.lastBox;
                    if (lastBox && !isFixedSize && width === undefined) {
                        width = lastBox.width - parseInt(me.getCachedStyle(cell, 'padding-left'), 10) - parseInt(me.getCachedStyle(cell, 'padding-right'), 10);
                    }
 
                    // Call the appropriate setter with this column's data field 
                    if (widget.defaultBindProperty && dataIndex) {
                        widget.setConfig(widget.defaultBindProperty, records[recordIndex].get(dataIndex));
                    }
                    if (hasAttach) {
                        Ext.callback(me.onWidgetAttach, me.scope, [me, widget, record], 0, me);
                    }
 
                    el = widget.el || widget.element;
                    if (el) {
                        dom = el.dom;
                        if (dom.parentNode !== cell) {
                            Ext.fly(cell).empty();
                            cell.appendChild(el.dom);
                        }
                        if (!isFixedSize) {
                            widget.setWidth(width);
                        }
                        widget.reattachToBody();
                    } else {
                        if (!isFixedSize) {
                            widget.width = width;
                        }
                        Ext.fly(cell).empty();
                        widget.render(cell);
                    }
                }
 
                Ext.resumeLayouts(true);
 
                // Free any unused widgets from the old live map. 
                // Move them into detachedBody. 
                for (id in oldWidgetMap) {
                    widget = oldWidgetMap[id];
                    widget.$widgetRecord = widget.$widgetColumn = null;
                    me.freeWidgetStack.unshift(widget);
                    widget.detachFromBody();
                }
            }
        },
 
        returnFalse: function() {
            return false;
        },
 
        setupViewListeners: function(view) {
            var me = this;
 
            me.viewListeners = view.on({
                refresh: me.onViewRefresh,
                itemupdate: me.onItemUpdate,
                itemadd: me.onItemAdd,
                itemremove: me.onItemRemove,
                scope: me,
                destroyable: true
            });
 
            if (Ext.isIE8) {
                view.on('beforerefresh', me.onBeforeRefresh, me);
            }
        },
 
        updateWidget: function(record) {
            var dataIndex = this.dataIndex,
                widget;
 
            if (this.rendered) {
                widget = this.liveWidgets[record.internalId];
                // Call the appropriate setter with this column's data field 
                if (widget && widget.defaultBindProperty && dataIndex) {
                    widget.setConfig(widget.defaultBindProperty, record.get(dataIndex));
                }
            }
        }, 
        
        widgetRecordDecorator: function() {
            return this.$widgetRecord;
        },
        
        widgetColumnDecorator: function() {
            return this.$widgetColumn;
        }
    }
});