/**
 * This is the base class for {@link Ext.grid.Grid grid} cells.
 *
 * {@link Ext.grid.Row Rows} create cells based on the {@link Ext.grid.column.Column#cell}
 * config. Application code would rarely create cells directly.
 */
Ext.define('Ext.grid.cell.Base', {
    extend: 'Ext.Widget',
    xtype: 'gridcellbase',
 
    isGridCell: true,
 
    mixins: [
        'Ext.mixin.Toolable'
    ],
 
    cachedConfig: {
        /**
         * @cfg {"left"/"center"/"right"} align
         * The value for the `text-align` of the cell content.
         */
        align: null,
 
        /**
         * @cfg {String} cls
         * An arbitrary CSS class to add to the cell's outermost element.
         */
        cls: null,
 
        /**
         * @cfg {String} bodyCls
         * An arbitrary CSS class to add to the cell's inner element (the element that
         * typically contains the cell's text).
         */
        bodyCls: null,
 
        /**
         * @cfg {String/Object} bodyStyle
         * Additional CSS styles that will be rendered into the cell's inner element (the
         * element that typically contains the cell's text).
         *
         * You can pass either a string syntax:
         *
         *     bodyStyle: 'background:red'
         *
         * Or by using an object:
         *
         *     bodyStyle: {
         *         background: 'red'
         *     }
         *
         * When using the object syntax, you can define CSS Properties by using a string:
         *
         *     bodyStyle: {
         *         'border-left': '1px solid red'
         *     }
         *
         * Although the object syntax is much easier to read, we suggest you to use the
         * string syntax for better performance.
         */
        bodyStyle: null,
 
        /**
         * @cfg {String} cellCls
         *
         * @protected
         */
        cellCls: null,
 
        /**
         * @cfg {Boolean} [selectable=true]
         * Set to `false` to disable selection of the record when tapping on this cell.
         */
        selectable: null
    },
 
    config: {
        /**
         * @cfg {Ext.grid.column.Column} column
         * The grid column that created this cell.
         * @readonly
         */
        column: null,
 
        /**
         * @cfg {Boolean} hidden
         * The hidden state of this cell (propagated from the column's hidden state).
         * @private
         */
        hidden: false,
 
        /**
         * @cfg {Ext.data.Model} record
         * The currently associated record.
         * @readonly
         */
        record: null,
 
        /**
         * @cfg {Mixed} value
         * The value of the {@link Ext.grid.column.Column#dataIndex dataIndex} field of
         * the associated record. Application code should not need to set this value.
         */
        value: null
    },
 
    classCls: Ext.baseCSSPrefix + 'gridcell',
    dirtyCls: Ext.baseCSSPrefix + 'dirty',
 
    alignCls: {
        left: Ext.baseCSSPrefix + 'align-left',
        center: Ext.baseCSSPrefix + 'align-center',
        right: Ext.baseCSSPrefix + 'align-right'
    },
 
    inheritUi: true,
 
    cellSelector: '.' + Ext.baseCSSPrefix + 'gridcell',
 
    defaultBindProperty: 'value',
 
    toolDefaults: {
        zone: 'head',
        ui: 'gridcell'
    },
 
    getTemplate: function() {
        var template = {
            reference: 'bodyElement',
            cls: Ext.baseCSSPrefix + 'body-el',
            uiCls: 'body-el'
        };
 
        // hook for subclasses to add elements inside the inner element
        // e.g. checkcell, expandercell
        if (!(template.children = this.innerTemplate)) {
            // Otherwise ensure that cells have content and achieve a proper height
            template.html = '\xA0';
        }
 
        return [template];
    },
 
    doDestroy: function() {
        this.setColumn(null);
        this.setRecord(null);
        this.mixins.toolable.doDestroy.call(this);
 
        this.callParent();
    },
 
    getComputedWidth: function() {
        return this.getHidden() ? 0 : this.getWidth();
    },
 
    updateAlign: function(align, oldAlign) {
        var me = this,
            alignCls = me.alignCls;
 
        if (oldAlign) {
            me.removeCls(alignCls[oldAlign]);
        }
 
        if (align) {
            //<debug>
            if (!alignCls[align]) {
                Ext.raise("Invalid value for align: '" + align + "'");
            }
 
            //</debug>
            me.addCls(alignCls[align]);
        }
 
        me.syncToolableAlign();
    },
 
    updateBodyCls: function(cellCls, oldCellCls) {
        if (cellCls || oldCellCls) {
            this.bodyElement.replaceCls(oldCellCls, cellCls);
        }
    },
 
    updateBodyStyle: function(style) {
        this.bodyElement.applyStyles(style);
    },
 
    updateCellCls: function(cls, oldCls) {
        this.element.replaceCls(oldCls, cls);
    },
 
    updateCls: function(cls, oldCls) {
        this.element.replaceCls(oldCls, cls);
    },
 
    updateColumn: function(column) {
        var dataIndex = null,
            row = this.row;
 
        if (column) {
            dataIndex = ((row && row.isSummaryRow) && column.getSummaryDataIndex()) ||
                    column.getDataIndex();
        }
 
        this.dataIndex = dataIndex;
    },
 
    updateRecord: function() {
        if (!this.destroyed && !this.destroying) {
            this.refresh();
        }
    },
 
    updateSelectable: function(value) {
        this.toggleCls(Ext.baseCSSPrefix + 'item-no-select', value === false);
    },
 
    refresh: function(ctx) {
        var me = this,
            was = me.refreshContext,
            context, modified, value;
 
        if (!me.isBound('value')) {
            ctx = ctx || was;
            modified = ctx && ctx.modified;
 
            if (!modified || me.bound(modified)) {
                me.refreshContext = context = me.beginRefresh(ctx);
 
                value = me.refreshValue(context);
 
                if (value !== me.getValue()) {
                    me.setValue(value);
                }
                else if (me.writeValue) {
                    me.writeValue();
                }
 
                me.refreshContext = was;
            }
        }
    },
 
    refreshValue: function(context) {
        var me = this,
            record = context.record,
            dataIndex = context.dataIndex,
            value, dirty, modified;
 
        if (context.summary) {
            value = me.summarize(context);
        }
        else if (record && dataIndex) {
            value = record.get(dataIndex);
            modified = record.modified;
            dirty = !!(modified && modified.hasOwnProperty(dataIndex));
 
            if (dirty !== me.$dirty) {
                me.toggleCls(me.dirtyCls, dirty);
 
                me.$dirty = dirty;
            }
        }
 
        return value;
    },
 
    privates: {
        //<debug>
        refreshCounter: 0,
        //</debug>
 
        $dirty: false,
 
        /**
         * @property {Object} refreshContext
         * This object holds a cache of information used across the cells of a row during
         * a `refresh` pass (when the record changes).
         * @since 6.5.0
         * @private
         */
        refreshContext: null,
 
        storeMethodRe: /^(?:average|max|min|sum)$/,
 
        augmentToolHandler: function(tool, args) {
            // args = [ cell, tool, ev ]   ==>   [ grid, info ]
            var info = args[1] = {
                event: args.pop(),
                record: this.getRecord(),
                column: this.getColumn(),
                cell: args[0],
                tool: args[1]
            };
 
            args[0] = info.grid = info.column.getGrid();
        },
 
        beginRefresh: function(context) {
            var me = this,
                column = me.getColumn(),
                row = me.row;
 
            // Ask our parent row or column to kick things off...
            context = context ||
                (row ? row.beginRefresh() : { record: me.getRecord() });
 
            //<debug>
            ++me.refreshCounter; // for testing
            context.from = context.from || 'cell';
            //</debug>
 
            context.cell = me;
            context.column = column;
            context.dataIndex = me.dataIndex;
            context.scope = column.getScope();
 
            return context;
        },
 
        /**
         * Returns `true` if this cell's value is bound to any of the given `fields`. This
         * is typically due to the `dataIndex`.
         * @param {Object} fields An object keyed by field names with truthy values.
         * @return {Boolean} 
         * @since 6.5.1
         * @private
         */
        bound: function(fields) {
            return !!fields[this.dataIndex];
        },
 
        summarize: function(context) {
            var me = this,
                column = context.column,
                summaryType = column.getSummaryType(),
                dataIndex = context.dataIndex,
                group = context.group,
                store = context.store,
                records = context.records,
                value;
 
            if (summaryType) {
                //<debug>
                if (!column.$warnSummaryType) {
                    column.$warnSummaryType = true;
                    Ext.log.warn('[column] summaryType is deprecated; use summaryRenderer (' +
                        column.getId() + ')');
                }
                //</debug>
 
                if (Ext.isFunction(summaryType)) {
                    value = summaryType.call(store, store.data.items.slice(), dataIndex);
                }
                else if (summaryType === 'count') {
                    value = store.getCount();
                }
                else if (me.storeMethodRe.test(summaryType)) {
                    value = store[summaryType](dataIndex);
                }
                else {
                    value = Ext.callback(summaryType, null,
                                         [ store.data.items.slice(), dataIndex, store ], 0, me);
                }
            }
            else if (!(summaryType = column.getSummary())) {
                if (dataIndex) {
                    value = context.record.get(dataIndex);
                }
            }
            // summaryType is an Ext.data.summary.* fellow
            //<debug>
            else if (!dataIndex) {
                Ext.raise('Cannot use summary config w/o summaryDataIndex or dataIndex (' +
                        context.grid.getId() + ')');
            }
            //</debug>
            else {
                //<debug>
                if (group) {
                    if (group.isVirtualGroup) {
                        Ext.raise('Cannot calculate a group summary on a virtual store (' +
                            context.grid.getId() + ')');
                    }
                }
                else if (store.getRemoteSort()) {
                    Ext.raise('Cannot calculate a summary on a remoteSort store (' +
                        context.grid.getId() + ')');
                }
                //</debug>
 
                value = summaryType.calculate(records, dataIndex, 'data', 0, records.length);
            }
 
            return value;
        }
    }, // privates
 
    deprecated: {
        '6.5': {
            configs: {
                innerStyle: 'bodyStyle',
                innerCls: 'bodyCls'
            }
        }
    }
});