/**
 * Private record store class which takes the place of the view's data store to provide a grouped
 * view of the data when the Grouping feature is used.
 * 
 * Relays granular mutation events from the underlying store as refresh events to the view.
 * 
 * On mutation events from the underlying store, updates the summary rows by firing update events on the corresponding
 * summary records.
 * @private
 */
Ext.define('Ext.grid.feature.GroupStore', {
    extend: 'Ext.util.Observable',

    isStore: true,

    constructor: function(groupingFeature, store) {
        var me = this;

        me.superclass.constructor.apply(me, arguments);
        me.groupingFeature = groupingFeature;
        me.bindStore(store);
        me.processStore(store);
        me.view.dataSource = me;
    },

    bindStore: function(store) {
        var me = this;

        if (me.store) {
            Ext.destroy(me.storeListeners);
            me.store = null;
        }
        if (store) {
            me.storeListeners = store.on({
                bulkremove: me.onBulkRemove,
                add: me.onAdd,
                update: me.onUpdate,
                refresh: me.onRefresh,
                clear: me.onClear,
                scope: me,
                destroyable: true
            });
            me.store = store;
        }
    },

    processStore: function(store) {
        var me = this,
            Model = store.model,
            groups = store.getGroups(),
            groupCount = groups.length,
            i,
            group,
            groupPlaceholder,
            data = me.data,
            oldGroupCache = this.groupingFeature.groupCache,
            groupCache = this.groupingFeature.groupCache = {},
            collapseAll = me.groupingFeature.startCollapsed;

        if (data) {
            data.clear();
        } else {
            data = me.data = new Ext.util.MixedCollection(false, Ext.data.Store.recordIdFn);
        }

        for (i = 0; i < groupCount; i++) {

            // group contains eg
            // { children: [childRec0, childRec1...], name: <group field value for group> }
            group = groups[i];

            // Cache group information by group name
            groupCache[group.name] = group;
            group.isCollapsed = collapseAll || (oldGroupCache[group.name] && oldGroupCache[group.name].isCollapsed);

            // If group is collapsed, then represent it by one dummy row which is never visible, but which acts
            // as a start and end group trigger.
            if (group.isCollapsed) {
                group.placeholder = groupPlaceholder = new Model(null, 'group-' + group.name + '-placeholder');
                groupPlaceholder.set(me.getGroupField(), group.name);
                groupPlaceholder.rows = groupPlaceholder.children = group.children;
                data.add(groupPlaceholder);
            }

            // Expanded group - add the group's child records.
            else {
                data.insert(me.data.length, group.children);
            }
        }
    },
    
    isCollapsed: function(name) {
        return this.groupingFeature.groupCache[name].isCollapsed; 
    },

    isInCollapsedGroup: function(record) {
        var groupData;

        if (this.store.isGrouped() && (groupData = this.groupingFeature.groupCache[record.get(this.getGroupField())])) {
            return groupData.isCollapsed || false;
        }
        return false;
    },

    getCount: function() {
        return this.data.getCount();
    },

    getTotalCount: function() {
        return this.data.getCount();
    },

    // This class is only created for fully loaded, non-buffered stores
    rangeCached: function() {
        return true;
    },

    getRange: function(start, end, options) {
        var result = this.data.getRange(start, end);

        if (options && options.callback) {
            options.callback.call(options.scope || this, result, start, end, options);
        }
        return result;
    },

    getAt: function(index) {
        return this.getRange(index, index)[0];
    },

    getById: function(id) {
        return this.store.getById(id);
    },

    // Find index of record in group store.
    // If it's in a collapsed group, then it's -1, not present
    // Otherwise, loop through groups keeping tally of intervening records.
    indexOf: function(record, viewOnly) {
        var me = this,
            groupField,
            groups,
            groupCount,
            i,
            group,
            groupIndex,
            result = 0;

        if (record && !me.isInCollapsedGroup(record)) {
            groupField = me.store.groupField;
            groups = me.store.getGroups();
            groupCount = groups.length;
            for (i = 0; i < groupCount; i++) {

                // group contains eg
                // { children: [childRec0, childRec1...], name: <group field value for group> }
                group = groups[i];
                if (group.name === record.get(groupField)) {
                    groupIndex = Ext.Array.indexOf(group.children, record);
                    return result + groupIndex;
                }

                result += (viewOnly && this.isCollapsed(group.name)) ? 1 : group.children.length;
            }
        }
        return -1;
    },

    onRefresh: function(store) {
        this.processStore(this.store);
        this.fireEvent('refresh', this);
    },

    onBulkRemove: function(store, records, indices) {
        this.processStore(this.store);
        this.fireEvent('refresh', this);
    },

    onClear: function(store, records, startIndex) {
        this.processStore(this.store);
        this.fireEvent('clear', this);
    },

    onAdd: function(store, records, startIndex) {
        this.processStore(this.store);
        this.fireEvent('refresh', this);
    },

    onUpdate: function(store, record, operation, modifiedFieldNames) {
        var me = this,
            groupInfo = me.groupingFeature.getRecordGroup(record),
            firstRec, lastRec;

        // The grouping field value has been modified.
        // This could either move a record from one group to another, or introduce a new group.
        // Either way, we have to refresh the grid
        if (store.isGrouped()) {
            if (modifiedFieldNames && Ext.Array.contains(modifiedFieldNames, me.groupingFeature.getGroupField())) {
                return me.onRefresh(me.store);
            }

            // Fire an update event on the collapsed group placeholder record
            if (groupInfo.isCollapsed) {
                me.fireEvent('update', me, groupInfo.placeholder);
            }

            // Not in a collapsed group, fire update event on the modified record
            // and, if in a grouped store, on the first and last records in the group.
            else {
                Ext.suspendLayouts();

                // Propagate the record's update event
                me.fireEvent('update', me, record, operation, modifiedFieldNames);

                // Fire update event on first and last record in group (only once if a single row group)
                // So that custom header TPL is applied, and the summary row is updated
                firstRec = groupInfo.children[0];
                lastRec = groupInfo.children[groupInfo.children.length - 1];

                // Do not pass modifiedFieldNames so that the TableView's shouldUpdateCell call always returns true.
                if (firstRec !== record) {
                    me.fireEvent('update', me, firstRec, 'edit');
                }
                if (lastRec !== record && lastRec !== firstRec) {
                    me.fireEvent('update', me, lastRec, 'edit');
                }
                Ext.resumeLayouts(true);
            }
        }
    }
});