/**
 * This specialized type of Store is created by a `list` or `grid` when the `collapsed`
 * config is specified. It presents a view of the underlying store with the records in
 * collapsed groups replaced by a placeholder.
 * @private
 */
Ext.define('Ext.dataview.GroupStore', {
    extend: 'Ext.data.ChainedStore',
 
    isGroupStore: true,
 
    syncSourceGrouping: true,
 
    /*
                                     +-----------------+
                               +---->| GroupCollection |
                              /      +-----------------+
            +------------+   /              ^   |
            | GroupStore |--+               :   |
            +------------+   \              :   v
                 ^ | ^        \         +------------+      +------------------+
                 : | :         +------->| Collection |----->| SorterCollection |
                 : | :................. +------------+      +------------------+
                 : |                        ^   |                   ^  |
        observer : | source                 :   |                   :  |
                 : |                        :   v                   :  v
                 : | .................. +------------+      +------------------+
                 : | :         +------->| Collection |----->| SorterCollection |
                 : v v        /         +------------+      +------------------+
               +-------+     /              :   ^
               | Store |----+               :   |
               +-------+     \              v   |
                              \      +-----------------+
                               +---->| GroupCollection |
                                     +-----------------+
     */
 
    getRootSource: function() {
        var source = this.source;
 
        while (source && source.getSource) {
            source = source.getSource();
        }
 
        return source;
    },
 
    getTotalCount: function() {
        return this.getRootSource().getTotalCount();
    },
 
    /**
     * @method load
     * @inheritdoc Ext.data.ProxyStore#load
     */
    load: function(options) {
        var source = this.getRootSource();
 
        if (!source) {
            return;
        }
 
        return source.load.apply(source, arguments);
    },
 
    /**
     * @method loadPage
     * @inheritdoc Ext.data.ProxyStore#loadPage
     */
    loadPage: function(page, options) {
        var me = this,
            source = this.getRootSource();
 
        me.currentPage = page;
 
        if (!source) {
            return;
        }
 
        return source.loadPage.apply(source, arguments);
    },
 
    /**
     * @method sync
     * @inheritdoc Ext.data.ProxyStore#sync
     */
    sync: function(options) {
        var source = this.getRootSource();
 
        //<debug>
        if (!source) {
            Ext.raise('Cannot sync records with no source.');
        }
        //</debug>
 
        return source.sync.apply(source, arguments);
    },
 
    /**
     * @method erase
     * @inheritdoc Ext.data.ProxyStore#erase
     */
    erase: function(options) {
        var source = this.getRootSource();
 
        //<debug>
        if (!source) {
            Ext.raise('Cannot erase records with no source.');
        }
        //</debug>
 
        return source.erase.apply(source, arguments);
    },
 
    /**
     * Loads the next 'page' in the current data set
     * @param {Object} options See options for {@link #method-load}
     */
    nextPage: function(options) {
        this.loadPage(this.currentPage + 1, options);
    },
 
    /**
     * Loads the previous 'page' in the current data set
     * @param {Object} options See options for {@link #method-load}
     */
    previousPage: function(options) {
        this.loadPage(this.currentPage - 1, options);
    },
 
    setGrouper: function(grouper) {
        var me = this,
            source = me.getSource();
 
        if (source && grouper !== source.getGrouper()) {
            source.setGrouper(grouper);
 
            grouper = source.getGrouper();
        }
 
        me.callParent([ grouper ]);
 
        me.refreshFromSource();
    },
 
    constructDataCollection: function() {
        var me = this,
            collection = me.callParent();
 
        // This maintains the indirection via name unlike "...bind(me)":
        collection.transformItems = function(items) {
            return me.transformItems(collection, items);
        };
 
        return collection;
    },
 
    createFiltersCollection: function() {
        return this.getSource().getFilters();
    },
 
    refreshFromSource: function() {
        var data = this.getData(),
            list = this.list,
            was = list.getScrollToTopOnRefresh();
 
        list.setScrollToTopOnRefresh(false);
 
        data.onCollectionRefresh(data.getSource());
 
        list.setScrollToTopOnRefresh(was);
    },
 
    transformItems: function(collection, records) {
        var me = this,
            n = records.length,
            list = me.list,
            collapser = list.isGrouping() && list.getCollapsible(),
            ret = records,
            groupMap = {},
            source = me.getSource(),
            M = source.getModel(),
            groupField = collapser && source.getGrouper().getProperty(),
            collapsed, copy, group, groupKey, i, placeholder, rec, sourceGroups, srcGroup,
            startCollapsed;
 
        if (collapser) {
            sourceGroups = collection.getSource().getGroups();
            startCollapsed = collapser.getCollapsed();
 
            for (= 0; i < n; ++i) {
                rec = records[i];
                srcGroup = sourceGroups.getItemGroup(rec);
                group = list.groupFrom(groupKey = srcGroup.getGroupKey());
                collapsed = group ? group.collapsed : startCollapsed;
 
                if (collapsed) {
                    if (!copy) {
                        copy = ret = records.slice(0, i);
                    }
 
                    if (!groupMap[groupKey]) {
                        if (!(placeholder = srcGroup.placeholderRec)) {
                            srcGroup.placeholderRec = placeholder = new M();
 
                            placeholder.data[groupField] = placeholder.$groupKey = groupKey;
                            placeholder.$groupValue = srcGroup.getGroupValue();
                            placeholder.$collapsedGroupPlaceholder = true;
                            placeholder.$srcGroup = srcGroup;
                        }
 
                        groupMap[groupKey] = placeholder;
 
                        copy.push(placeholder);
                    }
                }
                else if (copy) {
                    copy.push(rec);
                }
            }
        }
 
        return ret;
    },
 
    updateSource: function(source, oldSource) {
        var me = this;
 
        if (source) {
            me.list.store = me;
        }
 
        if (oldSource && !oldSource.destroyed && oldSource.getAutoDestroy()) {
            oldSource.destroy();
        }
 
        me.callParent([ source, oldSource ]);
 
        if (source && !me.destroying) {
            me.getSorters().setSource(source.getSorters());
        }
    }
});