/**
 * This is the default cell class for {@link Ext.grid.Grid grid} cells. Use this class if
 * you use the {@link Ext.grid.column.Column#renderer} or {@link Ext.grid.column.Column#tpl}
 * configs for a column.
 *
 * {@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.Cell', {
    extend: 'Ext.grid.cell.Text',
    xtype: 'gridcell',
 
    config: {
        /**
         * @cfg {String/String[]/Ext.XTemplate} tpl
         * An {@link Ext.XTemplate XTemplate}, or an XTemplate *definition string* to use
         * to process a {@link Ext.data.Model records} data to produce a cell's rendered
         * value.
         *
         *     @example
         *     Ext.create('Ext.data.Store', {
         *         storeId:'employeeStore',
         *         fields:['firstname', 'lastname', 'seniority', 'department'],
         *         groupField: 'department',
         *         data:[
         *             { firstname: "Michael", lastname: "Scott",   seniority: 7, department: "Management" },
         *             { firstname: "Dwight",  lastname: "Schrute", seniority: 2, department: "Sales" },
         *             { firstname: "Jim",     lastname: "Halpert", seniority: 3, department: "Sales" },
         *             { firstname: "Kevin",   lastname: "Malone",  seniority: 4, department: "Accounting" },
         *             { firstname: "Angela",  lastname: "Martin",  seniority: 5, department: "Accounting" }
         *         ]
         *     });
         *
         *     Ext.create('Ext.grid.Panel', {
         *         title: 'Column Template Demo',
         *         store: Ext.data.StoreManager.lookup('employeeStore'),
         *         columns: [{
         *             text: 'Full Name',
         *             tpl: '{firstname} {lastname}'
         *         }, {
         *             text: 'Department (Yrs)',
         *             tpl: '{department} ({seniority})'
         *         }],
         *         height: 200,
         *         width: 300,
         *         renderTo: Ext.getBody()
         *     });
         *
         * This config is only processed if the {@link #cell} type is the default of
         * {@link Ext.grid.cell.Cell gridcell}.
         *
         * **Note** See {@link Ext.grid.Grid} documentation for other, better alternatives
         * to rendering cell content.
         */
        tpl: null,
 
        /**
         * @cfg {Function/String} renderer
         * A renderer is a method which can be used to transform data (value, appearance, etc.)
         * before it is rendered.
         *
         * For example:
         *
         *      {
         *          text: 'Some column',
         *          dataIndex: 'fieldName',
         *
         *          renderer: function (value, record) {
         *              if (value === 1) {
         *                  return '1 person';
         *              }
         *              return value + ' people';
         *          }
         *      }
         *
         * If a string is supplied, it should be the name of a renderer method from the
         * appropriate {@link Ext.app.ViewController}.
         *
         * This config is only processed if the {@link #cell} type is the default of
         * {@link Ext.grid.cell.Cell gridcell}.
         *
         * **Note** See {@link Ext.grid.Grid} documentation for other, better alternatives
         * to rendering cell content.
         *
         * @cfg {Object} renderer.value The data value for the current cell.
         * @cfg {Ext.data.Model} renderer.record The record for the current row.
         * @cfg {Number} renderer.dataIndex The dataIndex of the current column.
         * @cfg {Ext.grid.cell.Base} renderer.cell The current cell.
         * @cfg {Ext.grid.column.Column} renderer.column The current column.
         * @cfg {String} renderer.return The HTML string to be rendered.
         */
        renderer: null,
 
        /**
         * @cfg {String} formatter
         * This config accepts a format specification as would be used in a `Ext.Template`
         * formatted token. For example `'round(2)'` to round numbers to 2 decimal places
         * or `'date("Y-m-d")'` to format a Date.
         *
         * In previous releases the `renderer` config had limited abilities to use one
         * of the `Ext.util.Format` methods but `formatter` now replaces that usage and
         * can also handle formatting parameters.
         *
         * When the value begins with `"this."` (for example, `"this.foo(2)"`), the
         * implied scope on which "foo" is found is the `scope` config for the column.
         *
         * If the `scope` is not given, or implied using a prefix of `"this"`, then either the
         * {@link #method-getController ViewController} or the closest ancestor component configured
         * as {@link #defaultListenerScope} is assumed to be the object with the method.
         * @since 6.2.0
         */
        formatter: null,
 
        /**
         * @cfg {Object} scope
         * The scope to use when calling the {@link #renderer} or {@link #formatter} function.
         */
        scope: null
    },
 
    friendly: null,
 
    updateColumn: function (column, oldColumn) {
        var me = this,
            friendly = true,
            tpl, renderer, formatter;
 
        me.callParent([ column, oldColumn ]);
 
        /*
            The `tpl`, `renderer` and `formatter` configs are on the Cell level
            so that a ViewModel can be used for cells to change these configs dynamically.
            If `formatter` is changed dynamically then performance will decrease since
            expressions need to be parsed for each cell.
        */
 
        if (column) {
            tpl = column.getTpl();
            renderer = column.getRenderer();
            formatter = column.getFormatter();
 
            if (renderer !== null) {
                me.setRenderer(renderer);
                friendly = (typeof renderer === 'function') && renderer.length === 1;
            }
            if (tpl !== null) {
                me.setTpl(tpl);
                friendly = false;
            }
            if (formatter !== null) {
                me.setFormatter(formatter);
            }
 
            me.friendly = friendly;
        }
    },
 
    applyTpl: function (tpl) {
        return Ext.XTemplate.get(tpl);
    },
 
    applyFormatter: function (format) {
        var me = this,
            fmt = format,
            parser;
 
        if (typeof fmt === 'string') {
            parser = Ext.app.bind.Parser.fly(fmt);
            fmt = parser.compileFormat();
            parser.release();
            return function (v) {
                return fmt(v, me.getScope() || me.resolveListenerScope());
            };
        }
        //<debug>
        else if (typeof fmt !== 'function') {
            Ext.raise('Invalid formatter');
        }
        //</debug>
 
        return fmt;
    },
 
    updateTpl: function () {
        if (!this.isConfiguring) {
            this.refresh();
        }
    },
 
    updateRenderer: function () {
        if (!this.isConfiguring) {
            this.refresh();
        }
    },
 
    updateFormatter: function () {
        if (!this.isConfiguring) {
            this.refresh();
        }
    },
 
    formatValue: function (v) {
        var me = this,
            context = me.refreshContext,
            dataIndex = context.dataIndex,
            column = context.column,
            record = context.record,
            zeroValue = me.getZeroValue(),
            raw = v, // raw value takes as default the cell value
            summary = context.summary,
            args, data, format, renderer, scope, tpl;
 
        if (!context.summary && v === 0 && zeroValue !== null) {
            raw = zeroValue;
        }
        else if (!(tpl = me.getTpl(context))) {
            format = me.getFormatter();
 
            if (summary) {
                renderer = column.getSummaryRenderer();
 
                if (renderer) {
                    format = null; // ignore the non-summary formatter
                    scope = context.scope;
 
                    if (typeof renderer === 'string') {
                        raw = Ext.callback(renderer, scope, [ v, context ], 0, column);
                        me.friendly = false;
                    }
                    else {
                        raw = renderer.call(scope || me, v, context);
                        if (renderer.length > 1) {
                            me.friendly = false;
                        }
                    }
                }
 
                format = column.getSummaryFormatter() || format;
            }
            else {
                renderer = me.getRenderer();
 
                if (renderer) {
                    args = [v, record, dataIndex, me, column];
                    scope = me.getScope() || context.scope;
 
                    if (typeof renderer === 'function') {
                        raw = renderer.apply(scope || column, args);
                    }
                    else {
                        raw = Ext.callback(renderer, scope, args, 0, me);
                    }
                }
            }
 
            if (format) {
                raw = format(raw);
            }
        }
        else {
            // We have either:
            //      cell: { tpl: '...' }
            // or:
            //      summaryCell: { tpl: '...' }
 
            if (!(data = context.data)) {
                context.data = data = context.summary ? context.record.getData()
                    : context.grid.gatherData(context.record);
            }
 
            raw = tpl.apply(data);
        }
 
        if (raw != null) {
            raw = String(raw);
        } else {
            raw = '';
        }
 
        return raw;
    },
 
    privates: {
        bound: function (fields) {
            var me = this,
                bound = !!fields[me.dataIndex],
                column, depends, i;
 
            if (!bound) {
                column = me.getColumn();
                depends = column && column.getDepends();
 
                if (depends) {
                    for (= depends.length; !bound && i-- > 0; ) {
                        bound = !!fields[depends[i]];
                    }
                }
                else if (!me.friendly) {
                    bound = true;
                }
            }
 
            return bound;
        }
    }
});